Skip to content

Commit 3de9539

Browse files
Merge pull request #14 from googleads/new_address_fields
feat(Formatter): Add formatting methods for new GA4 address info fields
2 parents 6080b88 + 7c80942 commit 3de9539

3 files changed

Lines changed: 211 additions & 3 deletions

File tree

google/ads/datamanager_util/format.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,55 @@ def format_region_code(self, region_code: str) -> str:
184184
raise ValueError("Region code must be two characters")
185185
return region_code
186186

187+
def _format_location_string(self, value: str, label: str) -> str:
188+
"""Returns the normalized and formatted location string.
189+
190+
Args:
191+
value: the string to format.
192+
label: the label used for error messages.
193+
Raises:
194+
ValueError: If the provided value is invalid.
195+
"""
196+
if value is None:
197+
raise ValueError(f"{label} is None")
198+
value = value.strip().lower()
199+
# Removes all punctuation and special characters, leaving only
200+
# alphanumeric characters and whitespace.
201+
value = re.sub(r"[^\w\s]|_", "", value)
202+
if not value:
203+
raise ValueError(f"{label} is blank or empty")
204+
return value
205+
206+
def format_address_line(self, address_line: str) -> str:
207+
"""Returns the normalized and formatted address line as a string.
208+
209+
Args:
210+
address_line: the address line.
211+
Raises:
212+
ValueError: If the provided address line is invalid.
213+
"""
214+
return self._format_location_string(address_line, "Address line")
215+
216+
def format_city(self, city: str) -> str:
217+
"""Returns the normalized and formatted city as a string.
218+
219+
Args:
220+
city: the city.
221+
Raises:
222+
ValueError: If the provided city is invalid.
223+
"""
224+
return self._format_location_string(city, "City")
225+
226+
def format_administrative_area(self, administrative_area: str) -> str:
227+
"""Returns the normalized and formatted administrative area as a string.
228+
229+
Args:
230+
administrative_area: the administrative area.
231+
Raises:
232+
ValueError: If the provided administrative area is invalid.
233+
"""
234+
return self._format_location_string(administrative_area, "Administrative area")
235+
187236
def hash_string(self, s: str) -> bytes:
188237
"""Returns bytes containing the hash of the string.
189238
@@ -195,8 +244,7 @@ def hash_string(self, s: str) -> bytes:
195244
"""
196245
if s is None:
197246
raise ValueError("String is None")
198-
s = "".join(s.split())
199-
if len(s) == 0:
247+
if len(s.strip()) == 0:
200248
raise ValueError("String is blank or empty")
201249
return hashlib.sha256(s.encode()).digest()
202250

@@ -350,6 +398,55 @@ def process_postal_code(self, postal_code: str) -> str:
350398
"""
351399
return self.format_postal_code(postal_code)
352400

401+
def process_address_line(
402+
self,
403+
address_line: str,
404+
encoding: Encoding,
405+
encrypter: Optional[Encrypter] = None,
406+
) -> str:
407+
"""Formats, hashes, and encodes an address line.
408+
409+
Args:
410+
address_line: The address line to process.
411+
encoding: The encoding to use.
412+
encrypter: An optional Encrypter to use for encryption.
413+
414+
Returns:
415+
The processed address line.
416+
"""
417+
formatted_address_line = self.format_address_line(address_line)
418+
if encrypter:
419+
return self._hash_encode_and_encrypt(
420+
formatted_address_line, encoding, encrypter
421+
)
422+
return self._hash_and_encode(formatted_address_line, encoding)
423+
424+
def process_city(self, city: str) -> str:
425+
"""Processes a city.
426+
427+
This is a convenience method that simply calls format_city.
428+
429+
Args:
430+
city: The city to process.
431+
432+
Returns:
433+
The processed city.
434+
"""
435+
return self.format_city(city)
436+
437+
def process_administrative_area(self, administrative_area: str) -> str:
438+
"""Processes an administrative area.
439+
440+
This is a convenience method that simply calls format_administrative_area.
441+
442+
Args:
443+
administrative_area: The administrative area to process.
444+
445+
Returns:
446+
The processed administrative area.
447+
"""
448+
return self.format_administrative_area(administrative_area)
449+
353450
def _hash_and_encode(
354451
self, normalized_string: str, encoding: Encoding
355452
) -> str:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ build-backend = "setuptools.build_meta"
1818

1919
[project]
2020
name = "google-ads-datamanager-util"
21-
version = "0.3.0"
21+
version = "0.4.0"
2222
description = "Utilities for the Data Manager API"
2323
readme = "./README.rst"
2424
requires-python = ">=3.9, <3.14"

tests/formatter_test.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,114 @@ def test_process_postal_code_valid_inputs(self):
326326
self.assertEqual(
327327
"1229-076", self.formatter.process_postal_code(" 1229-076 ")
328328
)
329+
330+
def test_format_address_line_valid_inputs(self):
331+
self.assertEqual(
332+
"1800 amphibious blvd",
333+
self.formatter.format_address_line(" 1800 Amphibious Blvd. "),
334+
)
335+
336+
def test_format_address_line_invalid_inputs(self):
337+
with self.assertRaises(ValueError):
338+
self.formatter.format_address_line(None)
339+
with self.assertRaises(ValueError):
340+
self.formatter.format_address_line("")
341+
with self.assertRaises(ValueError):
342+
self.formatter.format_address_line(" ")
343+
344+
def test_format_city_valid_inputs(self):
345+
self.assertEqual(
346+
"mountain view",
347+
self.formatter.format_city(" Mountain View "),
348+
)
349+
self.assertEqual(
350+
"mountain view",
351+
self.formatter.format_city("Mountain View,"),
352+
)
353+
354+
def test_format_city_invalid_inputs(self):
355+
with self.assertRaises(ValueError):
356+
self.formatter.format_city(None)
357+
with self.assertRaises(ValueError):
358+
self.formatter.format_city("")
359+
with self.assertRaises(ValueError):
360+
self.formatter.format_city(" ")
361+
362+
def test_format_administrative_area_valid_inputs(self):
363+
self.assertEqual(
364+
"ca",
365+
self.formatter.format_administrative_area(" CA "),
366+
)
367+
self.assertEqual(
368+
"california",
369+
self.formatter.format_administrative_area(" California "),
370+
)
371+
self.assertEqual(
372+
"ca",
373+
self.formatter.format_administrative_area("C.A."),
374+
)
375+
self.assertEqual(
376+
"ca",
377+
self.formatter.format_administrative_area("CA."),
378+
)
379+
380+
def test_format_administrative_area_invalid_inputs(self):
381+
with self.assertRaises(ValueError):
382+
self.formatter.format_administrative_area(None)
383+
with self.assertRaises(ValueError):
384+
self.formatter.format_administrative_area("")
385+
with self.assertRaises(ValueError):
386+
self.formatter.format_administrative_area(" ")
387+
388+
def test_process_address_line_valid_inputs_hex_encoding(self):
389+
encoded_hash = (
390+
"ff75e73a0e768cc1fa28a64faebbceccb562d7c05f2ffcdd8d100abad73e4579"
391+
)
392+
self.assertEqual(
393+
encoded_hash,
394+
self.formatter.process_address_line(
395+
" 1800 Amphibious Blvd. ", Encoding.HEX
396+
),
397+
)
398+
399+
def test_process_address_line_valid_inputs_base64_encoding(self):
400+
encoded_hash = "/3XnOg52jMH6KKZPrrvOzLVi18BfL/zdjRAKutc+RXk="
401+
self.assertEqual(
402+
encoded_hash,
403+
self.formatter.process_address_line(
404+
" 1800 Amphibious Blvd. ", Encoding.BASE64
405+
),
406+
)
407+
408+
def test_process_city_valid_inputs(self):
409+
self.assertEqual(
410+
"mountain view",
411+
self.formatter.process_city(" Mountain View "),
412+
)
413+
414+
def test_process_administrative_area_valid_inputs(self):
415+
self.assertEqual(
416+
"ca",
417+
self.formatter.process_administrative_area(" CA "),
418+
)
419+
420+
def test_format_example_address(self):
421+
# 1800 Amphibious Blvd.
422+
# Mountain View, CA 94045
423+
self.assertEqual(
424+
"1800 amphibious blvd",
425+
self.formatter.format_address_line("1800 Amphibious Blvd."),
426+
)
427+
self.assertEqual(
428+
"mountain view",
429+
self.formatter.format_city("Mountain View"),
430+
)
431+
self.assertEqual(
432+
"ca",
433+
self.formatter.format_administrative_area("CA."),
434+
)
435+
self.assertEqual(
436+
"94045",
437+
self.formatter.format_postal_code("94045"),
438+
)
439+

0 commit comments

Comments
 (0)