Skip to content

Commit 23659df

Browse files
fixed typing
1 parent b660acb commit 23659df

8 files changed

+60
-51
lines changed

tests/test_identity_map_client.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@
77
from uid2_client import IdentityMapClient, IdentityMapInput, normalize_and_hash_email, normalize_and_hash_phone
88

99

10-
@unittest.skipIf(
11-
os.getenv("UID2_BASE_URL") == None
12-
or os.getenv("UID2_API_KEY") == None
13-
or os.getenv("UID2_SECRET_KEY") == None,
14-
reason="Environment variables UID2_BASE_URL, UID2_API_KEY, and UID2_SECRET_KEY must be set",
15-
)
1610
class IdentityMapIntegrationTests(unittest.TestCase):
1711
UID2_BASE_URL = None
1812
UID2_API_KEY = None

tests/test_identity_map_v3_client.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@
88
from uid2_client.unmapped_identity_reason import UnmappedIdentityReason
99

1010

11-
@unittest.skipIf(
12-
os.getenv("UID2_BASE_URL") == None
13-
or os.getenv("UID2_API_KEY") == None
14-
or os.getenv("UID2_SECRET_KEY") == None,
15-
reason="Environment variables UID2_BASE_URL, UID2_API_KEY, and UID2_SECRET_KEY must be set",
16-
)
1711
class IdentityMapV3IntegrationTests(unittest.TestCase):
1812
UID2_BASE_URL = None
1913
UID2_API_KEY = None

tests/test_publisher_client.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@
88
from urllib.request import HTTPError
99

1010

11-
@unittest.skipIf(
12-
os.getenv("EUID_BASE_URL") == None
13-
or os.getenv("EUID_API_KEY") == None
14-
or os.getenv("EUID_SECRET_KEY") == None,
15-
reason="Environment variables EUID_BASE_URL, EUID_API_KEY, and EUID_SECRET_KEY must be set",
16-
)
1711
class PublisherEuidIntegrationTests(unittest.TestCase):
1812

1913
EUID_SECRET_KEY = None

uid2_client/identity_map_v3_input.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from typing import List, Dict
2+
from typing import List, Dict, Literal
33

44
from uid2_client import normalize_and_hash_email, normalize_and_hash_phone
55

@@ -36,7 +36,7 @@ def with_emails(self, emails: List[str]) -> 'IdentityMapV3Input':
3636
def with_email(self, email: str) -> 'IdentityMapV3Input':
3737
hashed_email = normalize_and_hash_email(email)
3838
self._hashed_emails.append(hashed_email)
39-
self._add_hashed_to_raw_dii_mapping(hashed_email, email)
39+
self._add_to_dii_mappings(hashed_email, email)
4040
return self
4141

4242
def with_phones(self, phones: List[str]) -> 'IdentityMapV3Input':
@@ -47,7 +47,7 @@ def with_phones(self, phones: List[str]) -> 'IdentityMapV3Input':
4747
def with_phone(self, phone: str) -> 'IdentityMapV3Input':
4848
hashed_phone = normalize_and_hash_phone(phone)
4949
self._hashed_phones.append(hashed_phone)
50-
self._add_hashed_to_raw_dii_mapping(hashed_phone, phone)
50+
self._add_to_dii_mappings(hashed_phone, phone)
5151
return self
5252

5353
def with_hashed_emails(self, hashed_emails: List[str]) -> 'IdentityMapV3Input':
@@ -57,7 +57,7 @@ def with_hashed_emails(self, hashed_emails: List[str]) -> 'IdentityMapV3Input':
5757

5858
def with_hashed_email(self, hashed_email: str) -> 'IdentityMapV3Input':
5959
self._hashed_emails.append(hashed_email)
60-
self._add_hashed_to_raw_dii_mapping(hashed_email, hashed_email)
60+
self._add_to_dii_mappings(hashed_email, hashed_email)
6161
return self
6262

6363
def with_hashed_phones(self, hashed_phones: List[str]) -> 'IdentityMapV3Input':
@@ -67,19 +67,19 @@ def with_hashed_phones(self, hashed_phones: List[str]) -> 'IdentityMapV3Input':
6767

6868
def with_hashed_phone(self, hashed_phone: str) -> 'IdentityMapV3Input':
6969
self._hashed_phones.append(hashed_phone)
70-
self._add_hashed_to_raw_dii_mapping(hashed_phone, hashed_phone)
70+
self._add_to_dii_mappings(hashed_phone, hashed_phone)
7171
return self
7272

73-
def get_input_diis(self, identity_type: str, index: int) -> List[str]:
73+
def get_input_diis(self, identity_type: Literal['email_hash', 'phone_hash'], index: int) -> List[str]:
7474
hashed_dii = self._get_hashed_dii(identity_type, index)
7575
return self._hashed_dii_to_raw_diis.get(hashed_dii, [])
7676

77-
def _add_hashed_to_raw_dii_mapping(self, hashed_dii: str, raw_dii: str):
77+
def _add_to_dii_mappings(self, hashed_dii: str, raw_dii: str) -> None:
7878
if hashed_dii not in self._hashed_dii_to_raw_diis:
7979
self._hashed_dii_to_raw_diis[hashed_dii] = []
8080
self._hashed_dii_to_raw_diis[hashed_dii].append(raw_dii)
8181

82-
def _get_hashed_dii(self, identity_type: str, index: int) -> str:
82+
def _get_hashed_dii(self, identity_type: Literal['email_hash', 'phone_hash'], index: int) -> str:
8383
if identity_type == "email_hash":
8484
return self._hashed_emails[index]
8585
elif identity_type == "phone_hash":

uid2_client/identity_map_v3_response.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
import json
22
from datetime import datetime, timezone
3-
from typing import Dict, List, Optional, Any
3+
from typing import Dict, List, Optional, Any, Literal
44

5+
from .identity_map_v3_input import IdentityMapV3Input
56
from .unmapped_identity_reason import UnmappedIdentityReason
67

78

89
class IdentityMapV3Response:
9-
def __init__(self, response: str, identity_map_input):
10+
def __init__(self, response: str, identity_map_input: IdentityMapV3Input):
1011
self._mapped_identities: Dict[str, MappedIdentity] = {}
1112
self._unmapped_identities: Dict[str, UnmappedIdentity] = {}
13+
1214
response_json = json.loads(response)
13-
self._status = response_json["status"]
15+
api_response = ApiResponse.from_json(response_json)
16+
self._status = api_response.status
1417

1518
if not self.is_success():
1619
raise ValueError("Got unexpected identity map status: " + self._status)
1720

18-
body = response_json["body"]
19-
self._populate_identities(body, identity_map_input)
21+
self._populate_identities(api_response.body, identity_map_input)
2022

21-
def _populate_identities(self, api_response: Dict[str, List[Dict]], identity_map_input):
23+
def _populate_identities(self, api_response: Dict[Literal['email_hash', 'phone_hash'], List['ApiIdentity']], identity_map_input: IdentityMapV3Input) -> None:
2224
for identity_type, identities in api_response.items():
2325
self._populate_identities_for_type(identity_map_input, identity_type, identities)
2426

25-
def _populate_identities_for_type(self, identity_map_input, identity_type: str, identities: List[Dict]):
26-
for i, api_identity_data in enumerate(identities):
27-
api_identity = ApiIdentity.from_json(api_identity_data)
27+
def _populate_identities_for_type(self, identity_map_input: IdentityMapV3Input, identity_type: Literal['email_hash', 'phone_hash'], identities: List['ApiIdentity']) -> None:
28+
for i, api_identity in enumerate(identities):
2829
input_diis = identity_map_input.get_input_diis(identity_type, i)
2930

3031
for input_dii in input_diis:
@@ -49,6 +50,22 @@ def status(self) -> str:
4950
return self._status
5051

5152

53+
class ApiResponse:
54+
def __init__(self, status: str, body: Dict[Literal['email_hash', 'phone_hash'], List['ApiIdentity']]):
55+
self.status = status
56+
self.body = body
57+
58+
@classmethod
59+
def from_json(cls, data) -> 'ApiResponse':
60+
api_body: Dict[Literal['email_hash', 'phone_hash'], List['ApiIdentity']] = {
61+
'email_hash': [ApiIdentity.from_json(item) for item in data.get('body').get('email_hash', [])] if data.get('body').get('email_hash') else [],
62+
'phone_hash': [ApiIdentity.from_json(item) for item in data.get('body').get('phone_hash', [])] if data.get('body').get('phone_hash') else [],
63+
}
64+
return cls(
65+
status=data.get("status"),
66+
body=api_body
67+
)
68+
5269
class ApiIdentity:
5370
def __init__(self, current_uid: Optional[str], previous_uid: Optional[str],
5471
refresh_from_seconds: Optional[int], error: Optional[str]):
@@ -74,7 +91,9 @@ def __init__(self, current_uid: str, previous_uid: Optional[str], refresh_from_s
7491
self._refresh_from = refresh_from_seconds
7592

7693
@classmethod
77-
def from_api_identity(cls, api_identity: ApiIdentity):
94+
def from_api_identity(cls, api_identity: ApiIdentity) -> 'MappedIdentity':
95+
if api_identity.current_uid is None or api_identity.refresh_from_seconds is None:
96+
raise ValueError("Mapped identity cannot be created from API identity with missing current_uid or refresh_from_seconds")
7897
return cls(api_identity.current_uid,
7998
api_identity.previous_uid,
8099
datetime.fromtimestamp(api_identity.refresh_from_seconds, tz=timezone.utc))

uid2_client/request_response_util.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import base64
22
import os
3+
from datetime import datetime
4+
from typing import Dict, Optional
35
from urllib import request
46

57
import pkg_resources
@@ -10,10 +12,10 @@
1012

1113
BINARY = 'application/octet-stream'
1214

13-
def _make_url(base_url, path):
15+
def _make_url(base_url: str, path: str) -> str:
1416
return base_url + path
1517

16-
def auth_headers(auth_key):
18+
def auth_headers(auth_key: str) -> Dict[str, str]:
1719
try:
1820
version = pkg_resources.get_distribution("uid2_client").version
1921
except Exception:
@@ -22,7 +24,7 @@ def auth_headers(auth_key):
2224
return {'Authorization': 'Bearer ' + auth_key,
2325
"X-UID2-Client-Version": "uid2-client-python-" + version}
2426

25-
def create_envelope(secret_key, now, data=None) -> 'Envelope':
27+
def create_envelope(secret_key: bytes, now: datetime, data: Optional[bytes] = None) -> 'Envelope':
2628
payload = int.to_bytes(int(now.timestamp() * 1000), 8, 'big')
2729
nonce = os.urandom(8)
2830
payload += nonce
@@ -33,27 +35,33 @@ def create_envelope(secret_key, now, data=None) -> 'Envelope':
3335
envelope += _encrypt_gcm(payload, None, secret_key)
3436
return Envelope(envelope, nonce)
3537

36-
def make_request(base_url, path, headers, envelope) -> 'Uid2Response':
38+
def make_request(base_url: str, path: str, headers: Dict[str, str], envelope: Envelope) -> 'Uid2Response':
3739
resp = post(base_url, path, headers, envelope.envelope)
3840
return Uid2Response.from_string(resp.read())
3941

40-
def make_binary_request(base_url, path, headers, envelope) -> 'Uid2Response':
42+
def make_binary_request(base_url: str, path: str, headers: Dict[str, str], envelope: Envelope) -> 'Uid2Response':
4143
headers['Content-Type'] = BINARY
4244
resp = post(base_url, path, headers, envelope.binary_envelope)
4345
return Uid2Response.from_bytes(resp.read())
4446

45-
def post(base_url, path, headers, data):
47+
def post(base_url: str, path: str, headers: Dict[str, str], data: bytes):
4648
req = request.Request(_make_url(base_url, path), headers=headers, method='POST', data=data)
4749
return request.urlopen(req)
4850

49-
def parse_response(secret_key, uid2_response: Uid2Response, nonce):
51+
def parse_response(secret_key: bytes, uid2_response: Uid2Response, nonce: bytes) -> str:
5052
if uid2_response.is_binary():
51-
return _decrypt_payload(secret_key, uid2_response.as_bytes, nonce)
53+
as_bytes = uid2_response.as_bytes
54+
if as_bytes is None:
55+
raise ValueError("Binary response has no bytes data")
56+
return _decrypt_payload(secret_key, as_bytes, nonce)
5257
else:
53-
encrypted_string = base64.b64decode(uid2_response.as_string)
58+
as_string = uid2_response.as_string
59+
if as_string is None:
60+
raise ValueError("String response has no string data")
61+
encrypted_string = base64.b64decode(as_string)
5462
return _decrypt_payload(secret_key, encrypted_string, nonce)
5563

56-
def _decrypt_payload(secret_key, encrypted, nonce):
64+
def _decrypt_payload(secret_key: bytes, encrypted: bytes, nonce: bytes) -> str:
5765
payload = _decrypt_gcm(encrypted, secret_key)
5866
if nonce != payload[8:16]:
5967
raise ValueError("nonce mismatch")

uid2_client/uid2_response.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ def __init__(self, as_string: Optional[str], as_bytes: Optional[bytes]):
77
self._as_bytes = as_bytes
88

99
@classmethod
10-
def from_string(cls, as_string: str):
10+
def from_string(cls, as_string: str) -> 'Uid2Response':
1111
return cls(as_string, None)
1212

1313
@classmethod
14-
def from_bytes(cls, as_bytes: bytes):
14+
def from_bytes(cls, as_bytes: bytes) -> 'Uid2Response':
1515
return cls(None, as_bytes)
1616

1717
@property
18-
def as_string(self) -> str:
18+
def as_string(self) -> Optional[str]:
1919
return self._as_string
2020

2121
@property
22-
def as_bytes(self) -> bytes:
22+
def as_bytes(self) -> Optional[bytes]:
2323
return self._as_bytes
2424

2525
def is_binary(self) -> bool:

uid2_client/unmapped_identity_reason.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class UnmappedIdentityReason(Enum):
77
UNKNOWN = "unknown"
88

99
@classmethod
10-
def from_string(cls, reason_str):
10+
def from_string(cls, reason_str: str) -> 'UnmappedIdentityReason':
1111
try:
1212
return cls(reason_str)
1313
except ValueError:

0 commit comments

Comments
 (0)