Skip to content

Commit 970de10

Browse files
committed
fix: brings back country code validation
1 parent 12ae1f5 commit 970de10

File tree

3 files changed

+243
-27
lines changed

3 files changed

+243
-27
lines changed

src/validators/__init__.py

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,81 @@
11
"""Validate Anything!"""
22
# -*- coding: utf-8 -*-
33

4-
# isort: skip_file
5-
6-
# The following imports are sorted alphabetically, manually.
7-
# Each line is grouped based first or type, then sorted alphabetically.
8-
# This is for the reference documentation.
9-
104
# local
115
from .between import between
126
from .btc_address import btc_address
137
from .card import amex, card_number, diners, discover, jcb, mastercard, unionpay, visa
14-
15-
# from .country_code import country_code
8+
from .country_code import country_code
169
from .domain import domain
1710
from .email import email
1811
from .hashes import md5, sha1, sha224, sha256, sha512
1912
from .hostname import hostname
13+
from .i18n import es_cif, es_doi, es_nie, es_nif, fi_business_id, fi_ssn
2014
from .iban import iban
2115
from .ip_address import ipv4, ipv6
2216
from .length import length
2317
from .mac_address import mac_address
2418
from .slug import slug
2519
from .url import url
26-
from .utils import validator, ValidationError
20+
from .utils import ValidationError, validator
2721
from .uuid import uuid
2822

2923
# from .crypto_addresses import eth_address
30-
from .i18n import es_cif, es_doi, es_nie, es_nif, fi_business_id, fi_ssn
3124

3225
__all__ = (
33-
"amex",
26+
# ...
3427
"between",
28+
# crypto addresses
3529
"btc_address",
3630
# "eth_address",
31+
# cards
32+
"amex",
3733
"card_number",
38-
# "country_code",
3934
"diners",
4035
"discover",
41-
"domain",
42-
"email",
43-
"hostname",
44-
"iban",
45-
"ipv4",
46-
"ipv6",
4736
"jcb",
48-
"length",
49-
"mac_address",
5037
"mastercard",
38+
"visa",
39+
"unionpay",
40+
# ...
41+
"country_code",
42+
# ...
43+
"domain",
44+
# ...
45+
"email",
46+
# hashes
5147
"md5",
5248
"sha1",
5349
"sha224",
5450
"sha256",
5551
"sha512",
56-
"slug",
57-
"unionpay",
58-
"url",
59-
"uuid",
60-
"ValidationError",
61-
"validator",
62-
"visa",
52+
# ...
53+
"hostname",
6354
# i18n
6455
"es_cif",
6556
"es_doi",
6657
"es_nie",
6758
"es_nif",
6859
"fi_business_id",
6960
"fi_ssn",
61+
# ...
62+
"iban",
63+
# ip addresses
64+
"ipv4",
65+
"ipv6",
66+
# ...
67+
"length",
68+
# ...
69+
"mac_address",
70+
# ...
71+
"slug",
72+
# ...
73+
"url",
74+
# ...
75+
"uuid",
76+
# utils
77+
"ValidationError",
78+
"validator",
7079
)
7180

7281
__version__ = "0.21.1"

src/validators/country_code.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""Country Codes."""
2+
# -*- coding: utf-8 -*-
3+
4+
# local
5+
from validators.utils import validator
6+
7+
# fmt: off
8+
alpha_2 = {
9+
"AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ",
10+
"BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS",
11+
"BT", "BV", "BW", "BY", "BZ",
12+
"CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW",
13+
"CX", "CY", "CZ",
14+
"DE", "DJ", "DK", "DM", "DO", "DZ",
15+
"EC", "EE", "EG", "EH", "ER", "ES", "ET",
16+
"FI", "FJ", "FK", "FM", "FO", "FR",
17+
"GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT",
18+
"GU", "GW", "GY",
19+
"HK", "HM", "HN", "HR", "HT", "HU",
20+
"ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT",
21+
"JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI",
22+
"KM", "KN", "KP", "KR", "KW", "KY", "KZ",
23+
"LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY",
24+
"MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS",
25+
"MT", "MU", "MV", "MW", "MX", "MY", "MZ",
26+
"NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ",
27+
"OM",
28+
"PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY",
29+
"QA",
30+
"RE", "RO", "RS", "RU", "RW",
31+
"SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS",
32+
"ST", "SV", "SX", "SY", "SZ",
33+
"TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ",
34+
"UA", "UG", "UM", "US", "UY", "UZ",
35+
"VC", "VE", "VG", "VI", "VN", "VU",
36+
"WF", "WS",
37+
"YE", "YT",
38+
"ZA", "ZM", "ZW",
39+
}
40+
alpha_3 = {
41+
"ABW", "AFG", "AGO", "AIA", "ALA", "ALB", "AND", "ARE", "ARG", "ARM", "ASM", "ATA", "ATF",
42+
"ATG", "AUS", "AUT", "AZE",
43+
"BDI", "BEL", "BEN", "BES", "BFA", "BGD", "BGR", "BHR", "BHS", "BIH", "BLM", "BLR", "BLZ",
44+
"BMU", "BOL", "BRA", "BRB", "BRN", "BTN", "BVT", "BWA",
45+
"CAF", "CAN", "CCK", "CHE", "CHL", "CHN", "CIV", "CMR", "COD", "COG", "COK", "COL", "COM",
46+
"CPV", "CRI", "CUB", "CUW", "CXR", "CYM", "CYP", "CZE",
47+
"DEU", "DJI", "DMA", "DNK", "DOM", "DZA",
48+
"ECU", "EGY", "ERI", "ESH", "ESP", "EST", "ETH",
49+
"FIN", "FJI", "FLK", "FRA", "FRO", "FSM",
50+
"GAB", "GBR", "GEO", "GGY", "GHA", "GIB", "GIN", "GLP", "GMB", "GNB", "GNQ", "GRC", "GRD",
51+
"GRL", "GTM", "GUF", "GUM", "GUY",
52+
"HKG", "HMD", "HND", "HRV", "HTI", "HUN",
53+
"IDN", "IMN", "IND", "IOT", "IRL", "IRN", "IRQ", "ISL", "ISR", "ITA",
54+
"JAM", "JEY", "JOR", "JPN",
55+
"KAZ", "KEN", "KGZ", "KHM", "KIR", "KNA", "KOR", "KWT",
56+
"LAO", "LBN", "LBR", "LBY", "LCA", "LIE", "LKA", "LSO", "LTU", "LUX", "LVA",
57+
"MAC", "MAF", "MAR", "MCO", "MDA", "MDG", "MDV", "MEX", "MHL", "MKD", "MLI", "MLT", "MMR",
58+
"MNE", "MNG", "MNP", "MOZ", "MRT", "MSR", "MTQ", "MUS", "MWI", "MYS", "MYT",
59+
"NAM", "NCL", "NER", "NFK", "NGA", "NIC", "NIU", "NLD", "NOR", "NPL", "NRU", "NZL",
60+
"OMN",
61+
"PAK", "PAN", "PCN", "PER", "PHL", "PLW", "PNG", "POL", "PRI", "PRK", "PRT", "PRY", "PSE",
62+
"PYF",
63+
"QAT",
64+
"REU", "ROU", "RUS", "RWA",
65+
"SAU", "SDN", "SEN", "SGP", "SGS", "SHN", "SJM", "SLB", "SLE", "SLV", "SMR", "SOM", "SPM",
66+
"SRB", "SSD", "STP", "SUR", "SVK", "SVN", "SWE", "SWZ", "SXM", "SYC", "SYR",
67+
"TCA", "TCD", "TGO", "THA", "TJK", "TKL", "TKM", "TLS", "TON", "TTO", "TUN", "TUR", "TUV",
68+
"TWN", "TZA",
69+
"UGA", "UKR", "UMI", "URY", "USA", "UZB",
70+
"VCT", "VEN", "VGB", "VIR", "VNM", "VUT",
71+
"WLF", "WSM",
72+
"YEM",
73+
"ZAF", "ZMB", "ZWE",
74+
}
75+
numeric = {
76+
"004", "008", "010", "012", "016", "020", "024", "028", "031", "032",
77+
"036", "040", "044", "048", "050", "051", "052", "056", "060", "064",
78+
"068", "070", "072", "074", "076", "084", "086", "090", "092", "096",
79+
"100", "104", "108", "112", "116", "120", "124", "132", "136", "140",
80+
"144", "148", "152", "156", "158", "162", "166", "170", "174", "175",
81+
"178", "180", "184", "188", "191", "192", "196", "203", "204", "208",
82+
"212", "214", "218", "222", "226", "231", "232", "233", "234", "238",
83+
"239", "242", "246", "248", "250", "254", "258", "260", "262", "266",
84+
"268", "270", "275", "276", "288", "292", "296", "300", "304", "308",
85+
"312", "316", "320", "324", "328", "332", "334", "340", "344", "348",
86+
"352", "356", "360", "364", "368", "372", "376", "380", "384", "388",
87+
"392", "398", "400", "404", "408", "410", "414", "417", "418", "422",
88+
"426", "428", "430", "434", "438", "440", "442", "446", "450", "454",
89+
"458", "462", "466", "470", "474", "478", "480", "484", "492", "496",
90+
"498", "499", "500", "504", "508", "512", "516", "520", "524", "528",
91+
"531", "533", "534", "535", "540", "548", "554", "558", "562", "566",
92+
"570", "574", "578", "580", "581", "583", "584", "585", "586", "591",
93+
"598", "600", "604", "608", "612", "616", "620", "624", "626", "630",
94+
"634", "638", "642", "643", "646", "652", "654", "659", "660", "662",
95+
"663", "666", "670", "674", "678", "682", "686", "688", "690", "694",
96+
"702", "703", "704", "705", "706", "710", "716", "724", "728", "729",
97+
"732", "740", "744", "748", "752", "756", "760", "762", "764", "768",
98+
"772", "776", "780", "784", "788", "792", "795", "796", "798", "800",
99+
"804", "807", "818", "826", "831", "832", "833", "834", "840", "850",
100+
"854", "858", "860", "862", "876", "882", "887", "894",
101+
}
102+
# fmt: on
103+
104+
105+
def get_code_type(format_type: str):
106+
"""Returns the type of country code."""
107+
if format_type.isdigit():
108+
return "numeric"
109+
if format_type.isalpha():
110+
if len(format_type) == 2:
111+
return "alpha2"
112+
if len(format_type) == 3:
113+
return "alpha3"
114+
return "invalid"
115+
116+
117+
@validator
118+
def country_code(value: str, /, *, iso_format: str = "auto"):
119+
"""Validates given country code.
120+
121+
This performs a case-sensitive [ISO 3166][1] country code validation.
122+
123+
[1]: https://www.iso.org/iso-3166-country-codes.html
124+
125+
Examples:
126+
>>> country_code('GB', iso_format='alpha3')
127+
# Output: False
128+
>>> country_code('USA')
129+
# Output: True
130+
>>> country_code('840', iso_format='numeric')
131+
# Output: True
132+
>>> country_code('iN', iso_format='alpha2')
133+
# Output: False
134+
>>> country_code('ZWE', iso_format='alpha3')
135+
# Output: True
136+
137+
Args:
138+
value:
139+
Country code string to validate.
140+
iso_format:
141+
ISO format to be used. Available options are:
142+
`auto`, `alpha2`, `alpha3` and `numeric`.
143+
144+
Returns:
145+
(Literal[True]):
146+
If `value` is a valid country code.
147+
(ValidationError):
148+
If `value` is an invalid country code.
149+
"""
150+
if not value:
151+
return False
152+
153+
if not (1 < len(value) < 4):
154+
return False
155+
156+
if iso_format == "auto" and (iso_format := get_code_type(value)) == "invalid":
157+
return False
158+
159+
if iso_format == "alpha2":
160+
return value in alpha_2
161+
if iso_format == "alpha3":
162+
return value in alpha_3
163+
return value in numeric if iso_format == "numeric" else False

tests/test_country_code.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Test Country Codes."""
2+
# -*- coding: utf-8 -*-
3+
4+
# external
5+
import pytest
6+
7+
# local
8+
from validators import ValidationError, country_code
9+
10+
11+
@pytest.mark.parametrize(
12+
("value", "iso_format"),
13+
[
14+
("ISR", "auto"),
15+
("US", "alpha2"),
16+
("USA", "alpha3"),
17+
("840", "numeric"),
18+
],
19+
)
20+
def test_returns_true_on_valid_country_code(value: str, iso_format: str):
21+
"""Test returns true on valid country code."""
22+
assert country_code(value, iso_format=iso_format)
23+
24+
25+
@pytest.mark.parametrize(
26+
("value", "iso_format"),
27+
[
28+
(None, "auto"),
29+
("", "auto"),
30+
("123456", "auto"),
31+
("XY", "alpha2"),
32+
("PPP", "alpha3"),
33+
("123", "numeric"),
34+
("us", "auto"),
35+
("uSa", "auto"),
36+
("US ", "auto"),
37+
("U.S", "auto"),
38+
("1ND", "unknown"),
39+
("ISR", None),
40+
],
41+
)
42+
def test_returns_failed_validation_on_invalid_country_code(value: str, iso_format: str):
43+
"""Test returns failed validation on invalid country code."""
44+
assert isinstance(country_code(value, iso_format=iso_format), ValidationError)

0 commit comments

Comments
 (0)