Skip to content

Commit 7ec64dd

Browse files
authored
Separate freely-associated states & US territories (#1828)
* Separate freely-associated states & US territories There are three sovereign states that are members of the Compact of Free Association. These states are not US territories, and having `state_abbr()` include them by default can cause problems with other tooling. For the purposes of a valid deliverable address, it's useful to include these freely-associated states by default. But it's also helpful to be able to dynamically exclude them. (Furthermore, there's good reason to simply be correct about how these sovereign states are referred to). * Fix Black formatting (line length is 120)
1 parent 3b7b315 commit 7ec64dd

File tree

2 files changed

+49
-16
lines changed

2 files changed

+49
-16
lines changed

faker/providers/address/en_US/__init__.py

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections import OrderedDict
2-
from typing import Optional
2+
from typing import Optional, Set
33

44
from ..en import Provider as AddressProvider
55

@@ -419,7 +419,8 @@ class Provider(AddressProvider):
419419
"WV": (24701, 26886),
420420
"WI": (53001, 54990),
421421
"WY": (82001, 83128),
422-
# Territories - incomplete ranges with accurate subsets - https://www.geonames.org/postalcode-search.html
422+
# Territories & freely-associated states
423+
# incomplete ranges with accurate subsets - https://www.geonames.org/postalcode-search.html
423424
"AS": (96799, 96799),
424425
"FM": (96941, 96944),
425426
"GU": (96910, 96932),
@@ -432,16 +433,21 @@ class Provider(AddressProvider):
432433

433434
territories_abbr = (
434435
"AS",
435-
"FM",
436436
"GU",
437-
"MH",
438437
"MP",
439-
"PW",
440438
"PR",
441439
"VI",
442440
)
443441

444-
states_and_territories_abbr = states_abbr + territories_abbr
442+
# Freely-associated states (sovereign states; members of COFA)
443+
# https://en.wikipedia.org/wiki/Compact_of_Free_Association
444+
freely_associated_states_abbr = (
445+
"FM",
446+
"MH",
447+
"PW",
448+
)
449+
450+
known_usps_abbr = states_abbr + territories_abbr + freely_associated_states_abbr
445451

446452
military_state_abbr = ("AE", "AA", "AP")
447453

@@ -494,16 +500,28 @@ def administrative_unit(self) -> str:
494500

495501
state = administrative_unit
496502

497-
def state_abbr(self, include_territories: bool = True) -> str:
503+
def state_abbr(
504+
self,
505+
include_territories: bool = True,
506+
include_freely_associated_states: bool = True,
507+
) -> str:
498508
"""
499-
:returns: A random state or territory abbreviation.
509+
:returns: A random two-letter USPS postal code
510+
511+
By default, the resulting code may abbreviate any of the fity states,
512+
five US territories, or three freely-associating sovereign states.
500513
501514
:param include_territories: If True, territories will be included.
502-
If False, only states will be returned.
515+
If False, US territories will be excluded.
516+
:param include_freely_associated_states: If True, freely-associated states will be included.
517+
If False, sovereign states in free association with the US will be excluded.
503518
"""
519+
abbreviations: Set[str] = set(self.states_abbr)
504520
if include_territories:
505-
return self.random_element(self.states_and_territories_abbr)
506-
return self.random_element(self.states_abbr)
521+
abbreviations.update(self.territories_abbr)
522+
if include_freely_associated_states:
523+
abbreviations.update(self.freely_associated_states_abbr)
524+
return self.random_element(abbreviations)
507525

508526
def postcode(self) -> str:
509527
return "%05d" % self.generator.random.randint(501, 99950)
@@ -520,7 +538,7 @@ def postcode_in_state(self, state_abbr: Optional[str] = None) -> str:
520538
if state_abbr is None:
521539
state_abbr = self.random_element(self.states_abbr)
522540

523-
if state_abbr in self.states_and_territories_abbr:
541+
if state_abbr in self.known_usps_abbr:
524542
postcode = "%d" % (
525543
self.generator.random.randint(
526544
self.states_postcode[state_abbr][0],

tests/providers/test_address.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -519,14 +519,29 @@ def test_state_abbr(self, faker, num_samples):
519519
for _ in range(num_samples):
520520
state_abbr = faker.state_abbr()
521521
assert isinstance(state_abbr, str)
522-
states_and_territories = EnUsAddressProvider.states_and_territories_abbr
522+
states_and_territories = EnUsAddressProvider.known_usps_abbr
523523
assert state_abbr in states_and_territories
524524

525+
def test_state_abbr_states_only(self, faker, num_samples):
526+
for _ in range(num_samples):
527+
state_abbr = faker.state_abbr(include_territories=False, include_freely_associated_states=False)
528+
assert isinstance(state_abbr, str)
529+
assert state_abbr in EnUsAddressProvider.states_abbr
530+
525531
def test_state_abbr_no_territories(self, faker, num_samples):
526532
for _ in range(num_samples):
527533
state_abbr = faker.state_abbr(include_territories=False)
528534
assert isinstance(state_abbr, str)
529-
assert state_abbr in EnUsAddressProvider.states_abbr
535+
assert (
536+
state_abbr in EnUsAddressProvider.states_abbr
537+
or state_abbr in EnUsAddressProvider.freely_associated_states_abbr
538+
)
539+
540+
def test_state_abbr_no_freely_associated_states(self, faker, num_samples):
541+
for _ in range(num_samples):
542+
state_abbr = faker.state_abbr(include_freely_associated_states=False)
543+
assert isinstance(state_abbr, str)
544+
assert state_abbr in EnUsAddressProvider.states_abbr or state_abbr in EnUsAddressProvider.territories_abbr
530545

531546
def test_postcode(self, faker, num_samples):
532547
for _ in range(num_samples):
@@ -536,7 +551,7 @@ def test_postcode(self, faker, num_samples):
536551

537552
def test_postcode_in_state(self, faker, num_samples):
538553
for _ in range(num_samples):
539-
for state_abbr in EnUsAddressProvider.states_and_territories_abbr:
554+
for state_abbr in EnUsAddressProvider.known_usps_abbr:
540555
code = faker.postcode_in_state(state_abbr)
541556
assert re.fullmatch(r"\d{5}", code)
542557
assert int(code) >= EnUsAddressProvider.states_postcode[state_abbr][0]
@@ -553,7 +568,7 @@ def test_zipcode(self, faker, num_samples):
553568

554569
def test_zipcode_in_state(self, faker, num_samples):
555570
for _ in range(num_samples):
556-
for state_abbr in EnUsAddressProvider.states_and_territories_abbr:
571+
for state_abbr in EnUsAddressProvider.known_usps_abbr:
557572
code = faker.zipcode_in_state(state_abbr)
558573
assert re.fullmatch(r"\d{5}", code)
559574
assert int(code) >= EnUsAddressProvider.states_postcode[state_abbr][0]

0 commit comments

Comments
 (0)