mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
feat(platform): standardize country inputs with ISO 3166 Country type (#7333)
* feat(platform): standardize country inputs using pydantic-extra-types Add CountryParam type that accepts: - ISO 3166-1 alpha-2 codes (e.g., 'US', 'us') - Full country names (e.g., 'United States') - lower_snake_case names (e.g., 'united_states') Uses pydantic-extra-types for programmatic ISO validation instead of hardcoded mappings. Serializes to lowercase alpha-2 code by default. Example usage in FMP equity screener: - country: CountryParam | None Partially addresses #6969 * refactor: use Country class inheriting from str for type checker compatibility - Replace Annotated CountryParam with Country(str) class per maintainer feedback - Store full pycountry object for access to alpha_2, alpha_3, name, numeric - Use pycountry directly instead of pydantic-extra-types - Add support for alpha-3 codes (e.g., 'USA') - Update tests to cover new functionality * Replace pycountry with static JSON country data - Add country_data.json with ISO 3166-1 data (20KB vs 6MB pycountry) - Update Country class to use static JSON lookup - Remove pycountry dependency from pyproject.toml - No decompression needed, data loaded once at import * fix(lint): D402 docstring should start with verb * fix(lint): work around pydocstyle D402 false positive When a method is named `name` and `.pydocstyle.ini` uses `match = .*\.py`, pydocstyle incorrectly triggers D402 for any docstring containing the word "name" anywhere - not just at the start as the rule intends. Changed docstring from "Return the full country name..." to "The country's official designation..." to avoid the false positive. See: https://github.com/PyCQA/pydocstyle/issues/375 * fix(lint): resolve pylint warnings in country_utils * fix(lint): use ternary for ruff SIM108 in country_utils * fix(lint): format ternary for black line length --------- Co-authored-by: Danglewood <85772166+deeleeramone@users.noreply.github.com>
This commit is contained in:
253
openbb_platform/core/openbb_core/provider/utils/country_data.json
vendored
Normal file
253
openbb_platform/core/openbb_core/provider/utils/country_data.json
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"countries": [
|
||||
{"alpha_2": "AF", "alpha_3": "AFG", "name": "Afghanistan", "numeric": "004"},
|
||||
{"alpha_2": "AL", "alpha_3": "ALB", "name": "Albania", "numeric": "008"},
|
||||
{"alpha_2": "DZ", "alpha_3": "DZA", "name": "Algeria", "numeric": "012"},
|
||||
{"alpha_2": "AS", "alpha_3": "ASM", "name": "American Samoa", "numeric": "016"},
|
||||
{"alpha_2": "AD", "alpha_3": "AND", "name": "Andorra", "numeric": "020"},
|
||||
{"alpha_2": "AO", "alpha_3": "AGO", "name": "Angola", "numeric": "024"},
|
||||
{"alpha_2": "AI", "alpha_3": "AIA", "name": "Anguilla", "numeric": "660"},
|
||||
{"alpha_2": "AQ", "alpha_3": "ATA", "name": "Antarctica", "numeric": "010"},
|
||||
{"alpha_2": "AG", "alpha_3": "ATG", "name": "Antigua and Barbuda", "numeric": "028"},
|
||||
{"alpha_2": "AR", "alpha_3": "ARG", "name": "Argentina", "numeric": "032"},
|
||||
{"alpha_2": "AM", "alpha_3": "ARM", "name": "Armenia", "numeric": "051"},
|
||||
{"alpha_2": "AW", "alpha_3": "ABW", "name": "Aruba", "numeric": "533"},
|
||||
{"alpha_2": "AU", "alpha_3": "AUS", "name": "Australia", "numeric": "036"},
|
||||
{"alpha_2": "AT", "alpha_3": "AUT", "name": "Austria", "numeric": "040"},
|
||||
{"alpha_2": "AZ", "alpha_3": "AZE", "name": "Azerbaijan", "numeric": "031"},
|
||||
{"alpha_2": "BS", "alpha_3": "BHS", "name": "Bahamas", "numeric": "044"},
|
||||
{"alpha_2": "BH", "alpha_3": "BHR", "name": "Bahrain", "numeric": "048"},
|
||||
{"alpha_2": "BD", "alpha_3": "BGD", "name": "Bangladesh", "numeric": "050"},
|
||||
{"alpha_2": "BB", "alpha_3": "BRB", "name": "Barbados", "numeric": "052"},
|
||||
{"alpha_2": "BY", "alpha_3": "BLR", "name": "Belarus", "numeric": "112"},
|
||||
{"alpha_2": "BE", "alpha_3": "BEL", "name": "Belgium", "numeric": "056"},
|
||||
{"alpha_2": "BZ", "alpha_3": "BLZ", "name": "Belize", "numeric": "084"},
|
||||
{"alpha_2": "BJ", "alpha_3": "BEN", "name": "Benin", "numeric": "204"},
|
||||
{"alpha_2": "BM", "alpha_3": "BMU", "name": "Bermuda", "numeric": "060"},
|
||||
{"alpha_2": "BT", "alpha_3": "BTN", "name": "Bhutan", "numeric": "064"},
|
||||
{"alpha_2": "BO", "alpha_3": "BOL", "name": "Bolivia, Plurinational State of", "numeric": "068"},
|
||||
{"alpha_2": "BQ", "alpha_3": "BES", "name": "Bonaire, Sint Eustatius and Saba", "numeric": "535"},
|
||||
{"alpha_2": "BA", "alpha_3": "BIH", "name": "Bosnia and Herzegovina", "numeric": "070"},
|
||||
{"alpha_2": "BW", "alpha_3": "BWA", "name": "Botswana", "numeric": "072"},
|
||||
{"alpha_2": "BV", "alpha_3": "BVT", "name": "Bouvet Island", "numeric": "074"},
|
||||
{"alpha_2": "BR", "alpha_3": "BRA", "name": "Brazil", "numeric": "076"},
|
||||
{"alpha_2": "IO", "alpha_3": "IOT", "name": "British Indian Ocean Territory", "numeric": "086"},
|
||||
{"alpha_2": "BN", "alpha_3": "BRN", "name": "Brunei Darussalam", "numeric": "096"},
|
||||
{"alpha_2": "BG", "alpha_3": "BGR", "name": "Bulgaria", "numeric": "100"},
|
||||
{"alpha_2": "BF", "alpha_3": "BFA", "name": "Burkina Faso", "numeric": "854"},
|
||||
{"alpha_2": "BI", "alpha_3": "BDI", "name": "Burundi", "numeric": "108"},
|
||||
{"alpha_2": "CV", "alpha_3": "CPV", "name": "Cabo Verde", "numeric": "132"},
|
||||
{"alpha_2": "KH", "alpha_3": "KHM", "name": "Cambodia", "numeric": "116"},
|
||||
{"alpha_2": "CM", "alpha_3": "CMR", "name": "Cameroon", "numeric": "120"},
|
||||
{"alpha_2": "CA", "alpha_3": "CAN", "name": "Canada", "numeric": "124"},
|
||||
{"alpha_2": "KY", "alpha_3": "CYM", "name": "Cayman Islands", "numeric": "136"},
|
||||
{"alpha_2": "CF", "alpha_3": "CAF", "name": "Central African Republic", "numeric": "140"},
|
||||
{"alpha_2": "TD", "alpha_3": "TCD", "name": "Chad", "numeric": "148"},
|
||||
{"alpha_2": "CL", "alpha_3": "CHL", "name": "Chile", "numeric": "152"},
|
||||
{"alpha_2": "CN", "alpha_3": "CHN", "name": "China", "numeric": "156"},
|
||||
{"alpha_2": "CX", "alpha_3": "CXR", "name": "Christmas Island", "numeric": "162"},
|
||||
{"alpha_2": "CC", "alpha_3": "CCK", "name": "Cocos (Keeling) Islands", "numeric": "166"},
|
||||
{"alpha_2": "CO", "alpha_3": "COL", "name": "Colombia", "numeric": "170"},
|
||||
{"alpha_2": "KM", "alpha_3": "COM", "name": "Comoros", "numeric": "174"},
|
||||
{"alpha_2": "CG", "alpha_3": "COG", "name": "Congo", "numeric": "178"},
|
||||
{"alpha_2": "CD", "alpha_3": "COD", "name": "Congo, Democratic Republic of the", "numeric": "180"},
|
||||
{"alpha_2": "CK", "alpha_3": "COK", "name": "Cook Islands", "numeric": "184"},
|
||||
{"alpha_2": "CR", "alpha_3": "CRI", "name": "Costa Rica", "numeric": "188"},
|
||||
{"alpha_2": "HR", "alpha_3": "HRV", "name": "Croatia", "numeric": "191"},
|
||||
{"alpha_2": "CU", "alpha_3": "CUB", "name": "Cuba", "numeric": "192"},
|
||||
{"alpha_2": "CW", "alpha_3": "CUW", "name": "Curaçao", "numeric": "531"},
|
||||
{"alpha_2": "CY", "alpha_3": "CYP", "name": "Cyprus", "numeric": "196"},
|
||||
{"alpha_2": "CZ", "alpha_3": "CZE", "name": "Czechia", "numeric": "203"},
|
||||
{"alpha_2": "CI", "alpha_3": "CIV", "name": "Côte d'Ivoire", "numeric": "384"},
|
||||
{"alpha_2": "DK", "alpha_3": "DNK", "name": "Denmark", "numeric": "208"},
|
||||
{"alpha_2": "DJ", "alpha_3": "DJI", "name": "Djibouti", "numeric": "262"},
|
||||
{"alpha_2": "DM", "alpha_3": "DMA", "name": "Dominica", "numeric": "212"},
|
||||
{"alpha_2": "DO", "alpha_3": "DOM", "name": "Dominican Republic", "numeric": "214"},
|
||||
{"alpha_2": "EC", "alpha_3": "ECU", "name": "Ecuador", "numeric": "218"},
|
||||
{"alpha_2": "EG", "alpha_3": "EGY", "name": "Egypt", "numeric": "818"},
|
||||
{"alpha_2": "SV", "alpha_3": "SLV", "name": "El Salvador", "numeric": "222"},
|
||||
{"alpha_2": "GQ", "alpha_3": "GNQ", "name": "Equatorial Guinea", "numeric": "226"},
|
||||
{"alpha_2": "ER", "alpha_3": "ERI", "name": "Eritrea", "numeric": "232"},
|
||||
{"alpha_2": "EE", "alpha_3": "EST", "name": "Estonia", "numeric": "233"},
|
||||
{"alpha_2": "SZ", "alpha_3": "SWZ", "name": "Eswatini", "numeric": "748"},
|
||||
{"alpha_2": "ET", "alpha_3": "ETH", "name": "Ethiopia", "numeric": "231"},
|
||||
{"alpha_2": "FK", "alpha_3": "FLK", "name": "Falkland Islands (Malvinas)", "numeric": "238"},
|
||||
{"alpha_2": "FO", "alpha_3": "FRO", "name": "Faroe Islands", "numeric": "234"},
|
||||
{"alpha_2": "FJ", "alpha_3": "FJI", "name": "Fiji", "numeric": "242"},
|
||||
{"alpha_2": "FI", "alpha_3": "FIN", "name": "Finland", "numeric": "246"},
|
||||
{"alpha_2": "FR", "alpha_3": "FRA", "name": "France", "numeric": "250"},
|
||||
{"alpha_2": "GF", "alpha_3": "GUF", "name": "French Guiana", "numeric": "254"},
|
||||
{"alpha_2": "PF", "alpha_3": "PYF", "name": "French Polynesia", "numeric": "258"},
|
||||
{"alpha_2": "TF", "alpha_3": "ATF", "name": "French Southern Territories", "numeric": "260"},
|
||||
{"alpha_2": "GA", "alpha_3": "GAB", "name": "Gabon", "numeric": "266"},
|
||||
{"alpha_2": "GM", "alpha_3": "GMB", "name": "Gambia", "numeric": "270"},
|
||||
{"alpha_2": "GE", "alpha_3": "GEO", "name": "Georgia", "numeric": "268"},
|
||||
{"alpha_2": "DE", "alpha_3": "DEU", "name": "Germany", "numeric": "276"},
|
||||
{"alpha_2": "GH", "alpha_3": "GHA", "name": "Ghana", "numeric": "288"},
|
||||
{"alpha_2": "GI", "alpha_3": "GIB", "name": "Gibraltar", "numeric": "292"},
|
||||
{"alpha_2": "GR", "alpha_3": "GRC", "name": "Greece", "numeric": "300"},
|
||||
{"alpha_2": "GL", "alpha_3": "GRL", "name": "Greenland", "numeric": "304"},
|
||||
{"alpha_2": "GD", "alpha_3": "GRD", "name": "Grenada", "numeric": "308"},
|
||||
{"alpha_2": "GP", "alpha_3": "GLP", "name": "Guadeloupe", "numeric": "312"},
|
||||
{"alpha_2": "GU", "alpha_3": "GUM", "name": "Guam", "numeric": "316"},
|
||||
{"alpha_2": "GT", "alpha_3": "GTM", "name": "Guatemala", "numeric": "320"},
|
||||
{"alpha_2": "GG", "alpha_3": "GGY", "name": "Guernsey", "numeric": "831"},
|
||||
{"alpha_2": "GN", "alpha_3": "GIN", "name": "Guinea", "numeric": "324"},
|
||||
{"alpha_2": "GW", "alpha_3": "GNB", "name": "Guinea-Bissau", "numeric": "624"},
|
||||
{"alpha_2": "GY", "alpha_3": "GUY", "name": "Guyana", "numeric": "328"},
|
||||
{"alpha_2": "HT", "alpha_3": "HTI", "name": "Haiti", "numeric": "332"},
|
||||
{"alpha_2": "HM", "alpha_3": "HMD", "name": "Heard Island and McDonald Islands", "numeric": "334"},
|
||||
{"alpha_2": "VA", "alpha_3": "VAT", "name": "Holy See", "numeric": "336"},
|
||||
{"alpha_2": "HN", "alpha_3": "HND", "name": "Honduras", "numeric": "340"},
|
||||
{"alpha_2": "HK", "alpha_3": "HKG", "name": "Hong Kong", "numeric": "344"},
|
||||
{"alpha_2": "HU", "alpha_3": "HUN", "name": "Hungary", "numeric": "348"},
|
||||
{"alpha_2": "IS", "alpha_3": "ISL", "name": "Iceland", "numeric": "352"},
|
||||
{"alpha_2": "IN", "alpha_3": "IND", "name": "India", "numeric": "356"},
|
||||
{"alpha_2": "ID", "alpha_3": "IDN", "name": "Indonesia", "numeric": "360"},
|
||||
{"alpha_2": "IR", "alpha_3": "IRN", "name": "Iran, Islamic Republic of", "numeric": "364"},
|
||||
{"alpha_2": "IQ", "alpha_3": "IRQ", "name": "Iraq", "numeric": "368"},
|
||||
{"alpha_2": "IE", "alpha_3": "IRL", "name": "Ireland", "numeric": "372"},
|
||||
{"alpha_2": "IM", "alpha_3": "IMN", "name": "Isle of Man", "numeric": "833"},
|
||||
{"alpha_2": "IL", "alpha_3": "ISR", "name": "Israel", "numeric": "376"},
|
||||
{"alpha_2": "IT", "alpha_3": "ITA", "name": "Italy", "numeric": "380"},
|
||||
{"alpha_2": "JM", "alpha_3": "JAM", "name": "Jamaica", "numeric": "388"},
|
||||
{"alpha_2": "JP", "alpha_3": "JPN", "name": "Japan", "numeric": "392"},
|
||||
{"alpha_2": "JE", "alpha_3": "JEY", "name": "Jersey", "numeric": "832"},
|
||||
{"alpha_2": "JO", "alpha_3": "JOR", "name": "Jordan", "numeric": "400"},
|
||||
{"alpha_2": "KZ", "alpha_3": "KAZ", "name": "Kazakhstan", "numeric": "398"},
|
||||
{"alpha_2": "KE", "alpha_3": "KEN", "name": "Kenya", "numeric": "404"},
|
||||
{"alpha_2": "KI", "alpha_3": "KIR", "name": "Kiribati", "numeric": "296"},
|
||||
{"alpha_2": "KP", "alpha_3": "PRK", "name": "Korea, Democratic People's Republic of", "numeric": "408"},
|
||||
{"alpha_2": "KR", "alpha_3": "KOR", "name": "Korea, Republic of", "numeric": "410"},
|
||||
{"alpha_2": "KW", "alpha_3": "KWT", "name": "Kuwait", "numeric": "414"},
|
||||
{"alpha_2": "KG", "alpha_3": "KGZ", "name": "Kyrgyzstan", "numeric": "417"},
|
||||
{"alpha_2": "LA", "alpha_3": "LAO", "name": "Lao People's Democratic Republic", "numeric": "418"},
|
||||
{"alpha_2": "LV", "alpha_3": "LVA", "name": "Latvia", "numeric": "428"},
|
||||
{"alpha_2": "LB", "alpha_3": "LBN", "name": "Lebanon", "numeric": "422"},
|
||||
{"alpha_2": "LS", "alpha_3": "LSO", "name": "Lesotho", "numeric": "426"},
|
||||
{"alpha_2": "LR", "alpha_3": "LBR", "name": "Liberia", "numeric": "430"},
|
||||
{"alpha_2": "LY", "alpha_3": "LBY", "name": "Libya", "numeric": "434"},
|
||||
{"alpha_2": "LI", "alpha_3": "LIE", "name": "Liechtenstein", "numeric": "438"},
|
||||
{"alpha_2": "LT", "alpha_3": "LTU", "name": "Lithuania", "numeric": "440"},
|
||||
{"alpha_2": "LU", "alpha_3": "LUX", "name": "Luxembourg", "numeric": "442"},
|
||||
{"alpha_2": "MO", "alpha_3": "MAC", "name": "Macao", "numeric": "446"},
|
||||
{"alpha_2": "MG", "alpha_3": "MDG", "name": "Madagascar", "numeric": "450"},
|
||||
{"alpha_2": "MW", "alpha_3": "MWI", "name": "Malawi", "numeric": "454"},
|
||||
{"alpha_2": "MY", "alpha_3": "MYS", "name": "Malaysia", "numeric": "458"},
|
||||
{"alpha_2": "MV", "alpha_3": "MDV", "name": "Maldives", "numeric": "462"},
|
||||
{"alpha_2": "ML", "alpha_3": "MLI", "name": "Mali", "numeric": "466"},
|
||||
{"alpha_2": "MT", "alpha_3": "MLT", "name": "Malta", "numeric": "470"},
|
||||
{"alpha_2": "MH", "alpha_3": "MHL", "name": "Marshall Islands", "numeric": "584"},
|
||||
{"alpha_2": "MQ", "alpha_3": "MTQ", "name": "Martinique", "numeric": "474"},
|
||||
{"alpha_2": "MR", "alpha_3": "MRT", "name": "Mauritania", "numeric": "478"},
|
||||
{"alpha_2": "MU", "alpha_3": "MUS", "name": "Mauritius", "numeric": "480"},
|
||||
{"alpha_2": "YT", "alpha_3": "MYT", "name": "Mayotte", "numeric": "175"},
|
||||
{"alpha_2": "MX", "alpha_3": "MEX", "name": "Mexico", "numeric": "484"},
|
||||
{"alpha_2": "FM", "alpha_3": "FSM", "name": "Micronesia, Federated States of", "numeric": "583"},
|
||||
{"alpha_2": "MD", "alpha_3": "MDA", "name": "Moldova, Republic of", "numeric": "498"},
|
||||
{"alpha_2": "MC", "alpha_3": "MCO", "name": "Monaco", "numeric": "492"},
|
||||
{"alpha_2": "MN", "alpha_3": "MNG", "name": "Mongolia", "numeric": "496"},
|
||||
{"alpha_2": "ME", "alpha_3": "MNE", "name": "Montenegro", "numeric": "499"},
|
||||
{"alpha_2": "MS", "alpha_3": "MSR", "name": "Montserrat", "numeric": "500"},
|
||||
{"alpha_2": "MA", "alpha_3": "MAR", "name": "Morocco", "numeric": "504"},
|
||||
{"alpha_2": "MZ", "alpha_3": "MOZ", "name": "Mozambique", "numeric": "508"},
|
||||
{"alpha_2": "MM", "alpha_3": "MMR", "name": "Myanmar", "numeric": "104"},
|
||||
{"alpha_2": "NA", "alpha_3": "NAM", "name": "Namibia", "numeric": "516"},
|
||||
{"alpha_2": "NR", "alpha_3": "NRU", "name": "Nauru", "numeric": "520"},
|
||||
{"alpha_2": "NP", "alpha_3": "NPL", "name": "Nepal", "numeric": "524"},
|
||||
{"alpha_2": "NL", "alpha_3": "NLD", "name": "Netherlands", "numeric": "528"},
|
||||
{"alpha_2": "NC", "alpha_3": "NCL", "name": "New Caledonia", "numeric": "540"},
|
||||
{"alpha_2": "NZ", "alpha_3": "NZL", "name": "New Zealand", "numeric": "554"},
|
||||
{"alpha_2": "NI", "alpha_3": "NIC", "name": "Nicaragua", "numeric": "558"},
|
||||
{"alpha_2": "NE", "alpha_3": "NER", "name": "Niger", "numeric": "562"},
|
||||
{"alpha_2": "NG", "alpha_3": "NGA", "name": "Nigeria", "numeric": "566"},
|
||||
{"alpha_2": "NU", "alpha_3": "NIU", "name": "Niue", "numeric": "570"},
|
||||
{"alpha_2": "NF", "alpha_3": "NFK", "name": "Norfolk Island", "numeric": "574"},
|
||||
{"alpha_2": "MK", "alpha_3": "MKD", "name": "North Macedonia", "numeric": "807"},
|
||||
{"alpha_2": "MP", "alpha_3": "MNP", "name": "Northern Mariana Islands", "numeric": "580"},
|
||||
{"alpha_2": "NO", "alpha_3": "NOR", "name": "Norway", "numeric": "578"},
|
||||
{"alpha_2": "OM", "alpha_3": "OMN", "name": "Oman", "numeric": "512"},
|
||||
{"alpha_2": "PK", "alpha_3": "PAK", "name": "Pakistan", "numeric": "586"},
|
||||
{"alpha_2": "PW", "alpha_3": "PLW", "name": "Palau", "numeric": "585"},
|
||||
{"alpha_2": "PS", "alpha_3": "PSE", "name": "Palestine, State of", "numeric": "275"},
|
||||
{"alpha_2": "PA", "alpha_3": "PAN", "name": "Panama", "numeric": "591"},
|
||||
{"alpha_2": "PG", "alpha_3": "PNG", "name": "Papua New Guinea", "numeric": "598"},
|
||||
{"alpha_2": "PY", "alpha_3": "PRY", "name": "Paraguay", "numeric": "600"},
|
||||
{"alpha_2": "PE", "alpha_3": "PER", "name": "Peru", "numeric": "604"},
|
||||
{"alpha_2": "PH", "alpha_3": "PHL", "name": "Philippines", "numeric": "608"},
|
||||
{"alpha_2": "PN", "alpha_3": "PCN", "name": "Pitcairn", "numeric": "612"},
|
||||
{"alpha_2": "PL", "alpha_3": "POL", "name": "Poland", "numeric": "616"},
|
||||
{"alpha_2": "PT", "alpha_3": "PRT", "name": "Portugal", "numeric": "620"},
|
||||
{"alpha_2": "PR", "alpha_3": "PRI", "name": "Puerto Rico", "numeric": "630"},
|
||||
{"alpha_2": "QA", "alpha_3": "QAT", "name": "Qatar", "numeric": "634"},
|
||||
{"alpha_2": "RO", "alpha_3": "ROU", "name": "Romania", "numeric": "642"},
|
||||
{"alpha_2": "RU", "alpha_3": "RUS", "name": "Russian Federation", "numeric": "643"},
|
||||
{"alpha_2": "RW", "alpha_3": "RWA", "name": "Rwanda", "numeric": "646"},
|
||||
{"alpha_2": "RE", "alpha_3": "REU", "name": "Réunion", "numeric": "638"},
|
||||
{"alpha_2": "BL", "alpha_3": "BLM", "name": "Saint Barthélemy", "numeric": "652"},
|
||||
{"alpha_2": "SH", "alpha_3": "SHN", "name": "Saint Helena, Ascension and Tristan da Cunha", "numeric": "654"},
|
||||
{"alpha_2": "KN", "alpha_3": "KNA", "name": "Saint Kitts and Nevis", "numeric": "659"},
|
||||
{"alpha_2": "LC", "alpha_3": "LCA", "name": "Saint Lucia", "numeric": "662"},
|
||||
{"alpha_2": "MF", "alpha_3": "MAF", "name": "Saint Martin (French part)", "numeric": "663"},
|
||||
{"alpha_2": "PM", "alpha_3": "SPM", "name": "Saint Pierre and Miquelon", "numeric": "666"},
|
||||
{"alpha_2": "VC", "alpha_3": "VCT", "name": "Saint Vincent and the Grenadines", "numeric": "670"},
|
||||
{"alpha_2": "WS", "alpha_3": "WSM", "name": "Samoa", "numeric": "882"},
|
||||
{"alpha_2": "SM", "alpha_3": "SMR", "name": "San Marino", "numeric": "674"},
|
||||
{"alpha_2": "ST", "alpha_3": "STP", "name": "Sao Tome and Principe", "numeric": "678"},
|
||||
{"alpha_2": "SA", "alpha_3": "SAU", "name": "Saudi Arabia", "numeric": "682"},
|
||||
{"alpha_2": "SN", "alpha_3": "SEN", "name": "Senegal", "numeric": "686"},
|
||||
{"alpha_2": "RS", "alpha_3": "SRB", "name": "Serbia", "numeric": "688"},
|
||||
{"alpha_2": "SC", "alpha_3": "SYC", "name": "Seychelles", "numeric": "690"},
|
||||
{"alpha_2": "SL", "alpha_3": "SLE", "name": "Sierra Leone", "numeric": "694"},
|
||||
{"alpha_2": "SG", "alpha_3": "SGP", "name": "Singapore", "numeric": "702"},
|
||||
{"alpha_2": "SX", "alpha_3": "SXM", "name": "Sint Maarten (Dutch part)", "numeric": "534"},
|
||||
{"alpha_2": "SK", "alpha_3": "SVK", "name": "Slovakia", "numeric": "703"},
|
||||
{"alpha_2": "SI", "alpha_3": "SVN", "name": "Slovenia", "numeric": "705"},
|
||||
{"alpha_2": "SB", "alpha_3": "SLB", "name": "Solomon Islands", "numeric": "090"},
|
||||
{"alpha_2": "SO", "alpha_3": "SOM", "name": "Somalia", "numeric": "706"},
|
||||
{"alpha_2": "ZA", "alpha_3": "ZAF", "name": "South Africa", "numeric": "710"},
|
||||
{"alpha_2": "GS", "alpha_3": "SGS", "name": "South Georgia and the South Sandwich Islands", "numeric": "239"},
|
||||
{"alpha_2": "SS", "alpha_3": "SSD", "name": "South Sudan", "numeric": "728"},
|
||||
{"alpha_2": "ES", "alpha_3": "ESP", "name": "Spain", "numeric": "724"},
|
||||
{"alpha_2": "LK", "alpha_3": "LKA", "name": "Sri Lanka", "numeric": "144"},
|
||||
{"alpha_2": "SD", "alpha_3": "SDN", "name": "Sudan", "numeric": "729"},
|
||||
{"alpha_2": "SR", "alpha_3": "SUR", "name": "Suriname", "numeric": "740"},
|
||||
{"alpha_2": "SJ", "alpha_3": "SJM", "name": "Svalbard and Jan Mayen", "numeric": "744"},
|
||||
{"alpha_2": "SE", "alpha_3": "SWE", "name": "Sweden", "numeric": "752"},
|
||||
{"alpha_2": "CH", "alpha_3": "CHE", "name": "Switzerland", "numeric": "756"},
|
||||
{"alpha_2": "SY", "alpha_3": "SYR", "name": "Syrian Arab Republic", "numeric": "760"},
|
||||
{"alpha_2": "TW", "alpha_3": "TWN", "name": "Taiwan, Province of China", "numeric": "158"},
|
||||
{"alpha_2": "TJ", "alpha_3": "TJK", "name": "Tajikistan", "numeric": "762"},
|
||||
{"alpha_2": "TZ", "alpha_3": "TZA", "name": "Tanzania, United Republic of", "numeric": "834"},
|
||||
{"alpha_2": "TH", "alpha_3": "THA", "name": "Thailand", "numeric": "764"},
|
||||
{"alpha_2": "TL", "alpha_3": "TLS", "name": "Timor-Leste", "numeric": "626"},
|
||||
{"alpha_2": "TG", "alpha_3": "TGO", "name": "Togo", "numeric": "768"},
|
||||
{"alpha_2": "TK", "alpha_3": "TKL", "name": "Tokelau", "numeric": "772"},
|
||||
{"alpha_2": "TO", "alpha_3": "TON", "name": "Tonga", "numeric": "776"},
|
||||
{"alpha_2": "TT", "alpha_3": "TTO", "name": "Trinidad and Tobago", "numeric": "780"},
|
||||
{"alpha_2": "TN", "alpha_3": "TUN", "name": "Tunisia", "numeric": "788"},
|
||||
{"alpha_2": "TR", "alpha_3": "TUR", "name": "Turkey", "numeric": "792"},
|
||||
{"alpha_2": "TM", "alpha_3": "TKM", "name": "Turkmenistan", "numeric": "795"},
|
||||
{"alpha_2": "TC", "alpha_3": "TCA", "name": "Turks and Caicos Islands", "numeric": "796"},
|
||||
{"alpha_2": "TV", "alpha_3": "TUV", "name": "Tuvalu", "numeric": "798"},
|
||||
{"alpha_2": "UG", "alpha_3": "UGA", "name": "Uganda", "numeric": "800"},
|
||||
{"alpha_2": "UA", "alpha_3": "UKR", "name": "Ukraine", "numeric": "804"},
|
||||
{"alpha_2": "AE", "alpha_3": "ARE", "name": "United Arab Emirates", "numeric": "784"},
|
||||
{"alpha_2": "GB", "alpha_3": "GBR", "name": "United Kingdom", "numeric": "826"},
|
||||
{"alpha_2": "US", "alpha_3": "USA", "name": "United States", "numeric": "840"},
|
||||
{"alpha_2": "UM", "alpha_3": "UMI", "name": "United States Minor Outlying Islands", "numeric": "581"},
|
||||
{"alpha_2": "UY", "alpha_3": "URY", "name": "Uruguay", "numeric": "858"},
|
||||
{"alpha_2": "UZ", "alpha_3": "UZB", "name": "Uzbekistan", "numeric": "860"},
|
||||
{"alpha_2": "VU", "alpha_3": "VUT", "name": "Vanuatu", "numeric": "548"},
|
||||
{"alpha_2": "VE", "alpha_3": "VEN", "name": "Venezuela, Bolivarian Republic of", "numeric": "862"},
|
||||
{"alpha_2": "VN", "alpha_3": "VNM", "name": "Viet Nam", "numeric": "704"},
|
||||
{"alpha_2": "VG", "alpha_3": "VGB", "name": "Virgin Islands, British", "numeric": "092"},
|
||||
{"alpha_2": "VI", "alpha_3": "VIR", "name": "Virgin Islands, U.S.", "numeric": "850"},
|
||||
{"alpha_2": "WF", "alpha_3": "WLF", "name": "Wallis and Futuna", "numeric": "876"},
|
||||
{"alpha_2": "EH", "alpha_3": "ESH", "name": "Western Sahara", "numeric": "732"},
|
||||
{"alpha_2": "YE", "alpha_3": "YEM", "name": "Yemen", "numeric": "887"},
|
||||
{"alpha_2": "ZM", "alpha_3": "ZMB", "name": "Zambia", "numeric": "894"},
|
||||
{"alpha_2": "ZW", "alpha_3": "ZWE", "name": "Zimbabwe", "numeric": "716"},
|
||||
{"alpha_2": "AX", "alpha_3": "ALA", "name": "Åland Islands", "numeric": "248"}
|
||||
]
|
||||
}
|
||||
182
openbb_platform/core/openbb_core/provider/utils/country_utils.py
Normal file
182
openbb_platform/core/openbb_core/provider/utils/country_utils.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""Utilities for standardizing country inputs across providers using ISO 3166 standards.
|
||||
|
||||
This module provides a Country type that inherits from str for type checker compatibility
|
||||
while providing full access to ISO 3166 country data via a static dataset.
|
||||
|
||||
Providers can access alpha_2, alpha_3, name, and numeric properties as needed.
|
||||
|
||||
References:
|
||||
- ISO 3166-1: https://en.wikipedia.org/wiki/ISO_3166-1
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pydantic_core import core_schema
|
||||
|
||||
|
||||
def _load_country_data() -> dict[str, dict[str, str]]:
|
||||
"""Load country data from JSON and build lookup indices.
|
||||
|
||||
Returns a dict with lookup keys (alpha_2, alpha_3, name variants) mapping to country data.
|
||||
"""
|
||||
data_path = Path(__file__).parent / "country_data.json"
|
||||
with open(data_path, encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
lookup: dict[str, dict[str, str]] = {}
|
||||
for country in data["countries"]:
|
||||
# Index by alpha_2 (case-insensitive)
|
||||
lookup[country["alpha_2"].upper()] = country
|
||||
lookup[country["alpha_2"].lower()] = country
|
||||
|
||||
# Index by alpha_3 (case-insensitive)
|
||||
lookup[country["alpha_3"].upper()] = country
|
||||
lookup[country["alpha_3"].lower()] = country
|
||||
|
||||
# Index by name (case-insensitive)
|
||||
name_lower = country["name"].lower()
|
||||
lookup[name_lower] = country
|
||||
lookup[country["name"]] = country
|
||||
|
||||
# Index by snake_case name
|
||||
snake_name = name_lower.replace(" ", "_").replace(",", "").replace("'", "")
|
||||
lookup[snake_name] = country
|
||||
|
||||
return lookup
|
||||
|
||||
|
||||
# Load once at module import - no decompression needed, just simple JSON parse
|
||||
_COUNTRY_LOOKUP = _load_country_data()
|
||||
|
||||
|
||||
class Country(str):
|
||||
"""Country string type with full ISO 3166 data access.
|
||||
|
||||
Inherits from str (storing alpha_2 code) for type checker compatibility
|
||||
while providing access to alpha_2, alpha_3, name, and numeric properties.
|
||||
|
||||
Accepts:
|
||||
- ISO 3166-1 alpha-2 codes (e.g., "US", "us")
|
||||
- ISO 3166-1 alpha-3 codes (e.g., "USA", "usa")
|
||||
- Full country names (e.g., "United States")
|
||||
- lower_snake_case names (e.g., "united_states")
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> c = Country("united_states")
|
||||
>>> str(c)
|
||||
'US'
|
||||
>>> c.alpha_2
|
||||
'US'
|
||||
>>> c.alpha_3
|
||||
'USA'
|
||||
>>> c.name
|
||||
'United States'
|
||||
"""
|
||||
|
||||
_country_data: dict[str, str]
|
||||
|
||||
def __new__(cls, value: Any) -> "Country":
|
||||
"""Create a new Country instance.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : Any
|
||||
Country input (alpha-2, alpha-3, name, or lower_snake_case).
|
||||
|
||||
Returns
|
||||
-------
|
||||
Country
|
||||
A Country instance storing the alpha_2 code as its string value.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the country cannot be resolved.
|
||||
"""
|
||||
country_data = (
|
||||
value._country_data
|
||||
if isinstance(value, Country)
|
||||
else cls._lookup_country(value)
|
||||
)
|
||||
|
||||
# Create str instance with the alpha_2 code
|
||||
instance = super().__new__(cls, country_data["alpha_2"])
|
||||
# Store the country data dict
|
||||
instance._country_data = country_data
|
||||
return instance
|
||||
|
||||
@staticmethod
|
||||
def _lookup_country(value: Any) -> dict[str, str]:
|
||||
"""Look up a country from various input formats.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : Any
|
||||
Country input to look up.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict[str, str]
|
||||
The country data dictionary with alpha_2, alpha_3, name, numeric.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the country cannot be found.
|
||||
"""
|
||||
val = str(value).strip()
|
||||
|
||||
# Convert lower_snake_case to lookup key
|
||||
if "_" in val:
|
||||
val = val.replace("_", " ")
|
||||
|
||||
# Try direct lookup
|
||||
lookup_key = val.lower()
|
||||
if lookup_key in _COUNTRY_LOOKUP:
|
||||
return _COUNTRY_LOOKUP[lookup_key]
|
||||
|
||||
# Try original case
|
||||
if val in _COUNTRY_LOOKUP:
|
||||
return _COUNTRY_LOOKUP[val]
|
||||
|
||||
raise ValueError(
|
||||
f"Invalid country: '{value}'. "
|
||||
"Accepts ISO 3166-1 alpha-2 codes (e.g., 'US'), "
|
||||
"alpha-3 codes (e.g., 'USA'), "
|
||||
"or country names (e.g., 'United States', 'united_states')."
|
||||
)
|
||||
|
||||
@property
|
||||
def alpha_2(self) -> str:
|
||||
"""ISO 3166-1 alpha-2 code (e.g., 'US')."""
|
||||
return self._country_data["alpha_2"]
|
||||
|
||||
@property
|
||||
def alpha_3(self) -> str:
|
||||
"""ISO 3166-1 alpha-3 code (e.g., 'USA')."""
|
||||
return self._country_data["alpha_3"]
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""The country's official designation (e.g., 'United States')."""
|
||||
return self._country_data["name"]
|
||||
|
||||
@property
|
||||
def numeric(self) -> str | None:
|
||||
"""ISO 3166-1 numeric code (e.g., '840'), if available."""
|
||||
return self._country_data.get("numeric")
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> Any:
|
||||
"""Return the Pydantic core schema for validation."""
|
||||
return core_schema.no_info_after_validator_function(
|
||||
cls,
|
||||
core_schema.str_schema(),
|
||||
)
|
||||
|
||||
|
||||
# Backwards-compatible alias
|
||||
CountryParam = Country
|
||||
@@ -0,0 +1,99 @@
|
||||
"""Tests for country_utils module."""
|
||||
|
||||
import pytest
|
||||
from openbb_core.provider.utils.country_utils import Country, CountryParam
|
||||
from pydantic import BaseModel, ValidationError
|
||||
|
||||
|
||||
class CountryTestModel(BaseModel):
|
||||
"""Test model for Country validation."""
|
||||
|
||||
country: Country | None = None
|
||||
|
||||
|
||||
class TestCountry:
|
||||
"""Tests for Country type."""
|
||||
|
||||
def test_iso_alpha2_code(self):
|
||||
"""ISO alpha-2 codes should be accepted."""
|
||||
model = CountryTestModel(country="US")
|
||||
assert str(model.country) == "US"
|
||||
assert model.country.alpha_2 == "US"
|
||||
assert model.country.alpha_3 == "USA"
|
||||
|
||||
model = CountryTestModel(country="us")
|
||||
assert str(model.country) == "US"
|
||||
|
||||
model = CountryTestModel(country="DE")
|
||||
assert str(model.country) == "DE"
|
||||
assert model.country.alpha_3 == "DEU"
|
||||
|
||||
def test_iso_alpha3_code(self):
|
||||
"""ISO alpha-3 codes should be accepted."""
|
||||
model = CountryTestModel(country="USA")
|
||||
assert str(model.country) == "US"
|
||||
assert model.country.alpha_2 == "US"
|
||||
assert model.country.alpha_3 == "USA"
|
||||
|
||||
model = CountryTestModel(country="deu")
|
||||
assert str(model.country) == "DE"
|
||||
|
||||
def test_country_name(self):
|
||||
"""Full country names should resolve to alpha-2 codes."""
|
||||
model = CountryTestModel(country="United States")
|
||||
assert str(model.country) == "US"
|
||||
assert model.country.name == "United States"
|
||||
|
||||
model = CountryTestModel(country="Germany")
|
||||
assert str(model.country) == "DE"
|
||||
assert model.country.name == "Germany"
|
||||
|
||||
model = CountryTestModel(country="United Kingdom")
|
||||
assert str(model.country) == "GB"
|
||||
|
||||
def test_lower_snake_case(self):
|
||||
"""lower_snake_case names should be accepted."""
|
||||
model = CountryTestModel(country="united_states")
|
||||
assert str(model.country) == "US"
|
||||
|
||||
model = CountryTestModel(country="united_kingdom")
|
||||
assert str(model.country) == "GB"
|
||||
|
||||
def test_none_value(self):
|
||||
"""None values should pass through."""
|
||||
model = CountryTestModel(country=None)
|
||||
assert model.country is None
|
||||
|
||||
def test_invalid_country(self):
|
||||
"""Invalid country names should raise ValidationError."""
|
||||
with pytest.raises(ValidationError):
|
||||
CountryTestModel(country="InvalidCountry")
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
CountryTestModel(country="XX") # Invalid ISO code
|
||||
|
||||
def test_country_properties(self):
|
||||
"""Country should expose all ISO properties."""
|
||||
c = Country("US")
|
||||
assert c.alpha_2 == "US"
|
||||
assert c.alpha_3 == "USA"
|
||||
assert c.name == "United States"
|
||||
assert c.numeric == "840"
|
||||
|
||||
def test_str_inheritance(self):
|
||||
"""Country should behave like a string."""
|
||||
c = Country("US")
|
||||
assert isinstance(c, str)
|
||||
assert c == "US"
|
||||
assert c.lower() == "us"
|
||||
assert f"Country: {c}" == "Country: US"
|
||||
|
||||
def test_model_dump(self):
|
||||
"""model_dump should serialize to string."""
|
||||
model = CountryTestModel(country="united_states")
|
||||
dumped = model.model_dump()
|
||||
assert dumped["country"] == "US"
|
||||
|
||||
def test_country_param_alias(self):
|
||||
"""CountryParam should be an alias for Country."""
|
||||
assert CountryParam is Country
|
||||
@@ -9,6 +9,7 @@ from openbb_core.provider.standard_models.equity_screener import (
|
||||
EquityScreenerData,
|
||||
EquityScreenerQueryParams,
|
||||
)
|
||||
from openbb_core.provider.utils.country_utils import Country
|
||||
from openbb_core.provider.utils.errors import EmptyDataError
|
||||
from openbb_fmp.utils.definitions import (
|
||||
Countries,
|
||||
@@ -121,8 +122,10 @@ class FMPEquityScreenerQueryParams(EquityScreenerQueryParams):
|
||||
default=None,
|
||||
description="Filter by industry.",
|
||||
)
|
||||
country: Countries | None = Field(
|
||||
default=None, description="Filter by country, as a two-letter country code."
|
||||
country: Country | None = Field(
|
||||
default=None,
|
||||
description="Filter by country. Accepts ISO 3166-1 alpha-2 codes (e.g., 'US', 'DE'), "
|
||||
"alpha-3 codes (e.g., 'USA'), or country names (e.g., 'United States', 'united_states').",
|
||||
)
|
||||
exchange: Exchanges | None = Field(default=None, description="Filter by exchange.")
|
||||
is_etf: bool | None = Field(
|
||||
@@ -154,6 +157,22 @@ class FMPEquityScreenerQueryParams(EquityScreenerQueryParams):
|
||||
raise ValueError(f"Industry must be one of {', '.join(industries)}")
|
||||
return v
|
||||
|
||||
@field_validator("country", mode="after")
|
||||
@classmethod
|
||||
def _validate_country(cls, v):
|
||||
"""Validate country is supported by FMP."""
|
||||
if v is None:
|
||||
return v
|
||||
# Country stores alpha_2 in uppercase, FMP expects lowercase
|
||||
country_code = v.alpha_2.lower()
|
||||
valid_countries = list(Countries.__args__)
|
||||
if country_code not in valid_countries:
|
||||
raise ValueError(
|
||||
f"Country '{v.name}' ({v.alpha_2}) is not supported by FMP. "
|
||||
f"Valid options: {', '.join(sorted(valid_countries)[:20])}..."
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
class FMPEquityScreenerData(EquityScreenerData):
|
||||
"""FMP Equity Screener Data."""
|
||||
|
||||
Reference in New Issue
Block a user