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
101 changes: 99 additions & 2 deletions google/ads/datamanager_util/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,55 @@ def format_region_code(self, region_code: str) -> str:
raise ValueError("Region code must be two characters")
return region_code

def _format_location_string(self, value: str, label: str) -> str:
"""Returns the normalized and formatted location string.

Args:
value: the string to format.
label: the label used for error messages.
Raises:
ValueError: If the provided value is invalid.
"""
if value is None:
raise ValueError(f"{label} is None")
value = value.strip().lower()
# Removes all punctuation and special characters, leaving only
# alphanumeric characters and whitespace.
value = re.sub(r"[^\w\s]|_", "", value)
if not value:
raise ValueError(f"{label} is blank or empty")
return value

def format_address_line(self, address_line: str) -> str:
"""Returns the normalized and formatted address line as a string.

Args:
address_line: the address line.
Raises:
ValueError: If the provided address line is invalid.
"""
return self._format_location_string(address_line, "Address line")

def format_city(self, city: str) -> str:
"""Returns the normalized and formatted city as a string.

Args:
city: the city.
Raises:
ValueError: If the provided city is invalid.
"""
return self._format_location_string(city, "City")

def format_administrative_area(self, administrative_area: str) -> str:
"""Returns the normalized and formatted administrative area as a string.

Args:
administrative_area: the administrative area.
Raises:
ValueError: If the provided administrative area is invalid.
"""
return self._format_location_string(administrative_area, "Administrative area")

def hash_string(self, s: str) -> bytes:
"""Returns bytes containing the hash of the string.

Expand All @@ -195,8 +244,7 @@ def hash_string(self, s: str) -> bytes:
"""
if s is None:
raise ValueError("String is None")
s = "".join(s.split())
if len(s) == 0:
if len(s.strip()) == 0:
Comment thread
jradcliff marked this conversation as resolved.
raise ValueError("String is blank or empty")
return hashlib.sha256(s.encode()).digest()

Expand Down Expand Up @@ -350,6 +398,55 @@ def process_postal_code(self, postal_code: str) -> str:
"""
return self.format_postal_code(postal_code)

def process_address_line(
self,
address_line: str,
encoding: Encoding,
encrypter: Optional[Encrypter] = None,
) -> str:
"""Formats, hashes, and encodes an address line.

Args:
address_line: The address line to process.
encoding: The encoding to use.
encrypter: An optional Encrypter to use for encryption.

Returns:
The processed address line.
"""
formatted_address_line = self.format_address_line(address_line)
if encrypter:
return self._hash_encode_and_encrypt(
formatted_address_line, encoding, encrypter
)
return self._hash_and_encode(formatted_address_line, encoding)

def process_city(self, city: str) -> str:
"""Processes a city.

This is a convenience method that simply calls format_city.

Args:
city: The city to process.

Returns:
The processed city.
"""
return self.format_city(city)

def process_administrative_area(self, administrative_area: str) -> str:
"""Processes an administrative area.

This is a convenience method that simply calls format_administrative_area.

Args:
administrative_area: The administrative area to process.

Returns:
The processed administrative area.
"""
return self.format_administrative_area(administrative_area)

def _hash_and_encode(
self, normalized_string: str, encoding: Encoding
) -> str:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "google-ads-datamanager-util"
version = "0.3.0"
version = "0.4.0"
description = "Utilities for the Data Manager API"
readme = "./README.rst"
requires-python = ">=3.9, <3.14"
Expand Down
111 changes: 111 additions & 0 deletions tests/formatter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,3 +326,114 @@ def test_process_postal_code_valid_inputs(self):
self.assertEqual(
"1229-076", self.formatter.process_postal_code(" 1229-076 ")
)

def test_format_address_line_valid_inputs(self):
self.assertEqual(
"1800 amphibious blvd",
self.formatter.format_address_line(" 1800 Amphibious Blvd. "),
)

def test_format_address_line_invalid_inputs(self):
with self.assertRaises(ValueError):
self.formatter.format_address_line(None)
with self.assertRaises(ValueError):
self.formatter.format_address_line("")
with self.assertRaises(ValueError):
self.formatter.format_address_line(" ")

def test_format_city_valid_inputs(self):
self.assertEqual(
"mountain view",
self.formatter.format_city(" Mountain View "),
)
self.assertEqual(
"mountain view",
self.formatter.format_city("Mountain View,"),
)

def test_format_city_invalid_inputs(self):
with self.assertRaises(ValueError):
self.formatter.format_city(None)
with self.assertRaises(ValueError):
self.formatter.format_city("")
with self.assertRaises(ValueError):
self.formatter.format_city(" ")

def test_format_administrative_area_valid_inputs(self):
self.assertEqual(
"ca",
self.formatter.format_administrative_area(" CA "),
)
self.assertEqual(
"california",
self.formatter.format_administrative_area(" California "),
)
self.assertEqual(
"ca",
self.formatter.format_administrative_area("C.A."),
)
self.assertEqual(
"ca",
self.formatter.format_administrative_area("CA."),
)

def test_format_administrative_area_invalid_inputs(self):
with self.assertRaises(ValueError):
self.formatter.format_administrative_area(None)
with self.assertRaises(ValueError):
self.formatter.format_administrative_area("")
with self.assertRaises(ValueError):
self.formatter.format_administrative_area(" ")

def test_process_address_line_valid_inputs_hex_encoding(self):
encoded_hash = (
"ff75e73a0e768cc1fa28a64faebbceccb562d7c05f2ffcdd8d100abad73e4579"
)
self.assertEqual(
encoded_hash,
self.formatter.process_address_line(
" 1800 Amphibious Blvd. ", Encoding.HEX
),
)

def test_process_address_line_valid_inputs_base64_encoding(self):
encoded_hash = "/3XnOg52jMH6KKZPrrvOzLVi18BfL/zdjRAKutc+RXk="
self.assertEqual(
encoded_hash,
self.formatter.process_address_line(
" 1800 Amphibious Blvd. ", Encoding.BASE64
),
)

def test_process_city_valid_inputs(self):
self.assertEqual(
"mountain view",
self.formatter.process_city(" Mountain View "),
)

def test_process_administrative_area_valid_inputs(self):
self.assertEqual(
"ca",
self.formatter.process_administrative_area(" CA "),
)

def test_format_example_address(self):
# 1800 Amphibious Blvd.
# Mountain View, CA 94045
self.assertEqual(
"1800 amphibious blvd",
self.formatter.format_address_line("1800 Amphibious Blvd."),
)
self.assertEqual(
"mountain view",
self.formatter.format_city("Mountain View"),
)
self.assertEqual(
"ca",
self.formatter.format_administrative_area("CA."),
)
self.assertEqual(
"94045",
self.formatter.format_postal_code("94045"),
)