From ad23b2721c4f01ffc5c3624c1872ebef350d9e88 Mon Sep 17 00:00:00 2001 From: Jovial Joe Jayarson Date: Wed, 3 Apr 2024 13:35:27 +0530 Subject: [PATCH] patch: moves `country_code` module to `country` module - feat: adds `currency` and `calling_code` validators --- docs/api/country.md | 5 + docs/api/country.rst | 7 + docs/api/country_code.md | 3 - docs/api/country_code.rst | 5 - mkdocs.yaml | 2 +- src/validators/__init__.py | 6 +- src/validators/country.py | 355 +++++++++++++++++++++++++++++++++ src/validators/country_code.py | 160 --------------- tests/test_country.py | 76 +++++++ tests/test_country_code.py | 43 ---- 10 files changed, 448 insertions(+), 214 deletions(-) create mode 100644 docs/api/country.md create mode 100644 docs/api/country.rst delete mode 100644 docs/api/country_code.md delete mode 100644 docs/api/country_code.rst create mode 100644 src/validators/country.py delete mode 100644 src/validators/country_code.py create mode 100644 tests/test_country.py delete mode 100644 tests/test_country_code.py diff --git a/docs/api/country.md b/docs/api/country.md new file mode 100644 index 00000000..0027e271 --- /dev/null +++ b/docs/api/country.md @@ -0,0 +1,5 @@ +# country + +::: validators.country.calling_code +::: validators.country.country_code +::: validators.country.currency diff --git a/docs/api/country.rst b/docs/api/country.rst new file mode 100644 index 00000000..26a8221d --- /dev/null +++ b/docs/api/country.rst @@ -0,0 +1,7 @@ +country +------- + +.. module:: validators.country +.. autofunction:: calling_code +.. autofunction:: country_code +.. autofunction:: currency diff --git a/docs/api/country_code.md b/docs/api/country_code.md deleted file mode 100644 index 63fcade3..00000000 --- a/docs/api/country_code.md +++ /dev/null @@ -1,3 +0,0 @@ -# country_code - -::: validators.country_code.country_code diff --git a/docs/api/country_code.rst b/docs/api/country_code.rst deleted file mode 100644 index fb19e319..00000000 --- a/docs/api/country_code.rst +++ /dev/null @@ -1,5 +0,0 @@ -country_code ------------- - -.. module:: validators.country_code -.. autofunction:: country_code diff --git a/mkdocs.yaml b/mkdocs.yaml index 511b66f0..1770c3cb 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -72,7 +72,7 @@ nav: - api/between.md - api/btc_address.md - api/card.md - - api/country_code.md + - api/country.md - api/cron.md - api/domain.md - api/email.md diff --git a/src/validators/__init__.py b/src/validators/__init__.py index a64fb0e0..87e0c015 100644 --- a/src/validators/__init__.py +++ b/src/validators/__init__.py @@ -4,7 +4,7 @@ from .between import between from .btc_address import btc_address from .card import amex, card_number, diners, discover, jcb, mastercard, unionpay, visa -from .country_code import country_code +from .country import calling_code, country_code, currency from .cron import cron from .domain import domain from .email import email @@ -37,8 +37,10 @@ "mastercard", "visa", "unionpay", - # ... + # country + "calling_code", "country_code", + "currency", # ... "cron", # ... diff --git a/src/validators/country.py b/src/validators/country.py new file mode 100644 index 00000000..d04b0b06 --- /dev/null +++ b/src/validators/country.py @@ -0,0 +1,355 @@ +"""Country.""" + +# local +from validators.utils import validator + +# fmt: off +_alpha3_to_alpha2 = { + # A + "ABW": "AW", "AFG": "AF", "AGO": "AO", "AIA": "AI", "ALB": "AL", "AND": "AD", "ANT": "AN", + "ARE": "AE", "ARG": "AR", "ARM": "AM", "ASM": "AS", "ATA": "AQ", "ATF": "TF", "ATG": "AG", + "AUS": "AU", "AUT": "AT", "AZE": "AZ", + # B + "BDI": "BI", "BEL": "BE", "BEN": "BJ", "BFA": "BF", "BGD": "BD", "BGR": "BG", "BHR": "BH", + "BHS": "BS", "BIH": "BA", "BLR": "BY", "BLZ": "BZ", "BMU": "BM", "BOL": "BO", "BRA": "BR", + "BRB": "BB", "BRN": "BN", "BTN": "BT", "BVT": "BV", "BWA": "BW", + # C + "CAF": "CF", "CAN": "CA", "CCK": "CC", "CHE": "CH", "CHL": "CL", "CHN": "CN", "CMR": "CM", + "COD": "CD", "COG": "CG", "COK": "CK", "COL": "CO", "COM": "KM", "CPV": "CV", "CRI": "CR", + "CUB": "CU", "CXR": "CX", "CYM": "KY", "CYP": "CY", "CZE": "CZ", + # D + "DEU": "DE", "DJI": "DJ", "DMA": "DM", "DNK": "DK", "DOM": "DO", "DZA": "DZ", + # E + "ECU": "EC", "EGY": "EG", "ERI": "ER", "ESH": "EH", "ESP": "ES", "EST": "EE", "ETH": "ET", + # F + "FIN": "FI", "FJI": "FJ", "FLK": "FK", "FRA": "FR", "FRO": "FO", "FSM": "FM", + # G + "GAB": "GA", "GBR": "GB", "GEO": "GE", "GGY": "GG", "GHA": "GH", "GIB": "GI", "GIN": "GN", + "GLP": "GP", "GMB": "GM", "GNB": "GW", "GNQ": "GQ", "GRC": "GR", "GRD": "GD", "GRL": "GL", + "GTM": "GT", "GUF": "GF", "GUM": "GU", "GUY": "GY", + # H + "HKG": "HK", "HMD": "HM", "HND": "HN", "HRV": "HR", "HTI": "HT", "HUN": "HU", + # I + "IDN": "ID", "IMN": "IM", "IND": "IN", "IOT": "IO", "IRL": "IE", "IRN": "IR", "IRQ": "IQ", + "ISL": "IS", "ISR": "IL", "ITA": "IT", + # J + "JAM": "JM", "JEY": "JE", "JOR": "JO", "JPN": "JP", + # K + "KAZ": "KZ", "KEN": "KE", "KGZ": "KG", "KHM": "KH", "KIR": "KI", "KNA": "KN", "KOR": "KR", + "KWT": "KW", + # L + "LAO": "LA", "LBN": "LB", "LBR": "LR", "LBY": "LY", "LCA": "LC", "LIE": "LI", "LKA": "LK", + "LSO": "LS", "LTU": "LT", "LUX": "LU", "LVA": "LV", + # M + "MAC": "MO", "MAR": "MA", "MCO": "MC", "MDA": "MD", "MDG": "MG", "MDV": "MV", "MEX": "MX", + "MHL": "MH", "MKD": "MK", "MLI": "ML", "MLT": "MT", "MMR": "MM", "MNE": "ME", "MNG": "MN", + "MNP": "MP", "MOZ": "MZ", "MRT": "MR", "MSR": "MS", "MTQ": "MQ", "MUS": "MU", "MWI": "MW", + "MYS": "MY", "MYT": "YT", + # N + "NAM": "NA", "NCL": "NC", "NER": "NE", "NFK": "NF", "NGA": "NG", "NIC": "NI", "NIU": "NU", + "NLD": "NL", "NOR": "NO", "NPL": "NP", "NRU": "NR", "NZL": "NZ", + # O + "OMN": "OM", + # P + "PAK": "PK", "PAN": "PA", "PCN": "PN", "PER": "PE", "PHL": "PH", "PLW": "PW", "PNG": "PG", + "POL": "PL", "PRI": "PR", "PRK": "KP", "PRT": "PT", "PRY": "PY", "PSE": "PS", "PYF": "PF", + # Q + "QAT": "QA", + # R + "REU": "RE", "ROU": "RO", "RUS": "RU", "RWA": "RW", + # S + "SAU": "SA", "SDN": "SD", "SEN": "SN", "SGP": "SG", "SGS": "GS", "SHN": "SH", "SJM": "SJ", + "SLB": "SB", "SLE": "SL", "SLV": "SV", "SMR": "SM", "SOM": "SO", "SPM": "PM", "SRB": "RS", + "STP": "ST", "SUR": "SR", "SVK": "SK", "SVN": "SI", "SWE": "SE", "SWZ": "SZ", "SYC": "SC", + "SYR": "SY", + # T + "TCA": "TC", "TCD": "TD", "TGO": "TG", "THA": "TH", "TJK": "TJ", "TKL": "TK", "TKM": "TM", + "TLS": "TL", "TON": "TO", "TTO": "TT", "TUN": "TN", "TUR": "TR", "TUV": "TV", "TWN": "TW", + "TZA": "TZ", + # U + "UGA": "UG", "UKR": "UA", "UMI": "UM", "URY": "UY", "USA": "US", "UZB": "UZ", + # V + "VAT": "VA", "VCT": "VC", "VEN": "VE", "VGB": "VG", "VIR": "VI", "VNM": "VN", "VUT": "VU", + # W + "WLF": "WF", "WSM": "WS", + # Y + "YEM": "YE", + # Z + "ZAF": "ZA", "ZMB": "ZM", "ZWE": "ZW", +} +_calling_codes = { + # A + "ABW": "+297", "AFG": "+93", "AGO": "+244", "AIA": "+1-264", "ALB": "+355", "AND": "+376", + "ANT": "+599", "ARE": "+971", "ARG": "+54", "ARM": "+374", "ASM": "+1-684", "ATA": "+672", + "ATG": "+1-268", "AUS": "+61", "AUT": "+43", "AZE": "+994", + # B + "BDI": "+257", "BEL": "+32", "BEN": "+229", "BFA": "+226", "BGD": "+880", "BGR": "+359", + "BHR": "+973", "BHS": "+1-242", "BIH": "+387", "BLR": "+375", "BLZ": "+501", + "BMU": "+1-441", "BOL": "+591", "BRA": "+55", "BRB": "+1-246", "BRN": "+673", "BTN": "+975", + "BWA": "+267", + # C + "CAF": "+236", "CAN": "+1", "CCK": "+61", "CHE": "+41", "CHL": "+56", "CHN": "+86", + "CMR": "+237", "COD": "+243", "COG": "+242", "COK": "+682", "COL": "+57", "COM": "+269", + "CPV": "+238", "CRI": "+506", "CUB": "+53", "CXR": "+61", "CYM": "+1-345", "CYP": "+357", + "CZE": "+420", + # D + "DEU": "+49", "DJI": "+253", "DMA": "+1-767", "DNK": "+45", "DOM": "+1-809", "DZA": "+213", + # E + "ECU": "+593", "EGY": "+20", "ERI": "+291", "ESH": "+212", "ESP": "+34", "EST": "+372", + "ETH": "+251", + # F + "FIN": "+358", "FJI": "+679", "FLK": "+500", "FRA": "+33", "FRO": "+298", "FSM": "+691", + # G + "GAB": "+241", "GBR": "+44", "GEO": "+995", "GGY": "+44-1481", "GHA": "+233", "GIB": "+350", + "GIN": "+224", "GLP": "+590", "GMB": "+220", "GNB": "+245", "GNQ": "+240", "GRC": "+30", + "GRD": "+1-473", "GRL": "+299", "GTM": "+502", "GUF": "+594", "GUM": "+1-671", + "GUY": "+592", + # H + "HKG": "+852", "HMD": "+672", "HND": "+504", "HRV": "+385", "HTI": "+509", "HUN": "+36", + # I + "IDN": "+62", "IMN": "+44-1624", "IND": "+91", "IOT": "+246", "IRL": "+353", "IRN": "+98", + "IRQ": "+964", "ISL": "+354", "ISR": "+972", "ITA": "+39", + # J + "JAM": "+1-876", "JEY": "+44-1534", "JOR": "+962", "JPN": "+81", + # K + "KAZ": "+7", "KEN": "+254", "KGZ": "+996", "KHM": "+855", "KIR": "+686", "KNA": "+1-869", + "KOR": "+82", "KWT": "+965", + # L + "LAO": "+856", "LBN": "+961", "LBR": "+231", "LBY": "+218", "LCA": "+1-758", "LIE": "+423", + "LKA": "+94", "LSO": "+266", "LTU": "+370", "LUX": "+352", "LVA": "+371", + # M + "MAC": "+853", "MAR": "+212", "MCO": "+377", "MDA": "+373", "MDG": "+261", "MDV": "+960", + "MEX": "+52", "MHL": "+692", "MKD": "+389", "MLI": "+223", "MLT": "+356", "MMR": "+95", + "MNE": "+382", "MNG": "+976", "MNP": "+1-670", "MOZ": "+258", "MRT": "+222", + "MSR": "+1-664", "MTQ": "+596", "MUS": "+230", "MWI": "+265", "MYS": "+60", "MYT": "+262", + # N + "NAM": "+264", "NCL": "+687", "NER": "+227", "NFK": "+672", "NGA": "+234", "NIC": "+505", + "NIU": "+683", "NLD": "+31", "NOR": "+47", "NPL": "+977", "NRU": "+674", "NZL": "+64", + # O + "OMN": "+968", + # P + "PAK": "+92", "PAN": "+507", "PCN": "+64", "PER": "+51", "PHL": "+63", "PLW": "+680", + "PNG": "+675", "POL": "+48", "PRI": "+1-787", "PRK": "+850", "PRT": "+351", "PRY": "+595", + "PSE": "+970", "PYF": "+689", + # Q + "QAT": "+974", + # R + "REU": "+262", "ROU": "+40", "RUS": "+7", "RWA": "+250", + # S + "SAU": "+966", "SDN": "+249", "SEN": "+221", "SGP": "+65", "SHN": "+290", "SJM": "+47", + "SLB": "+677", "SLE": "+232", "SLV": "+503", "SMR": "+378", "SOM": "+252", "SPM": "+508", + "SRB": "+381", "STP": "+239", "SUR": "+597", "SVK": "+421", "SVN": "+386", "SWE": "+46", + "SWZ": "+268", "SYC": "+248", "SYR": "+963", + # T + "TCA": "+1-649", "TCD": "+235", "TGO": "+228", "THA": "+66", "TJK": "+992", "TKL": "+690", + "TKM": "+993", "TLS": "+670", "TON": "+676", "TTO": "+1-868", "TUN": "+216", "TUR": "+90", + "TUV": "+688", "TWN": "+886", "TZA": "+255", + # U + "UGA": "+256", "UKR": "+380", "UMI": "+1", "URY": "+598", "USA": "+1", "UZB": "+998", + # V + "VAT": "+379", "VCT": "+1-784", "VEN": "+58", "VGB": "+1-284", "VIR": "+1-340", + "VNM": "+84", "VUT": "+678", + # W + "WLF": "+681", "WSM": "+685", + # Y + "YEM": "+967", + # Z + "ZAF": "+27", "ZMB": "+260", "ZWE": "+263" +} +_numeric = { + "004", "008", "010", "012", "016", "020", "024", "028", "031", "032", + "036", "040", "044", "048", "050", "051", "052", "056", "060", "064", + "068", "070", "072", "074", "076", "084", "086", "090", "092", "096", + "100", "104", "108", "112", "116", "120", "124", "132", "136", "140", + "144", "148", "152", "156", "158", "162", "166", "170", "174", "175", + "178", "180", "184", "188", "191", "192", "196", "203", "204", "208", + "212", "214", "218", "222", "226", "231", "232", "233", "234", "238", + "239", "242", "246", "248", "250", "254", "258", "260", "262", "266", + "268", "270", "275", "276", "288", "292", "296", "300", "304", "308", + "312", "316", "320", "324", "328", "332", "334", "340", "344", "348", + "352", "356", "360", "364", "368", "372", "376", "380", "384", "388", + "392", "398", "400", "404", "408", "410", "414", "417", "418", "422", + "426", "428", "430", "434", "438", "440", "442", "446", "450", "454", + "458", "462", "466", "470", "474", "478", "480", "484", "492", "496", + "498", "499", "500", "504", "508", "512", "516", "520", "524", "528", + "531", "533", "534", "535", "540", "548", "554", "558", "562", "566", + "570", "574", "578", "580", "581", "583", "584", "585", "586", "591", + "598", "600", "604", "608", "612", "616", "620", "624", "626", "630", + "634", "638", "642", "643", "646", "652", "654", "659", "660", "662", + "663", "666", "670", "674", "678", "682", "686", "688", "690", "694", + "702", "703", "704", "705", "706", "710", "716", "724", "728", "729", + "732", "740", "744", "748", "752", "756", "760", "762", "764", "768", + "772", "776", "780", "784", "788", "792", "795", "796", "798", "800", + "804", "807", "818", "826", "831", "832", "833", "834", "840", "850", + "854", "858", "860", "862", "876", "882", "887", "894", +} +_currency_iso4217 = { + # https://en.wikipedia.org/wiki/ISO_4217 + "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", + "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", + "BWP", "BYN", "BZD", + "CAD", "CDF", "CHE", "CHF", "CHW", "CKD", "CLF", "CLP", "CNY", "COP", "CRC", "CUC", "CUP", + "CVE", "CZK", + "DJF", "DKK", "DOP", "DZD", + "EGP", "ERN", "ETB", "EUR", + "FJD", "FKP", + "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", + "HKD", "HNL", "HRK", "HTG", "HUF", + "IDR", "IEP", "ILS", "INR", "IQD", "IRR", "ISK", + "JMD", "JOD", "JPY", + "KES", "KGS", "KHR", "KID", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", + "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", + "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", + "MWK", "MXN", "MYR", "MZN", + "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", + "OMR", + "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", + "QAR", + "RON", "RSD", "RUB", "RWF", + "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", + "SRD", "SSP", "STN", "SVC", "SYP", "SZL", + "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", + "UAH", "UGX", "USD", "UYU", "UZS", + "VED", "VES", "VND", "VUV", + "WST", + "XAF", "XCD", "XDR", "XOF", "XPF", + "YER", + "ZAR", "ZMW", "ZWL" +} +_currency_symbols = { + # https://en.wikipedia.org/wiki/Currency_sign_(generic) + "؋", "฿", "₵", "₡", "¢", "$", "₫", "֏", "€", "ƒ", "₣", "₲", "₴", "₭", "₾", "£", "₺", "₼", "₦", + "₱", "元", "圆", "圓", "﷼", "៛", "₽", "₹", "रू", "රු", "૱", "௹", "꠸", "Rs", "₪", "⃀" "৳", "₸", + "₮", "₩", "¥", "円", "₿", "¤" +} +# fmt: on + + +def _get_code_type(format_type: str): + """Returns the type of country code.""" + if format_type.isdecimal(): + return "numeric" + if format_type.isalpha(): + if len(format_type) == 2: + return "alpha2" + if len(format_type) == 3: + return "alpha3" + return "invalid" + + +@validator +def calling_code(value: str, /): + """Validates given calling code. + + This performs country's calling code validation. + + Examples: + >>> calling_code('+91') + # Output: True + >>> calling_code('-31') + # Output: ValidationError(func=calling_code, args={'value': '-31'}) + + Args: + value: + Country's calling code string to validate. + + Returns: + (Literal[True]): If `value` is a valid calling code. + (ValidationError): If `value` is an invalid calling code. + """ + if not value: + return False + + return value in set(_calling_codes.values()) + + +@validator +def country_code(value: str, /, *, iso_format: str = "auto", ignore_case: bool = False): + """Validates given country code. + + This performs a case-sensitive [ISO 3166][1] country code validation. + + [1]: https://www.iso.org/iso-3166-country-codes.html + + Examples: + >>> country_code('GB', iso_format='alpha3') + # Output: False + >>> country_code('USA') + # Output: True + >>> country_code('840', iso_format='numeric') + # Output: True + >>> country_code('iN', iso_format='alpha2') + # Output: False + >>> country_code('ZWE', iso_format='alpha3') + # Output: True + + Args: + value: + Country code string to validate. + iso_format: + ISO format to be used. Available options are: + `auto`, `alpha2`, `alpha3` and `numeric`. + ignore_case: + Enable/Disable case-sensitive matching. + + Returns: + (Literal[True]): If `value` is a valid country code. + (ValidationError): If `value` is an invalid country code. + """ + if not value: + return False + + if not (1 < len(value) < 4): + return False + + if iso_format == "auto" and (iso_format := _get_code_type(value)) == "invalid": + return False + + if iso_format == "alpha2": + return ( + value.upper() in set(_alpha3_to_alpha2.values()) + if ignore_case + else value in set(_alpha3_to_alpha2.values()) + ) + if iso_format == "alpha3": + return value.upper() in _alpha3_to_alpha2 if ignore_case else value in _alpha3_to_alpha2 + + return value in _numeric if iso_format == "numeric" else False + + +@validator +def currency(value: str, /, *, skip_symbols: bool = True, ignore_case: bool = False): + """Validates given currency code. + + This performs [ISO 4217][1] currency code/symbol validation. + + [1]: https://www.iso.org/iso-4217-currency-codes.html + + Examples: + >>> currency('USD') + # Output: True + >>> currency('ZWX') + # Output: ValidationError(func=currency, args={'value': 'ZWX'}) + + Args: + value: + Currency code/symbol string to validate. + skip_symbols: + Skip currency symbol validation. + ignore_case: + Enable/Disable case-sensitive matching. + + Returns: + (Literal[True]): If `value` is a valid currency code. + (ValidationError): If `value` is an invalid currency code. + """ + if not value: + return False + + if not skip_symbols and value in _currency_symbols: + return True + + if len(value) != 3: + return False + + return value.upper() in _currency_iso4217 if ignore_case else value in _currency_iso4217 diff --git a/src/validators/country_code.py b/src/validators/country_code.py deleted file mode 100644 index 61cfbbd3..00000000 --- a/src/validators/country_code.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Country Codes.""" - -# local -from validators.utils import validator - -# fmt: off -alpha_2 = { - "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", - "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", - "BT", "BV", "BW", "BY", "BZ", - "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", "CO", "CR", "CU", "CV", "CW", - "CX", "CY", "CZ", - "DE", "DJ", "DK", "DM", "DO", "DZ", - "EC", "EE", "EG", "EH", "ER", "ES", "ET", - "FI", "FJ", "FK", "FM", "FO", "FR", - "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", "GN", "GP", "GQ", "GR", "GS", "GT", - "GU", "GW", "GY", - "HK", "HM", "HN", "HR", "HT", "HU", - "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", "IT", - "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", - "KM", "KN", "KP", "KR", "KW", "KY", "KZ", - "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", "LU", "LV", "LY", - "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", "MM", "MN", "MO", "MP", "MQ", "MR", "MS", - "MT", "MU", "MV", "MW", "MX", "MY", "MZ", - "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", "NU", "NZ", - "OM", - "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", "PR", "PS", "PT", "PW", "PY", - "QA", - "RE", "RO", "RS", "RU", "RW", - "SA", "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", "SO", "SR", "SS", - "ST", "SV", "SX", "SY", "SZ", - "TC", "TD", "TF", "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", "TZ", - "UA", "UG", "UM", "US", "UY", "UZ", - "VC", "VE", "VG", "VI", "VN", "VU", - "WF", "WS", - "YE", "YT", - "ZA", "ZM", "ZW", -} -alpha_3 = { - "ABW", "AFG", "AGO", "AIA", "ALA", "ALB", "AND", "ARE", "ARG", "ARM", "ASM", "ATA", "ATF", - "ATG", "AUS", "AUT", "AZE", - "BDI", "BEL", "BEN", "BES", "BFA", "BGD", "BGR", "BHR", "BHS", "BIH", "BLM", "BLR", "BLZ", - "BMU", "BOL", "BRA", "BRB", "BRN", "BTN", "BVT", "BWA", - "CAF", "CAN", "CCK", "CHE", "CHL", "CHN", "CIV", "CMR", "COD", "COG", "COK", "COL", "COM", - "CPV", "CRI", "CUB", "CUW", "CXR", "CYM", "CYP", "CZE", - "DEU", "DJI", "DMA", "DNK", "DOM", "DZA", - "ECU", "EGY", "ERI", "ESH", "ESP", "EST", "ETH", - "FIN", "FJI", "FLK", "FRA", "FRO", "FSM", - "GAB", "GBR", "GEO", "GGY", "GHA", "GIB", "GIN", "GLP", "GMB", "GNB", "GNQ", "GRC", "GRD", - "GRL", "GTM", "GUF", "GUM", "GUY", - "HKG", "HMD", "HND", "HRV", "HTI", "HUN", - "IDN", "IMN", "IND", "IOT", "IRL", "IRN", "IRQ", "ISL", "ISR", "ITA", - "JAM", "JEY", "JOR", "JPN", - "KAZ", "KEN", "KGZ", "KHM", "KIR", "KNA", "KOR", "KWT", - "LAO", "LBN", "LBR", "LBY", "LCA", "LIE", "LKA", "LSO", "LTU", "LUX", "LVA", - "MAC", "MAF", "MAR", "MCO", "MDA", "MDG", "MDV", "MEX", "MHL", "MKD", "MLI", "MLT", "MMR", - "MNE", "MNG", "MNP", "MOZ", "MRT", "MSR", "MTQ", "MUS", "MWI", "MYS", "MYT", - "NAM", "NCL", "NER", "NFK", "NGA", "NIC", "NIU", "NLD", "NOR", "NPL", "NRU", "NZL", - "OMN", - "PAK", "PAN", "PCN", "PER", "PHL", "PLW", "PNG", "POL", "PRI", "PRK", "PRT", "PRY", "PSE", - "PYF", - "QAT", - "REU", "ROU", "RUS", "RWA", - "SAU", "SDN", "SEN", "SGP", "SGS", "SHN", "SJM", "SLB", "SLE", "SLV", "SMR", "SOM", "SPM", - "SRB", "SSD", "STP", "SUR", "SVK", "SVN", "SWE", "SWZ", "SXM", "SYC", "SYR", - "TCA", "TCD", "TGO", "THA", "TJK", "TKL", "TKM", "TLS", "TON", "TTO", "TUN", "TUR", "TUV", - "TWN", "TZA", - "UGA", "UKR", "UMI", "URY", "USA", "UZB", - "VCT", "VEN", "VGB", "VIR", "VNM", "VUT", - "WLF", "WSM", - "YEM", - "ZAF", "ZMB", "ZWE", -} -numeric = { - "004", "008", "010", "012", "016", "020", "024", "028", "031", "032", - "036", "040", "044", "048", "050", "051", "052", "056", "060", "064", - "068", "070", "072", "074", "076", "084", "086", "090", "092", "096", - "100", "104", "108", "112", "116", "120", "124", "132", "136", "140", - "144", "148", "152", "156", "158", "162", "166", "170", "174", "175", - "178", "180", "184", "188", "191", "192", "196", "203", "204", "208", - "212", "214", "218", "222", "226", "231", "232", "233", "234", "238", - "239", "242", "246", "248", "250", "254", "258", "260", "262", "266", - "268", "270", "275", "276", "288", "292", "296", "300", "304", "308", - "312", "316", "320", "324", "328", "332", "334", "340", "344", "348", - "352", "356", "360", "364", "368", "372", "376", "380", "384", "388", - "392", "398", "400", "404", "408", "410", "414", "417", "418", "422", - "426", "428", "430", "434", "438", "440", "442", "446", "450", "454", - "458", "462", "466", "470", "474", "478", "480", "484", "492", "496", - "498", "499", "500", "504", "508", "512", "516", "520", "524", "528", - "531", "533", "534", "535", "540", "548", "554", "558", "562", "566", - "570", "574", "578", "580", "581", "583", "584", "585", "586", "591", - "598", "600", "604", "608", "612", "616", "620", "624", "626", "630", - "634", "638", "642", "643", "646", "652", "654", "659", "660", "662", - "663", "666", "670", "674", "678", "682", "686", "688", "690", "694", - "702", "703", "704", "705", "706", "710", "716", "724", "728", "729", - "732", "740", "744", "748", "752", "756", "760", "762", "764", "768", - "772", "776", "780", "784", "788", "792", "795", "796", "798", "800", - "804", "807", "818", "826", "831", "832", "833", "834", "840", "850", - "854", "858", "860", "862", "876", "882", "887", "894", -} -# fmt: on - - -def get_code_type(format_type: str): - """Returns the type of country code.""" - if format_type.isdigit(): - return "numeric" - if format_type.isalpha(): - if len(format_type) == 2: - return "alpha2" - if len(format_type) == 3: - return "alpha3" - return "invalid" - - -@validator -def country_code(value: str, /, *, iso_format: str = "auto"): - """Validates given country code. - - This performs a case-sensitive [ISO 3166][1] country code validation. - - [1]: https://www.iso.org/iso-3166-country-codes.html - - Examples: - >>> country_code('GB', iso_format='alpha3') - # Output: False - >>> country_code('USA') - # Output: True - >>> country_code('840', iso_format='numeric') - # Output: True - >>> country_code('iN', iso_format='alpha2') - # Output: False - >>> country_code('ZWE', iso_format='alpha3') - # Output: True - - Args: - value: - Country code string to validate. - iso_format: - ISO format to be used. Available options are: - `auto`, `alpha2`, `alpha3` and `numeric`. - - Returns: - (Literal[True]): If `value` is a valid country code. - (ValidationError): If `value` is an invalid country code. - """ - if not value: - return False - - if not (1 < len(value) < 4): - return False - - if iso_format == "auto" and (iso_format := get_code_type(value)) == "invalid": - return False - - if iso_format == "alpha2": - return value in alpha_2 - if iso_format == "alpha3": - return value in alpha_3 - return value in numeric if iso_format == "numeric" else False diff --git a/tests/test_country.py b/tests/test_country.py new file mode 100644 index 00000000..fbea0ba1 --- /dev/null +++ b/tests/test_country.py @@ -0,0 +1,76 @@ +"""Test Country.""" + +# external +import pytest + +# local +from validators import ValidationError, calling_code, country_code, currency + + +@pytest.mark.parametrize(("value"), ["+1", "+371"]) +def test_returns_true_on_valid_calling_code(value: str): + """Test returns true on valid calling code.""" + assert calling_code(value) + + +@pytest.mark.parametrize(("value"), ["+19", "+37", "-9"]) +def test_returns_failed_validation_invalid_calling_code(value: str): + """Test returns failed validation invalid calling code.""" + assert isinstance(calling_code(value), ValidationError) + + +@pytest.mark.parametrize( + ("value", "iso_format"), + [ + ("ISR", "auto"), + ("US", "alpha2"), + ("USA", "alpha3"), + ("840", "numeric"), + ], +) +def test_returns_true_on_valid_country_code(value: str, iso_format: str): + """Test returns true on valid country code.""" + assert country_code(value, iso_format=iso_format) + + +@pytest.mark.parametrize( + ("value", "iso_format"), + [ + (None, "auto"), + ("", "auto"), + ("123456", "auto"), + ("XY", "alpha2"), + ("PPP", "alpha3"), + ("123", "numeric"), + ("us", "auto"), + ("uSa", "auto"), + ("US ", "auto"), + ("U.S", "auto"), + ("1ND", "unknown"), + ("ISR", None), + ], +) +def test_returns_failed_validation_on_invalid_country_code(value: str, iso_format: str): + """Test returns failed validation on invalid country code.""" + assert isinstance(country_code(value, iso_format=iso_format), ValidationError) + + +@pytest.mark.parametrize( + ("value", "skip_symbols", "ignore_case"), [("$", False, False), ("uSd", True, True)] +) +def test_returns_true_on_valid_currency(value: str, skip_symbols: bool, ignore_case: bool): + """Test returns true on valid currency.""" + assert currency(value, skip_symbols=skip_symbols, ignore_case=ignore_case) + + +@pytest.mark.parametrize( + ("value", "skip_symbols", "ignore_case"), + [("$", True, False), ("uSd", True, False), ("Bucks", True, True)], +) +def test_returns_failed_validation_invalid_currency( + value: str, skip_symbols: bool, ignore_case: bool +): + """Test returns failed validation invalid currency.""" + assert isinstance( + currency(value, skip_symbols=skip_symbols, ignore_case=ignore_case), ValidationError + ) diff --git a/tests/test_country_code.py b/tests/test_country_code.py deleted file mode 100644 index 056486cc..00000000 --- a/tests/test_country_code.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Test Country Codes.""" - -# external -import pytest - -# local -from validators import ValidationError, country_code - - -@pytest.mark.parametrize( - ("value", "iso_format"), - [ - ("ISR", "auto"), - ("US", "alpha2"), - ("USA", "alpha3"), - ("840", "numeric"), - ], -) -def test_returns_true_on_valid_country_code(value: str, iso_format: str): - """Test returns true on valid country code.""" - assert country_code(value, iso_format=iso_format) - - -@pytest.mark.parametrize( - ("value", "iso_format"), - [ - (None, "auto"), - ("", "auto"), - ("123456", "auto"), - ("XY", "alpha2"), - ("PPP", "alpha3"), - ("123", "numeric"), - ("us", "auto"), - ("uSa", "auto"), - ("US ", "auto"), - ("U.S", "auto"), - ("1ND", "unknown"), - ("ISR", None), - ], -) -def test_returns_failed_validation_on_invalid_country_code(value: str, iso_format: str): - """Test returns failed validation on invalid country code.""" - assert isinstance(country_code(value, iso_format=iso_format), ValidationError)