diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 9ddc4da..2a564a3 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -43,6 +43,7 @@ jobs: python3 -m pip install setuptools wheel twine pip install flake8 pytest pip install pytest + pip install codecov pip install pytest-cov pip install bandit pip install safety @@ -78,7 +79,13 @@ jobs: run: | echo "Testing using unittest..." python3 -m unittest discover tests -b - + + #create coverage report using pytest package + - name: Generate Coverage Report + run: | + pytest --cov=./ --cov-report=xml + codecov + #upload to Code Coverage, only if matrix python version is 3.9 - name: Upload Coverage Report to Codecov if: ${{ matrix.python-version == '3.9' }} @@ -86,7 +93,7 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: - flags: iso3166_2_workflow + flags: iso3166_2_workflow #upload test artifacts to workflow - name: Upload Test Artifacts diff --git a/.github/workflows/deploy_pypi.yml b/.github/workflows/deploy_pypi.yml index dddc032..52b853a 100644 --- a/.github/workflows/deploy_pypi.yml +++ b/.github/workflows/deploy_pypi.yml @@ -28,18 +28,20 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python3 -m pip install setuptools wheel twine python3 setup.py install + pip install build - # Build package and upload to PyPI - - name: Build and upload to PyPI + # Build package + - name: Build package run: | - python3 setup.py sdist bdist_wheel - twine check dist/* - twine upload dist/* --verbose - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + python -m build + + # publish to pypi + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} #sleep for 30 seconds to ensure that distribution package has finised uploading to Test PyPI - name: Wait / Sleep @@ -50,6 +52,6 @@ jobs: # download package of iso3166_2 from PYPI server to ensure it uploaded correctly - name: Install iso3166_2 from PyPI run: | - pip install iso3166_updates --upgrade - echo -e "import iso3166_updates as iso3166_2" | python3 + pip install iso3166_2 --upgrade + echo -e "import iso3166_2 as iso3166_2" | python3 echo "iso3166_2 successfully installed" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 670a18d..95574b1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__/ old/ test-iso3166_2/ iso3166-2-data-archive/ +get_iso3166_2.py .DS_Store .vscode diff --git a/.readthedocs.yml b/.readthedocs.yml index 25c0eeb..8b235e7 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,8 +1,5 @@ -# .readthedocs.yaml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +# Read the Docs configuration file: see https://docs.readthedocs.io/en/stable/config-file/v2.html for details -# Required version: 2 # Set the OS, Python version and other tools you might need @@ -10,10 +7,6 @@ build: os: ubuntu-22.04 tools: python: "3.12" - # You can also specify other tool versions: - # nodejs: "19" - # rust: "1.64" - # golang: "1.19" # Build documentation in the "docs/" directory with Sphinx sphinx: @@ -25,8 +18,7 @@ formats: - pdf # - epub -# Optional but recommended, declare the Python requirements required -# to build your documentation +# Optional but recommended, declare the Python requirements required to build your documentation # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: diff --git a/API.md b/API.md index d43165d..68f1b72 100644 --- a/API.md +++ b/API.md @@ -9,16 +9,22 @@ The main API endpoint is: The other endpoints available in the API are: * https://iso3166-2-api.vercel.app/api/all -* https://iso3166-2-api.vercel.app/api/alpha2/ -* https://iso3166-2-api.vercel.app/api/name/ +* https://iso3166-2-api.vercel.app/api/alpha/ +* https://iso3166-2-api.vercel.app/api/country_name/ +* https://iso3166-2-api.vercel.app/api/subdivision/ +* https://iso3166-2-api.vercel.app/api/name/ -Three paths/endpoints are available in the API - `/api/all`, `/api/alpha2` and `/api/name`. +Five paths/endpoints are available in the API - `/api/all`, `/api/alpha`, `/api/country_name`, `/api/subdivision` and `/api/name`. * The `/api/all` path/endpoint returns all of the ISO 3166 subdivision data for all countries. -* The `/api/alpha2` endpoint accepts the 2 letter alpha-2 country code appended to the path/endpoint e.g. /api/alpha2/JP. A single alpha-2 code or list of them can be passed to the API e.g. /api/alpha2/FR,DE,HU,ID,MA. For redundancy, the 3 letter alpha-3 counterpart for each country's alpha-2 code can also be appended to the path e.g. /api/alpha2/FRA,DEU,HUN,IDN,MAR. If an invalid alpha-2 code is input then an error will be returned. +* The `/api/alpha` endpoint accepts the 2 letter alpha-2, 3 letter alpha-3 and or numeric ISO 3166-1 country codes appended to the path/endpoint e.g. `/api/alpha/JP`. A single alpha-2, alpha-3 or numeric code or list of them can be passed to the API e.g. `/api/alpha/FR,DE,HU,ID,MA`, `/api/alpha/FRA,DEU,HUN,IDN,MAR` and `/api/alpha/428,504,638`. If an invalid country code is input then an error will be returned. -* The `/api/name` endpoint accepts the country/territory name as it is most commonly known in english, according to the ISO 3166-1. The name can similarly be appended to the **name** path/endpoint e.g. /api/name/Denmark. A single country name or list of them can be passed into the API e.g. /name/France,Moldova,Benin. A closeness function is utilised so the most approximate name from the input will be used e.g. Sweden will be used if input is /api/name/Swede. If no country is found from the closeness function or an invalid name is input then an error will be returned. +* The `/api/country_name` endpoint accepts the country/territory name as it is most commonly known in english, according to the ISO 3166-1 e.g. `/api/country_name/Denmark`. A single country name or list of them can be passed into the API e.g. `/api/country_name/France,Moldova,Benin`. A closeness function is utilised so the most approximate name from the input will be used e.g. Sweden will be used if input is `/api/country_name/Swede`. If no country is found from the closeness function or an invalid name is input then an error will be returned. + +* The `/api/subdivision` endpoint accepts the ISO 3166-2 subdivision codes, e.g `/api/subdivision/GB-ABD`. You can also input a list of subdivision codes from the same and or different countries and the data for each will be returned e.g `/api/subdivision/IE-MO,FI-17,RO-AG`. If the input subdivision code is not in the correct format then an error will be raised. Similarly if an invalid subdivision code that doesn't exist is input then an error will be raised. + +* The `/api/name/` endpoint accepts the ISO 3166-2 subdivision names, e.g `/api/name/Derry`. You can also input a list of subdivision name from the same or different countries and the data for each will be returned e.g `/api/name/Paris,Frankfurt,Rimini`. A closeness function is utilised to find the matching subdivision name, if no exact name match found then the most approximate subdivisions will be returned. Some subdivisions may have the same name, in this case eahc subdivision and its data will be returned e.g `/api/name/Saint George,Sucre`. This endpoint also has the likeness score (`?likeness=`) query string parameter that can be appended to the URL. This can be set between 1 - 100, representing a % of likeness to the input name the return subdivisions should be, e.g: a likeness score of 90 will return fewer potential matches whose name only match to a high degree compared to a score of 10 which will create a larger search space, thus returning more potential subdivision matches. A default likeness of 100 (exact match) is used, if no match found then this is reduced to 90. If an invalid subdivision name that doesn't match any is input then an error will be raised. * The main API endpoint (`/` or `/api`) will return the homepage and API documentation. @@ -34,7 +40,7 @@ Get ALL ISO 3166-2 subdivision data for ALL countries ### Response HTTP/2 200 content-type: application/json - date: Tue, 20 Dec 2022 17:29:39 GMT + date: Wed, 20 Dec 2023 17:29:39 GMT server: Vercel content-length: 837958 @@ -44,9 +50,7 @@ Get ALL ISO 3166-2 subdivision data for ALL countries ```python import requests -base_url = "https://iso3166-2-api.vercel.app/api/" - -request_url = base_url + "all" +request_url = "https://iso3166-2-api.vercel.app/api/all" all_request = requests.get(request_url) all_request.json() @@ -63,59 +67,279 @@ function getData() { var data = JSON.parse(this.response) ``` -Get all ISO 3166-2 subdivision data for a specific country, using its 2 letter alpha-2 code e.g FR, DE, HN ----------------------------------------------------------------------------------------------------------- - +Get all ISO 3166-2 subdivision data for a specific country, using its 2 letter alpha-2 code e.g FR, DE +------------------------------------------------------------------------------------------------------ ### Request -`GET /api/alpha2/FR` +`GET /api/alpha/FR` - curl -i https://iso3166-2-api.vercel.app/api/alpha2/FR + curl -i https://iso3166-2-api.vercel.app/api/alpha/FR ### Response HTTP/2 200 content-type: application/json - date: Tue, 20 Dec 2022 17:30:27 GMT + date: Wed, 20 Dec 2023 17:30:27 GMT server: Vercel content-length: 26298 {"FR":{"FR-01":{...}}} ### Request -`GET /api/alpha2/DE` +`GET /api/alpha/DE` - curl -i https://iso3166-2-api.vercel.app/api/alpha2/DE + curl -i https://iso3166-2-api.vercel.app/api/alpha/DE ### Response HTTP/2 200 content-type: application/json - date: Tue, 20 Dec 2022 17:31:19 GMT + date: Wed, 20 Dec 2023 17:31:19 GMT server: Vercel content-length: 3053 {"DE":{"DE-BB":{...}}} +### Python +```python +import requests + +base_url = "https://iso3166-2-api.vercel.app/api/alpha/" +input_alpha2 = "FR" #DE + +request_url = base_url + input_alpha2 + +all_request = requests.get(request_url) +all_request.json() +``` + +### Javascript +```javascript +let input_alpha2 = "FR"; //DE + +function getData() { + const response = + await fetch(`https://iso3166-2-api.vercel.app/api/alpha/${input_alpha2}`); + const data = await response.json() +} + +// Begin accessing JSON data here +var data = JSON.parse(this.response) +``` + +Get all ISO 3166-2 subdivision data for a specific country, using its 3 letter alpha-3 code e.g CZE, HRV +-------------------------------------------------------------------------------------------------------- + +### Request +`GET /api/alpha/CZE` + + curl -i https://iso3166-2-api.vercel.app/api/alpha/CZE + +### Response + HTTP/2 200 + content-type: application/json + date: Wed, 20 Dec 2023 17:42:24 GMT + server: Vercel + content-length: 14266 + + {"CZ":{"CZ-10":{"..."}}} + +### Request +`GET /api/alpha/HRV` + + curl -i https://iso3166-2-api.vercel.app/api/alpha/HRV + +### Response + HTTP/2 200 + content-type: application/json + date: Wed, 20 Dec 2023 17:46:12 GMT + server: Vercel + content-length: 5456 + + {"HR":{"HR-01":{"..."}}} + +### Python +```python +import requests + +base_url = "https://iso3166-2-api.vercel.app/api/alpha/" +input_alpha3 = "CZE" #HRV + +request_url = base_url + input_alpha3 + +all_request = requests.get(request_url) +all_request.json() +``` + +### Javascript +```javascript +let input_alpha3 = "CZE"; //HRV + +function getData() { + const response = + await fetch(`https://iso3166-2-api.vercel.app/api/alpha/${input_alpha3}`); + const data = await response.json() +} + +// Begin accessing JSON data here +var data = JSON.parse(this.response) +``` + +Get all ISO 3166-2 subdivision data for a specific country, using its numeric code e.g 268, 398 +----------------------------------------------------------------------------------------------- + +### Request +`GET /api/alpha/268` + + curl -i https://iso3166-2-api.vercel.app/api/alpha/268 + +### Response + HTTP/2 200 + content-type: application/json + date: Fri, 22 Dec 2023 18:20:14 GMT + server: Vercel + content-length: 1899 + + {"GE":{"GE-AB":{"..."}}} + +### Request +`GET /api/alpha/398` + + curl -i https://iso3166-2-api.vercel.app/api/alpha/398 + +### Response + HTTP/2 200 + content-type: application/json + date: Fri, 22 Dec 2023 19:40:10 GMT + server: Vercel + content-length: 2922 + + {"KZ":{"KZ-10":{"..."}}} + +### Python +```python +import requests + +base_url = "https://iso3166-2-api.vercel.app/api/alpha/" +input_numeric = "268" #398 + +request_url = base_url + input_numeric + +all_request = requests.get(request_url) +all_request.json() +``` + +### Javascript +```javascript +let input_numeric = "268"; //398 + +function getData() { + const response = + await fetch(`https://iso3166-2-api.vercel.app/api/alpha/${input_numeric}`); + const data = await response.json() +} + +// Begin accessing JSON data here +var data = JSON.parse(this.response) +``` + +Get all ISO 3166-2 subdivision data for a specific subdivision, using its subdivision code e.g LV-007, PA-3, ZA-NC +------------------------------------------------------------------------------------------------------------------ + +### Request +`GET /api/subdivision/LV-007` + + curl -i https://iso3166-2-api.vercel.app/api/subdivision/LV-007 + +### Response + HTTP/2 200 + content-type: application/json + date: Mon, 29 Jan 2024 11:25:52 GMT + server: Vercel + content-length: 244 + + {"LV-007":{"flagUrl":"https://github.com/amckenna41/...}} + +### Request +`GET /api/subdivision/PA-3` + + curl -i https://iso3166-2-api.vercel.app/api/subdivision/PA-3 + +### Response + HTTP/2 200 + content-type: application/json + date: Mon, 29 Jan 2024 11:28:02 GMT + server: Vercel + content-length: 214 + + {"PA-3":{"flagUrl":"https://github.com/amckenna41...}} + +### Request +`GET /api/subdivision/ZA-NC` + + curl -i https://iso3166-2-api.vercel.app/api/subdivision/ZA-NC + +### Response + HTTP/2 200 + content-type: application/json + date: Mon, 29 Jan 2024 11:37:04 GMT + server: Vercel + content-length: 225 + + {"ZA-NC":{"flagUrl":"https://github.com/amckenna41...}} + + +Get all ISO 3166-2 subdivision data for a specific subdivision, using its subdivision name e.g Treviso, Nordland, Musandam +-------------------------------------------------------------------------------------------------------------------------- + +### Request +`GET /api/name/Treviso` + + curl -i https://iso3166-2-api.vercel.app/api/name/Treviso + +### Response + HTTP/2 200 + content-type: application/json + date: Mon, 29 Jan 2024 13:02:10 GMT + server: Vercel + content-length: 215 + + {"IT-TV":{"flagUrl":"}} + +### Request +`GET /api/name/Nordland` + + curl -i https://iso3166-2-api.vercel.app/api/name/Nordland + +### Response + HTTP/2 200 + content-type: application/json + date: Mon, 29 Jan 2024 13:07:01 GMT + server: Vercel + content-length: 212 + + {"NO-18":{"flagUrl":"}} + ### Request -`GET /api/alpha2/HN` +`GET /api/name/Musandam` - curl -i https://iso3166-2-api.vercel.app/api/alpha2/HN + curl -i https://iso3166-2-api.vercel.app/api/name/Musandam ### Response HTTP/2 200 content-type: application/json - date: Tue, 20 Dec 2022 17:31:53 GMT + date: Mon, 29 Jan 2024 13:11:09 GMT server: Vercel - content-length: 2708 + content-length: 132 - {"HN":{"HN-AT":{...}}} + {"OM-MU":{"flagUrl":}} ### Python ```python import requests -base_url = "https://iso3166-2-api.vercel.app/api/" -input_alpha2 = "FR" #DE, HN +base_url = "https://iso3166-2-api.vercel.app/api/name/" +input_subdivision_name = "Treviso" #Nordland, Musandam -request_url = base_url + f"alpha2/{input_alpha2}" +request_url = base_url + input_subdivision_name +params={"likeness":"90"} all_request = requests.get(request_url) all_request.json() @@ -123,11 +347,12 @@ all_request.json() ### Javascript ```javascript -let input_alpha2 = "FR"; //DE, HN +let input_subdivision_name = "Treviso"; //Nordland, Musandam +let params = {"likeness": "90"} function getData() { const response = - await fetch(`https://iso3166-2-api.vercel.app/api/alpha2/${input_alpha2}`); + await fetch(`https://iso3166-updates.com/api/name/${input_subdivision_name}`, params); const data = await response.json() } @@ -139,42 +364,42 @@ Get all ISO 3166-2 subdivision data for a specific country, using country name, ------------------------------------------------------------------------------------------------------------------- ### Request -`GET /api/name/Tajikistan` +`GET /api/country_name/Tajikistan` - curl -i https://iso3166-2-api.vercel.app/api/name/Tajikistan + curl -i https://iso3166-2-api.vercel.app/api/country_name/Tajikistan ### Response HTTP/2 200 content-type: application/json - date: Tue, 20 Dec 2022 17:40:19 GMT + date: Sat, 23 Dec 2023 14:01:19 GMT server: Vercel content-length: 701 {"TJ":{"TJ-DU":{...}}} ### Request -`GET /api/name/Seychelles` +`GET /api/country_name/Seychelles` - curl -i https://iso3166-2-api.vercel.app/api/name/Seychelles + curl -i https://iso3166-2-api.vercel.app/api/country_name/Seychelles ### Response HTTP/2 200 content-type: application/json - date: Tue, 20 Dec 2022 17:41:53 GMT + date: Sat, 23 Dec 2023 14:15:53 GMT server: Vercel content-length: 5085 {"SC":{"SC-01":{...}}} ### Request -`GET /api/name/Uganda` +`GET /api/country_name/Uganda` - curl -i https://iso3166-2-api.vercel.app/api/name/Uganda + curl -i https://iso3166-2-api.vercel.app/api/country_name/Uganda ### Response HTTP/2 200 content-type: application/json - date: Tue, 21 Dec 2022 19:43:19 GMT + date: Sat, 23 Dec 2023 14:17:39 GMT server: Vercel content-length: 14965 @@ -184,10 +409,10 @@ Get all ISO 3166-2 subdivision data for a specific country, using country name, ```python import requests -base_url = "https://iso3166-2-api.vercel.app/api/" +base_url = "https://iso3166-2-api.vercel.app/api/country_name/" input_name = "Tajikistan" #Seychelles, Uganda -request_url = base_url + f"name/{input_name}" +request_url = base_url + input_name all_request = requests.get(request_url) all_request.json() @@ -199,7 +424,7 @@ let input_name = "Tajikistan"; //Seychelles, Uganda function getData() { const response = - await fetch(`https://iso3166-updates.com/api/name/${input_name}`); + await fetch(`https://iso3166-updates.com/api/country_name/${input_name}`); const data = await response.json() } diff --git a/LICENSE b/LICENSE index b401227..9d1d5d6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,27 +1,21 @@ -Copyright (c) 2023, AJ McKenna -All rights reserved. +MIT License -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +Copyright (c) 2023 AJ -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 4124974..3960a0a 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,10 @@ [![CircleCI](https://dl.circleci.com/status-badge/img/gh/amckenna41/iso3166-2/tree/main.svg?style=svg&circle-token=b9d41c530558587fb44ade899c532158d885b193)](https://dl.circleci.com/status-badge/redirect/gh/amckenna41/iso3166-2/tree/main) [![PythonV](https://img.shields.io/pypi/pyversions/iso3166-2?logo=2)](https://pypi.org/project/iso3166-2/) [![Platforms](https://img.shields.io/badge/platforms-linux%2C%20macOS%2C%20Windows-green)](https://pypi.org/project/iso3166-2/) +[![Documentation Status](https://readthedocs.org/projects/iso3166-2/badge/?version=latest)](https://iso3166-2.readthedocs.io/en/latest/?badge=latest) [![License: MIT](https://img.shields.io/github/license/amckenna41/iso3166-2)](https://opensource.org/licenses/MIT) [![Issues](https://img.shields.io/github/issues/amckenna41/iso3166-2)](https://github.com/amckenna41/iso3166-2/issues) - +[![Size](https://img.shields.io/github/repo-size/amckenna41/iso3166-2)](https://github.com/amckenna41/iso3166-2) @@ -25,19 +26,27 @@ Table of Contents ----------------- - * [Introduction](#introduction) - * [Latest Updates](#latestupdates) - * [API](#api) - * [Requirements](#requirements) - * [Installation](#installation) - * [Usage](#usage) - * [Issues](#issuesorcontributing) - * [Contact](#contact) - * [References](#references) +- [Introduction](#introduction) +- [Latest Updates](#latest-updates) +- [API](#api) +- [Requirements](#requirements) +- [Installation](#installation) +- [Usage](#usage) +- [Documentation](#documentation) +- [Usage](#usage-iso3166_2_scriptsupdate_subdivisionspy) +- [Directories](#directories) +- [Issues or Contributing](#issues-or-contributing) +- [Contact](#contact) +- [References](#references) +- [Support](#support) Introduction ------------ -`iso3166-2` is a lightweight custom-built Python package, and accompanying API, that can be used to access all of the world's ISO 3166-2 subdivision data. Here, subdivision can be used interchangably with regions/states/provinces etc. Currently, the package and API supports data from 250 countries/territories, according to the ISO 3166-1 standard. The software uses another custom-built Python package called [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates/tree/main) to ensure all the subdivision data is accurate, reliable and up-to-date. The full list of subdivision data attributes supported are: +The International Organisation for Standards defines codes for the names of countries, dependent territories, special areas of geographical interest, and their principal subdivisions [[1]](#references). The ISO 3166-2 defines codes for identifying the principal subdivisions (e.g. provinces, states, municipalities etc) of all countries coded in the ISO 3166-1. The official name of the standard is "Codes for the representation of names of countries and their subdivisions – Part 2: Country subdivision code." For some countries, codes are defined for more than one level of subdivisions. + +Currently, this package and accompanying API support subdivision data from **250** officially assigned code elements within the ISO 3166-1, with **200** of these countries having recognised subdivisions (50 entires have 0 subdivisions), totalling **5,039** subdivisions across the whole dataset. Transitional reservations are not included and only 4 of the exceptional reservations, that have now been officially assigned, are included: AX (Aland Islands), GG (Guernsey), IM (Isle of Man) and JE (Jersey) [[3]](#references). The ISO 3166-2 was first published in 1998 and as of **November 2023** there are **5,039** codes defined in it [[2]](#references). + +The full list of subdivision data attributes supported are: * Name (subdivsion name) * Local name (subdivision name in local language) @@ -45,19 +54,23 @@ Introduction * Parent Code (subdivision parent code) * Type (subdivision type, e.g. region, state, canton, parish etc) * Latitude/Longitude (subdivision coordinates) -* Flag (subdivsion flag from [`iso3166-flag-icons`](https://github.com/amckenna41/iso3166-flag-icons) repo) +* Flag (subdivsion flag from [`iso3166-flag-icons`](https://github.com/amckenna41/iso3166-flag-icons) repo: this is another ISO 3166 related custom-built dataset) -The International Organisation for Standards defines codes for the names of countries, dependent territories, special areas of geographical interest, and their principal subdivisions [[1]](#references). The ISO 3166-2 defines codes for identifying the principal subdivisions (e.g. provinces, states, municipality etc) of all countries coded in the ISO 3166-1. The official name of the standard is "Codes for the representation of names of countries and their subdivisions – Part 2: Country subdivision code." For some countries, codes are defined for more than one level of subdivisions. Currently, this package and accompanying API support 250 officially assigned code elements along with the user assigned code XK (Kosovo). Transitional reservations are not included and only 4 of the exceptional reservations, that have now been officially assigned, are included: AX (Aland Islands), GG (Guernsey), IM (Isle of Man) and JE (Jersey) [[3]](#references). +The above 7 attributes were chosen as they are the most relevant and useful pieces of data for each subdivision. Other attributes are available in scattered data sources such as area, population, regional name translations, geonames ID, FIPS code etc but these are less relevant than the ones included, unless someone really desires the population of the Bhutan region of Bumthang (17,820 btw). It was an aim during development to make the package as lightweight as possible, therefore for example if the 5 aforementioned attributes were included for the existing **5,039** codes, this would significantly increase the size of the dataset from **~1.6MB to ~2.8MB**. + +Motivation +---------- +The primary motivation for building this software was for use in my [`iso3166-flag-icons`](https://github.com/amckenna41/iso3166-flag-icons) project. When building the dataset of flags, I found that some existing projects/softwares were inaccurate, outdated and or not maintained. As mentioned the ISO 3166-2 is a dynamic and ever-changing standard therefore it can be difficult to maintain and keep up-to-date; although in the case for this software, that problem is largely alleviated thanks to the custom-built [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates) package (see next section). -The ISO 3166-2 was first published in 1998 and as of November 2023 there are 5,039 codes defined in it [[2]](#references). +Furthermore, there are existing toolkits and datasets that offer a rich collection of regional attributes data, [geonames](https://www.geonames.org/) for example. Although, many of these datasets are very large in size and difficult to parse, with an abundance of unessential data attributes. Thus the aim during development was to build a lightweight ISO 3166-2 dataset with the most sought data attributes that can be easily packaged into a Python package. Latest Updates -------------- -An important thing to note about the ISO 3166-2 and its subdivision codes/names is that changes are made consistently to it, from a small subdivision name change to an addition/deletion of a whole subdivision. These changes can happen due for a variety of geopolitical and administrative reasons. Therefore, it's important that this library and its JSON have the most up-to-date, accurate and reliable data. To achieve this, the custom-built [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates) repo was created. +An important thing to note about the ISO 3166-2 and its subdivision codes/names is that changes are made consistently to it, from a small subdivision name change to an addition/deletion of a whole subdivision. These changes can happen due for a variety of geopolitical and administrative reasons. Therefore, it's important that this library and its JSON have the most **up-to-date, accurate and reliable data**. To achieve this, the custom-built [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates) repo was created. -The [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates) repo is another open-source software package and accompanying API that pulls the latest updates and changes for any and all countries in the ISO 3166 from a variety of data sources including the ISO website itself. A script is called every few months to check for any updates/changes to the subdivisions, which are communicated via the ISO's Online Browsing Platform [[4]](#references), and will then be manually incorporated into this repo. Please visit the repository home page for more info about the purpose and process of the software and API - [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates). +The [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates) repo is another open-source software package and accompanying API that pulls the latest updates and changes for any and all countries in the ISO 3166 from a variety of data sources including the ISO website itself. A script is called periodically to check for any updates/changes to the subdivisions, which are communicated via the ISO's Online Browsing Platform [[4]](#references), and will then be manually incorporated into this repo. Please visit the repository home page for more info about the purpose and process of the software and API - [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates). -The list of ISO 3166 updates was last updated on Nov 2023. A log of the latest ISO 3166 updates can be seen in the [UPDATES.md][updates_md] file. +The list of ISO 3166 updates was last updated on March 2024. A log of the latest ISO 3166 updates can be seen in the [UPDATES.md][updates_md] file. API --- @@ -67,16 +80,22 @@ The main API endpoint is: The other endpoints available in the API are: * https://iso3166-2-api.vercel.app/api/all -* https://iso3166-2-api.vercel.app/api/alpha2/ -* https://iso3166-2-api.vercel.app/api/name/ +* https://iso3166-2-api.vercel.app/api/alpha/ +* https://iso3166-2-api.vercel.app/api/country_name/ +* https://iso3166-2-api.vercel.app/api/subdivision/ +* https://iso3166-2-api.vercel.app/api/name/ -Three paths/endpoints are available in the API - `/api/all`, `/api/alpha2` and `/api/name`. +Five paths/endpoints are available in the API - `/api/all`, `/api/alpha`, `/api/country_name`, `/api/subdivision` and `/api/name`. * The `/api/all` path/endpoint returns all of the ISO 3166 subdivision data for all countries. -* The `/api/alpha2` endpoint accepts the 2 letter alpha-2 country code appended to the path/endpoint e.g. /api/alpha2/JP. A single alpha-2 code or list of them can be passed to the API e.g. /api/alpha2/FR,DE,HU,ID,MA. For redundancy, the 3 letter alpha-3 counterpart for each country's alpha-2 code can also be appended to the path e.g. /api/alpha2/FRA,DEU,HUN,IDN,MAR. If an invalid alpha-2 code is input then an error will be returned. +* The `/api/alpha` endpoint accepts the 2 letter alpha-2, 3 letter alpha-3 and or numeric ISO 3166-1 country codes appended to the path/endpoint e.g. `/api/alpha/JP`. A single alpha-2, alpha-3 or numeric code or list of them can be passed to the API e.g. `/api/alpha/FR,DE,HU,ID,MA`, `/api/alpha/FRA,DEU,HUN,IDN,MAR` and `/api/alpha/428,504,638`. If an invalid country code is input then an error will be returned. -* The `/api/name` endpoint accepts the country/territory name as it is most commonly known in english, according to the ISO 3166-1. The name can similarly be appended to the **name** path/endpoint e.g. /api/name/Denmark. A single country name or list of them can be passed into the API e.g. /name/France,Moldova,Benin. A closeness function is utilised so the most approximate name from the input will be used e.g. Sweden will be used if input is /api/name/Swede. If no country is found from the closeness function or an invalid name is input then an error will be returned. +* The `/api/country_name` endpoint accepts the country/territory name as it is most commonly known in english, according to the ISO 3166-1 e.g. `/api/country_name/Denmark`. A single country name or list of them can be passed into the API e.g. `/api/country_name/France,Moldova,Benin`. A closeness function is utilised so the most approximate name from the input will be used e.g. Sweden will be used if input is `/api/country_name/Swede`. If no country is found from the closeness function or an invalid name is input then an error will be returned. + +* The `/api/subdivision` endpoint accepts the ISO 3166-2 subdivision codes, e.g `/api/subdivision/GB-ABD`. You can also input a list of subdivision codes from the same and or different countries and the data for each will be returned e.g `/api/subdivision/IE-MO,FI-17,RO-AG`. If the input subdivision code is not in the correct format then an error will be raised. Similarly if an invalid subdivision code that doesn't exist is input then an error will be raised. + +* The `/api/name/` endpoint accepts the ISO 3166-2 subdivision names, e.g `/api/name/Derry`. You can also input a list of subdivision name from the same or different countries and the data for each will be returned e.g `/api/name/Paris,Frankfurt,Rimini`. A closeness function is utilised to find the matching subdivision name, if no exact name match found then the most approximate subdivisions will be returned. Some subdivisions may have the same name, in this case each subdivision and its data will be returned e.g `/api/name/Saint George,Sucre`. This endpoint also has the likeness score (`?likeness=`) query string parameter that can be appended to the URL. This can be set between 1 - 100, representing a % of likeness to the input name the return subdivisions should be, e.g: a likeness score of 90 will return fewer potential matches whose name only match to a high degree compared to a score of 10 which will create a larger search space, thus returning more potential subdivision matches. A default likeness of 100 (exact match) is used, if no match found then this is reduced to 90. If an invalid subdivision name that doesn't match any is input then an error will be raised. * The main API endpoint (`/` or `/api`) will return the homepage and API documentation. @@ -86,19 +105,9 @@ Requirements ------------ * [python][python] >= 3.8 * [iso3166][iso3166] >= 2.1.1 - -Requirements (iso3166_2_scripts/get_iso3166_2.py) -------------------------------------------------- -* [python][python] >= 3.8 -* [iso3166-2][iso3166_2] >= 1.5.0 -* [requests][requests] >= 2.28.1 -* [iso3166][iso3166] >= 2.1.1 -* [pycountry][pycountry] >= 22.3.5 -* [googlemaps][googlemaps] >= 4.10.0 -* [tqdm][tqdm] >= 4.64.0 * [natsort][natsort] >= 8.4.0 -* [pandas][pandas] >= 1.4.3 -* [numpy][numpy] >= 1.23.2 +* [unidecode][unidecode] >= 1.3.8 +* [thefuzz][thefuzz] >= 0.22.1 Installation ------------ @@ -117,135 +126,145 @@ python3 setup.py install Usage ----- -The main JSON iso3166-2.json contains each country's ISO 3166-2 subdivision data and attributes. In the main module, iso3166_2.py, all data from the iso3166-2.json is accessible via the `iso.country` object. +The main JSON iso3166-2.json contains each country's ISO 3166-2 subdivision data and attributes. The data can be accessed after creating an instance of the ISO3166_2 class, with the instance being subscriptable such that data can be accessed via their ISO 3166-1 alpha-2, alpha-3 or numeric country codes. -Import ISO3166_2 class and access the subdivision data: +**Import ISO3166_2 class and access the subdivision data:** ```python -import iso3166_2 as iso +from iso3166_2 import * + +#create instance of ISO3166_2 class +iso = ISO3166_2() #access all country's subdivision data -canada_iso3166_2 = iso.country["CA"] -denmark_iso3166_2 = iso.country["DK"] -estonia_iso3166_2 = iso.country["EE"] -fiji_haiti_guyana_iso3166_2 = iso.country["FJ, HT, GY"] -peru_iso3166_2 = iso.country["PE"] +canada_iso3166_2 = iso["CA"] +denmark_iso3166_2 = iso["DK"] +estonia_iso3166_2 = iso["EST"] +peru_iso3166_2 = iso["PER"] +fiji_haiti_guyana_iso3166_2 = iso["FJ, HTI, 328"] ``` -Get a specific subdivision's info: +**Get a specific subdivision's info:** ```python -import iso3166_2 as iso +from iso3166_2 import * -canada_iso3166_2['CA-AB'] #Alberta subdivision -denmark_iso3166_2['DK-81'] #Nordjylland subdivision -estonia_iso3166_2['EE-899'] #Viljandi subdivision -fiji_haiti_guyana_iso3166_2['FJ-03'] #Cakaudrove subdivision -peru_iso3166_2['PE-AMA'] #Amarumayu subdivision -``` +#create instance of ISO3166_2 class +iso = ISO3166_2() -Get individual attribute values per subdivision: -```python -import iso3166_2 as iso - -canada_iso3166_2['CA-AB'].latLng #Alberta subdivision latitude/longitude -denmark_iso3166_2['DK-81'].flagUrl #Nordjylland subdivision flag URL -estonia_iso3166_2['EE-899'].name #Viljandi subdivision name -fiji_haiti_guyana_iso3166_2['FJ-03'].type #Cakaudrove subdivision type -peru_iso3166_2['PE-AMA'].parentCode #Amarumayu subdivision +iso["CA"]['CA-AB'] #Alberta subdivision +iso["DK"]['DK-81'] #Nordjylland subdivision +iso["EST"]['EE-899'] #Viljandi subdivision +iso["FJI"]['FJ-03'] #Cakaudrove subdivision +iso["604"]['PE-AMA'] #Amarumayu subdivision ``` -Get all ISO 3166-2 data for all countries: +**Get individual attribute values per subdivision:** ```python -import iso3166_2 as iso +from iso3166_2 import * -#get all subdivision data using all attribute -iso.country.all +#create instance of ISO3166_2 class +iso = ISO3166_2() + +iso["CA"]['CA-AB'].latLng #Alberta subdivision latitude/longitude +iso["DK"]['DK-81'].flagUrl #Nordjylland subdivision flag URL +iso["EST"]['EE-899'].name #Viljandi subdivision name +iso["FJI"]['FJ-03'].type #Cakaudrove subdivision type +iso["604"]['PE-AMA'].parentCode #Amarumayu subdivision ``` -Adding a custom subdivision to the iso3166-2 object. The context for this -functionality is similar to that of the user-assigned code elements of the -ISO 3166-1 standard. Custom subdivisions and subdivision codes can be used -for in-house/bespoke applications that are using the iso3166-2 software but -require additional custom subdivisions to be represented: +**Get all ISO 3166-2 data for all countries:** ```python -import iso3166_2 as iso +from iso3166_2 import * -#adding custom Belfast province to Ireland -iso.country.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) -``` +#create instance of ISO3166_2 class +iso = ISO3166_2() -Searching for a specific subdivision via its subdivision name attribute. The -search functionality will search over all subdivisions in the object, +#get all subdivision data using all attribute +iso.all +``` +**Searching for a specific subdivision via its subdivision name attribute: the search functionality will search over all subdivisions in the object, returning either a subdivision with the exact match or subdivisions whose -names approximately match the sought input name: +names approximately match the sought input name according to the likeness +input parameter:** ```python -import iso3166_2 as iso +from iso3166_2 import * #searching for the Monaghan county in Ireland (IE-MN) - returning exact matching subdivision -iso.country.search("Monaghan", any=False) +iso.search("Monaghan") -#searching for any subdivisions that have "Southern" in their name -iso.country.search("Southern", any=True) +#searching for any subdivisions that have "Southern" in their name, using a likeness score of 0.8 +iso.country.search("Southern", likeness=0.8) ``` -Usage (iso3166_2_scripts/get_iso3166_2.py) ------------------------------------------- -The script [`iso3166_2_scripts/get_iso3166_2.py`](https://github.com/amckenna41/iso3166-2/blob/main/iso3166_2_scripts/get_iso3166_2.py) is used for gathering and exporting subdivision data for ALL countries to the JSON object. It uses the [pycountry][pycountry] and [googlemaps][googlemaps] packages to gather and export all the required subdivision info. Calling the script using its default parameters will gather all the data for ALL countries, but the alpha2_codes parameter can be set to pull the latest data for a specific list of one or more countries (the alpha-3 code can also be input, which is then converted into its 2 letter alpha-2 counterpart). - -To download all of the latest ISO 3166-2 subdivision data for ALL countries, from the main repo dir, run the `get_iso3166_2.py` in a terminal or command line below; (the script takes around 1 hour and 40 mins to execute): +**Adding a custom subdivision to the iso3166-2 object: the context for this functionality is similar to that of the user-assigned +code elements of the ISO 3166-1 standard. Custom subdivisions and subdivision +codes can be used for in-house/bespoke applications that are using the +iso3166-2 software but require additional custom subdivisions to be represented:** +```python +from iso3166_2 import * -```bash -python3 iso3166_2_scripts/get_iso3166_2.py --json_filename=iso3166_2.json --output_folder=iso3166_2 --verbose +#adding custom Belfast province to Ireland +iso.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) -#--alpha2_codes: list of 1 or more 2 letter alpha-2 country codes (if not specified then all country codes will be used). -#--json_filename: output filename for exported JSONs. -#--output_folder: output folder to store JSONs. -#--verbose: if set to 1 then the progress of the ISO 3166-2 data export will be output. +#deleting above custom subdivision from object +iso.custom_subdivision("IE", "IE-BF", delete=1) ``` -To download all of the latest ISO 3166-2 subdivision data for Germany, Portugal and Spain (the data will be exported to a JSON called iso3166_2-DE,ES,PT.json): -```bash -python3 iso3166_2_scripts/get_iso3166_2.py --alpha2_codes=DE,PT,ES --json_filename=iso3166_2.json -``` +Documentation +------------- +Documentation for installation and usage of the software and API is availble on the readthedocs platform: + +https://iso3166-2.readthedocs.io/en/latest/ + Usage (iso3166_2_scripts/update_subdivisions.py) ------------------------------------------------ The script [`iso3166_2_scripts/update_subdivisions.py`](https://github.com/amckenna41/iso3166-2/blob/main/iso3166_2_scripts/update_subdivisions.py) has the `update_subdivision()` function that was created to streamline the addition/amendment/deletion to any of the subdivisions in the data object. The function can accept an individual subdivision change by passing in all the required attribute values to the function directly. Alternatively, a CSV file with rows of the individual changes can be passed in, allowing for hundreds of changes to be made in one go. -The primary input parameters to the `update_subdivision()` function are: alpha2_code, subdivision_code, name, local_name, type, latLng, parent_code, flag_url and delete. The first eight parameters represent the data to be added/changed to the specified country code and subdivision code (alpha2_code, subdivision_code) and delete is a boolean flag that should be set (0/1) if the input subdivision is to be deleted - by default this will be 0. For any addition, amendment or deletion, the country_code and subdivision_code parameters are required, but the remainder of the parameters are optional. If these optional parameters are not set then they will be set null, in the case of an addition or deletion, or remain as their previous values in the case of an amendment. +The primary input parameters to the `update_subdivision()` function are: alpha2_code, subdivision_code, name, local_name, type, latLng, parent_code, flag_url and delete. The first eight parameters represent the data to be added/changed to the specified country code and subdivision code (alpha2_code, subdivision_code) and delete is a boolean flag that should be set (0/1) if the input subdivision is to be deleted - by default this will be 0. For any addition, amendment or deletion, the country_code and subdivision_code parameters are required, but the remainder of the parameters are optional. If these optional parameters are not set then they will be set to null, in the case of an addition or deletion, or remain as their previous values in the case of an amendment. As mentioned, you can also pass in a CSV with rows of all the changes to be made to the subdivision object. The CSV has the same columns as the aforementioned function parameters, but additionally has the localNameSpelling, notes and dateIssued columns. localNameSpelling should be set to 1 if the subdivision local name is the same as its name, if the column is empty or 0 then the subdivision will take the value specified by the localName column. notes just contains a small description about the addition/amendment/deletion being made and dateIssued is the date that the subdivision change was communicated by the ISO. ```python from iso3166_2_scripts.update_subdivisions import * -#adding Timimoun Province of Algeria (DZ-49) to ISO 3166-2 object (from newsletter 2022-11-29) +#adding Timimoun Province of Algeria (DZ-49) to ISO 3166-2 object (date issued: 2022-11-29) update_subdivision("DZ", "DZ-49", name="Timimoun", local_name="ولاية تيميمون", type="Province", latLng=[29.263, 0.241], parent_code=None, flag_url=None) #adding Waterford County of Ireland (IE-WD) to ISO 3166-2 object - subdivision already present so no changes made update_subdivision("IE", "IE-WD", "Waterford", local_name="Port Láirge", type="County", latLng=[52.260, -7.110], parent_code="IE-M", flag_url="https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/IE/IE-WD.png") -#iso.update_subdivision("IE", "IE-WD") - this will also work as only the first 2 params requried +#iso.update_subdivision("IE", "IE-WD") - this will also work as only the first 2 parameters explicitly requried -#amending the subdivision name of subdivision FI-17 from Satakunda to Satakunta (from newsletter 2022-11-29) +#amending the subdivision name of subdivision FI-17 from Satakunda to Satakunta (date issued: 2022-11-29) update_subdivision("FI", "FI-17", name="Satakunta") -#deleting FR-GP (Guadeloupe) and (FR-MQ Martinique) subdivisions +#deleting FR-GP (Guadeloupe) and (FR-MQ Martinique) subdivisions (date issued: 2022-11-25) update_subdivision("FR", "FR-GP", delete=1) update_subdivision("FR", "FR-MQ", delete=1) #error raised as both country_code and subdivision_code parameters required update_subdivision(type="region", latLng=[], parent_code=None) -#passing in a csv with rows of subdivision additions/updates/deletions -update_subdivision(subdivision_csv="new_subdivisions.csv") +#passing in a csv with rows of subdivision additions/updates/deletions (iso3166_2_updates/subdivision_updates.csv) +update_subdivision(subdivision_csv="iso3166_2_updates/subdivision_updates.csv") ``` +Directories +----------- +* `/iso3166_2` - source code for `iso3166-2` software. +* `/iso3166_2_scripts` - scripts for pulling all the ISO 3166 data and for adding/amending/deleting subdivisions to the dataset. +* `/iso3166_2_updates` - contains CSV file for listing any changes/updates to be made to the dataset (subdivision_updates.csv) via functionality in the /iso3166_2_scripts dir and a CSV of all subdivisions and their respective local names (local_names.csv). +* `/docs` - documentation for `iso3166-2`, available on [readthedocs](https://iso3166-2.readthedocs.io/en/latest/). +* `/tests` - unit and integration tests for `iso3166-2` +* `API.md` - info and useful commands/examples for this software's accompanying API - **iso3166-2-api** +* `UPDATES.md` - markdown file listing all of the additions, amendments and deletions to the ISO 3166-2 dataset (dating from 2022), exported via the [`iso3166-updates`](https://github.com/amckenna41/iso3166-updates) software. + Issues or Contributing ---------------------- -Any issues, bugs or enhancements can be raised via the [Issues][issues] tab in the repository. If you would like to contribute to this project, please make a PR. +Any issues, bugs or enhancements can be raised via the [Issues][issues] tab in the repository. If you would like to contribute any functional/feature changes to the project, please make a Pull Request. Contact ------- -If you have any questions or comments, please contact amckenna41@qub.ac.uk or raise an issue in the [Issues][issues] tab.

+If you have any questions, comments or suggestions, please contact amckenna41@qub.ac.uk or raise an issue in the [Issues][issues] tab.

References @@ -271,6 +290,8 @@ Support [tqdm]: https://github.com/tqdm/tqdm [natsort]: https://pypi.org/project/natsort/ [pandas]: https://pandas.pydata.org/ +[unidecode]: https://pypi.org/project/Unidecode/ +[thefuzz]: https://github.com/seatgeek/thefuzz/tree/master [numpy]: https://numpy.org/ [PyPi]: https://pypi.org/project/iso3166-2/ [iso3166-updates]: https://github.com/amckenna41/iso3166-updates @@ -280,5 +301,5 @@ Support [api_md]: https://github.com/amckenna41/iso3166-2/API.md [flag_icons_repo]: https://github.com/amckenna41/iso3166-flag-icons [issues]: https://github.com/amckenna41/iso3166-2/issues -[medium]: https://github.com/amckenna41/iso3166-2 +[medium]: https://ajmckenna69.medium.com/iso3166-2-71a13d9157f7 [updates_md]: https://github.com/amckenna41/iso3166-2/blob/main/UPDATES.md \ No newline at end of file diff --git a/ToDo.md b/ToDo.md index bd99394..e8f8327 100644 --- a/ToDo.md +++ b/ToDo.md @@ -108,16 +108,14 @@ https://iso3166-updates.com/api/year/2022 - DZ (subdivisions added), ET (subdivi https://iso3166-updates.com/api/year/2021 - https://iso3166-updates.com/api/year/2020 : up-to-date. - [X] Add link to medium article on readme. -- [ ] Add github release (need to make repo public first). -- [ ] Codecov. -- [ ] Check variable naming conventions. +- [X] Add github release (need to make repo public first). +- [X] Check variable naming conventions. - [X] Any references of other softwares, put in `` and add link to it. - [X] GB-NIR, GB-ENG, GB-SCT and GB-WLS absent from GB subdivision entry. - [X] Mention that repo only contains officially assigned code elements and XK. Doesn't contain exceptional and transitional reservetations. Doesn't contain exceptional reservations but some have been reassinged (AZ, GG, IM, JE). - [X] Add wiki link to each country: https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes. - [X] Update number of attributes - 36 -> 37. - [X] Add more filter attribute unit tests for when dict of keys/vals passed in. -- [ ] In get script, need to phase out pycountry. - [X] Make individual attributes per subdivision accessible via dot notation. - [X] Update check-for-updates func. - [X] Double check elapsed time for get_ function is correct. @@ -130,10 +128,10 @@ https://iso3166-updates.com/api/year/2020 : up-to-date. - [X] Add subdivisions CSV, add delete column that flags if a subdivision is to be deleted. - [X] Add functionality such that you can change an existing subdivision code - add new code in brackets? - issue since countries are indexed by their subdivision codes. - [X] Double check when adding "IQ,IQ-KR,Iqlīm Kūrdistān,Region,"IQ-AR, IQ-DA, IQ-SU"" via csv that the "" aren't added to the parent code section. -- [ ] Am i testing all flag URLs are valid rather than just subsection? +- [X] Am i testing all flag URLs are valid rather than just subsection? - [X] Unit test for testing a country's parent code is in the list of subdivison codes. - [X] Add local name key to object. -- [ ] Update camel caseing on attributes. +- [X] Update camel caseing on attributes. - [X] Go through each subdivision name and double check its localName isnt name. - [X] When running get_all script, create some functionality that automatically runs the csv files in order, making any additions/deletions/amendments to the object. - [X] Unit test to check that no subdivision latLng's are None. @@ -167,7 +165,7 @@ https://iso3166-updates.com/api/year/2020 : up-to-date. - [X] Update elapsed time - after iso3166-flag-icons repo. - [X] Add tqdm to get_ script - [X] Update filesize in readme. -- [ ] Update readme, swap pycountry for iso3166-2. +- [X] Update readme, swap pycountry for iso3166-2. - [X] When calling update_subdivisions() function, check order of attributes remains the same. - [X] When calling local_names() function, check order of attributes remains the same. - [X] For update_subdivisions(), check correct JSON is being used as input for the get_all script. @@ -176,23 +174,79 @@ https://iso3166-updates.com/api/year/2020 : up-to-date. - [X] Mention # of tests and test cases (including skipped ones.) - [X] Update metadata tests - similar to iso3166-updates. - [X] Functionaltiy to add country/subdivision data in iso3166_2.py. -- [ ] Update license. - [ ] Change functions to parameter typed (https://docs.python.org/3/library/typing.html). - [X] Add search functionality that allows you to search by name. - [X] In search functionality, add alpha_2 code to it. - [ ] For localName, add different translations for subdivision names, use language code as key. If local name same as name then don't add language code keys. - [ ] Convert localNames.csv into a json? -- [ ] Double check # of test functions/cases after running them. +- [X] Double check # of test functions/cases after running them. +- [ ] Upload to conda: https://anaconda.org/conda-forge/pgeocode +- [X] iso3166-2.json in csv form. +- [X] Update table of contents. +- [X] Add tests for iso3166-2 CSV export. +- [X] Add to readme that some existing projects are inaccurate, outdated and or not maintained. +- [X] Update license. +- [X] Mention on readme that additional attributes for each subdivision will be available on the country-db repo including geonames ID, fips, area, population, regional translations etc. These are not included to try and reduce the size of the software and API, only including most useful info. Remove all the bloat that other repos have. Add a "What's in and what's out" section to readme, give examples e.g translations. +- [X] Add directories subsection to main readme and Table of contents. +- [X] If 1 subdivision code returned from class, return list not dict. +- [X] Remove subdivision_parent_codes() from iso3166_2.py +- [X] Mention for functions in iso3166_2 can return multiple data types depending if 1 or more country data is returned. +- [ ] Codecov. +- [X] Subdivision names not exporting in alphabetical order. +- [X] /subd examples on api.md. +- [X] Double check links on readme work. +- [X] In update_subdivisions() function, if not getting all data, skip over rows that aren't equal to input country codes. +- [X] Add /subd example to docs. +- [X] Multiple subdivisions not returning in API. +- [X] Add additional API tests for /subd, passing in multiple list of subdivisions. +- [X] Search via subdivision name - might need to update /name endpoint. +- [X] Update API.md for /subd endpoint. +- [X] Update API.md and unit tests. +- [X] Update comments of api test case. +- [ ] Add note mentioning interchangability of use of 'countries.' +- [X] In sofrware, search via numeric code as well - update unit tests. +- [X] Mention that searching via alpha-3 or numeric will return alpha-2. +- [X] In .subdivision_codes(), uppercase alpha input parameter. +- [X] In iso3166_2.py, change input param name from alpha2_code to alpha_code. +- [X] ES - for some subdivisions, spelling mistake in name (catalan names added but not english). +- [X] Update get_script, such that if a subdivision with a code that is already in the dataset is pulled, an error is raised with the existing and new subdivision details. +- [X] Update medium article. +- [X] Read over iso3166_2.py. +- [X] Read over non-api unit tests. +- [X] In sw, try searching for subdivision with comma in it. +- [X] In sw, in search function, add subdivision code as key when multiple subdivisions returned. +- [X] Query string parameter where you can set the likeness cutoff point, lowering the likeness threshold when searching for a name that might be in multiple subdivision names. Add api unit tests. +- [X] Change difflib to fuzzysearch for API and software. +- [X] In iso3166_2.py, change "any" param to "likeness". +- [X] Play about with scorer ratio in thefuzz - mention in comments which scorer is being used. +- [X] Search funtionality should return dict objecr rather than list of dicts. +- [X] Rearrange Usage and API contents in readme? +- [X] Update /docs +- [X] In api docs, put API funcs all into one section. +- [X] Check order of sections in readme. Future Additions ---------------- - [ ] Build same iso3166-2 package for JS/TS - https://github.com/annexare/Countries, https://www.npmjs.com/package/iso-3166. -- [ ] Build frontend using jsVectorMap and dropdown etc. -- [ ] include boundary lat/lng for subdivisions. -- [ ] Implement more info about each subdivision if possible - area, pop, capital, subdivision borders, subdivision bounds from maps api, subdivison names in their native language. +- [ ] Build frontend using jsVectorMap, dropdown and React etc. +- [ ] include boundary lat/lng for subdivisions - counttrydb +- [ ] Implement more info about each subdivision if possible - area, pop, capital, subdivision borders, subdivision bounds from maps api, subdivison names in their native language - country db. - [ ] Pull request for django-countries (https://github.com/SmileyChris/django-countries/tree/main) - [ ] Add geonames (geonames.org), openstreetmap relation ID, openstreetmap administrarive division level (https://wiki.openstreetmap.org/wiki/Key:admin%20level?uselang=en-GB), boundary (https://wiki.openstreetmap.org/wiki/Key:boundary?uselang=en-GB), FIPS code, to subdivisions. - [X] Documentation on readthedocs: https://www.youtube.com/watch?v=3NUmOGVdjqk&list=PLPDCBPbzk1AYghqYazE7Cxt3p7edml8I7&index=6 -- [ ] Add readthedocs badge - [![Documentation Status](https://readthedocs.org/projects/ansicolortags/badge/?version=latest)](http://ansicolortags.readthedocs.io/?badge=latest). https://github.com/readthedocs-examples/awesome-read-the-docs#sphinx-projects +- [X] Add readthedocs badge - [![Documentation Status](https://readthedocs.org/projects/ansicolortags/badge/?version=latest)](http://ansicolortags.readthedocs.io/?badge=latest). https://github.com/readthedocs-examples/awesome-read-the-docs#sphinx-projects - [X] For new subdivision additions in Changes.md, add flags in iso3166-flag-icons repo. +- [ ] Search via lat and long and return subdivision data - find a tool that maps lat/lang to code (gmaps.reverse_geocode("56.503, 23.693")). +- [ ] Other useful geonames stuff (https://download.geonames.org/export/dump/allCountries.zip, https://download.geonames.org/export/dump/, https://download.geonames.org/export/dump/alternateNamesV2.zip ) +- [ ] Add geoname IDs - countrydb. +- [X] Keep population of subdivisions seperate from main iso3166-2. +- [ ] Add localName from alternativeNames in https://download.geonames.org/export/dump/. +- [ ] For usage for API on docs, add examples for other languages - similar to https://countrystatecity.in/docs/api/states-by-country/. +- [ ] Add example Error and Not Found response - https://countrystatecity.in/docs/api/states-by-country/ +- [ ] Add demo similar to https://dr5hn.github.io/countries-states-cities-database/. +- [ ] Create PR for https://github.com/dr5hn/countries-states-cities-database - add up-to-date ISO 3166-2 data and flag icons from iso3166-flag-icons (https://github.com/dr5hn/countries-states-cities-database/blob/master/states.json). +- [ ] Add subregion for each subdivision. +- [X] Go over https://github.com/dr5hn/countries-states-cities-database. +- [X] Go over https://github.com/esosedi/3166. +- [X] Add readme for /docs, add process for building and uploading the docs. \ No newline at end of file diff --git a/UPDATES.md b/UPDATES.md index 187b4c4..5773dda 100644 --- a/UPDATES.md +++ b/UPDATES.md @@ -1,5 +1,15 @@ # ISO 3166 latest changes (2022-) +## 2024-02-29 +| Country Code | Edition/Newsletter | Date Issued | Code/Subdivision Change | Description of Change in Newsletter | +|:-------------------|:------------|-----------------------------------:|------------------------:|-------------:| +| Bolivia (BO) | Online Browsing Platform (OBP) - (https://www.iso.org/obp/ui/#iso:code:3166:BO). | 2024-02-29 | | Change of short name upper case: replace the parentheses with a coma. | +| Micronesia (FM) | Online Browsing Platform (OBP) - (https://www.iso.org/obp/ui/#iso:code:3166:FM). | 2024-02-29 | | Change of short name upper case: replace the parentheses with a coma and correct the remark in English of the entry of Micronesia Federate states (removing duplicated text). | +| Iran (IR) | Online Browsing Platform (OBP) - (https://www.iso.org/obp/ui/#iso:code:3166:IR). | 2024-02-29 | | Change of short name upper case: replace the parentheses with a coma. | +| Korea (KP) | Online Browsing Platform (OBP) - (https://www.iso.org/obp/ui/#iso:code:3166:KP). | 2024-02-29 | | Change of short name upper case in eng: replace the parentheses with a coma. | +| Venezuela (VE) | Online Browsing Platform (OBP) - (https://www.iso.org/obp/ui/#iso:code:3166:VE). | 2024-02-29 | | Change of short name upper case: replace the parentheses with a coma. | + + ## 2023-11-23 | Country Code | Edition/Newsletter | Date Issued | Code/Subdivision Change | Description of Change in Newsletter | |:-------------------|:------------|-----------------------------------:|------------------------:|-------------:| diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9f34ac7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,34 @@ +# Documentation for iso3166-2 + +[![Documentation Status](https://readthedocs.org/projects/iso3166-2/badge/?version=latest)](https://iso3166-2.readthedocs.io/en/latest/?badge=latest) + +The documentation for `iso3166-2` is hosted on the **readthedocs** platform and is available [here](https://iso3166-2.readthedocs.io/en/latest). The documentation is automatically built and published on the platform via the repo. + +## Build documentation and test locally + +Install *sphinx* Python package +```bash +pip install sphinx +``` + +Create a /docs directory inside the `iso3166-2` package +```bash +mkdir /docs +``` + +Run *sphinx-quickstart* in /docs directory to create template for the documentation +```bash +cd /docs && sphinx-quickstart +``` + +After editing the documentation, build using make command in the /docs folder +```bash +make html +``` + +Open the index.html file generated in the docs/_build/html folder +```bash +open _build/html/index.html +``` + +The full tutorial for using the readthedocs platform is available [here](https://docs.readthedocs.io/en/stable/tutorial/index.html). diff --git a/docs/api.rst b/docs/api.rst index f28dc24..658d08d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,23 +2,27 @@ API ==== The main API endpoint is: `https://iso3166-2-api.vercel.app/api `_. This endpoint displays the API documentation and forms the -base URL for the 3 other endpoints. +base URL for the 5 other endpoints. The other endpoints available in the API are: * https://iso3166-2-api.vercel.app/api/all -* https://iso3166-2-api.vercel.app/api/alpha2/ +* https://iso3166-2-api.vercel.app/api/alpha/ +* https://iso3166-2-api.vercel.app/api/subdivision/ +* https://iso3166-2-api.vercel.app/api/country_name/ * https://iso3166-2-api.vercel.app/api/name/ -Three paths/endpoints are available in the API - `/api/all`, `/api/alpha2` and `/api/name`. +Five paths/endpoints are available in the API - `/api/all`, `/api/alpha`, `/api/subdivision`, `/api/country_name` and `/api/name`. * The `/api/all` path/endpoint returns all of the ISO 3166 subdivision data for all countries. -* The `/api/alpha2` endpoint accepts the 2 letter alpha-2 country code appended to the path/endpoint e.g. */api/alpha2/JP*. A single alpha-2 code or list of them can be passed to the API e.g. */api/alpha2/FR,DE,HU,ID,MA*. For redundancy, the 3 letter alpha-3 counterpart for each country's alpha-2 code can also be appended to the path e.g. */api/alpha2/FRA,DEU,HUN,IDN,MAR*. If an invalid alpha-2 code is input then an error will be returned. -* The `/api/name` endpoint accepts the country/territory name as it is most commonly known in english, according to the ISO 3166-1. The name can similarly be appended to the **name** path/endpoint e.g. */api/name/Denmark*. A single country name or list of them can be passed into the API e.g. */name/France,Moldova,Benin*. A closeness function is utilised so the most approximate name from the input will be used e.g. Sweden will be used if input is */api/name/Swede*. If no country is found from the closeness function or an invalid name is input then an error will be returned. +* The `/api/alpha` endpoint accepts the 2 letter alpha-2, 3 letter alpha-3 or numeric country codes appended to the path/endpoint e.g. */api/alpha/JP*. A single alpha code or list of them can be passed to the API e.g. */api/alpha2/FR,DEU,HUN,360,504*. If an invalid alpha code is input then an error will be returned. +* The `/api/subdivision` endpoint accepts the ISO 3166-2 subdivision codes, e.g */api/subd/GB-ABD*. You can also input a list of subdivision code and the data for each will be returned e.g */api/subd/IE-MO,FI-17,RO-AG*. If the input subdivision code is not in the correct format then an error will be raised. Similarly if an invalid subdivision code that doesn't exist is input then an error will be raised. +* The `/api/country_name` endpoint accepts the country/territory name as it is most commonly known in english, according to the ISO 3166-1 e.g `/api/country_name/Denmark`. A single country name or list of them can be passed into the API e.g. `/api/country_name/France,Moldova,Benin`. A closeness function is utilised so the most approximate name from the input will be used e.g. Sweden will be used if input is `/api/country_name/Swede`. If no country is found from the closeness function or an invalid name is input then an error will be returned. +* The `/api/name` endpoint accepts the ISO 3166-2 subdivision names, e.g `/api/name/Derry`. You can also input a list of subdivision name from the same or different countries and the data for each will be returned e.g `/api/name/Paris,Frankfurt,Rimini`. A closeness function is utilised to find the matching subdivision name, if no exact name match found then the most approximate subdivisions will be returned. Some subdivisions may have the same name, in this case each subdivision and its data will be returned e.g `/api/name/Saint George,Sucre`. This endpoint also has the likeness score (`?likeness=`) query string parameter that can be appended to the URL. This can be set between 1 - 100, representing a % of likeness to the input name the return subdivisions should be, e.g: a likeness score of 90 will return fewer potential matches whose name only match to a high degree compared to a score of 10 which will create a larger search space, thus returning more potential subdivision matches. A default likeness of 100 (exact match) is used, if no match found then this is reduced to 90. If an invalid subdivision name that doesn't match any is input then an error will be raised. * The main API endpoint (`/` or `/api`) will return the homepage and API documentation. -Getting all ISO 3166-2 subdivision data ---------------------------------------- +Accessing subdivision data for all countries +-------------------------------------------- Python Requests: @@ -37,8 +41,10 @@ curl:: $ curl -i https://iso3166-2-api.vercel.app/api/all -Get all ISO 3166-2 subdivision data for a specific country, using its 2 letter alpha-2 code e.g FR, DE, HN ----------------------------------------------------------------------------------------------------------- +Accessing subdivision data per country, using its ISO 3166-1 alpha codes +------------------------------------------------------------------------ + +For example, accessing all subdivision data for France, Germany or Gambia (FR, DE, GM), via their **alpha-2 country code**: Python Requests: @@ -47,19 +53,19 @@ Python Requests: import requests base_url = "https://iso3166-2-api.vercel.app/api/" - input_alpha2 = "FR" #DE, HN - all_data = requests.get(base_url + f'/alpha2/{input_alpha2}').json() + input_alpha2 = "FR" #DE, GM + all_data = requests.get(base_url + f'/alpha/{input_alpha}').json() all_data["FR"] #subdivision data for France curl:: - $ curl -i https://iso3166-2-api.vercel.app/api/alpha2/FR - $ curl -i https://iso3166-2-api.vercel.app/api/alpha2/DE - $ curl -i https://iso3166-2-api.vercel.app/api/alpha2/HN + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/FR + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/DE + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/GM + -Get all ISO 3166-2 subdivision data for a specific country, using country name, e.g. Tajikistan, Seychelles, Uganda -------------------------------------------------------------------------------------------------------------------- +For example, accessing all subdivision data for Greece, Mexico or Montenegro (GRC, MEX, MNE), via their **alpha-3 country code**: Python Requests: @@ -68,16 +74,119 @@ Python Requests: import requests base_url = "https://iso3166-2-api.vercel.app/api/" - input_name = "Tajikistan" #Seychelles, Uganda - all_data = requests.get(base_url + f'/name/{input_name}').json() + input_alpha2 = "GRC" #MEX, MNE + all_data = requests.get(base_url + f'/alpha/{input_alpha}').json() + + all_data["GR"] #subdivision data for Greece + +curl:: + + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/GRC + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/MEX + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/MNE + + +For example, accessing all subdivision data for Nicaragua, Papa New Guinea or Qatar (558, 598, 634), via their **alpha numeric code**: + +Python Requests: + +.. code-block:: python + + import requests + + base_url = "https://iso3166-2-api.vercel.app/api/" + input_alpha2 = "558" #598, 634 (NI, PG, QA) + all_data = requests.get(base_url + f'/alpha/{input_alpha}').json() + + all_data["NI"] #subdivision data for Nicaragua + +curl:: + + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/558 + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/598 + $ curl -i https://iso3166-2-api.vercel.app/api/alpha/634 + + +Accessing all subdivision data for a specific subdivision, using its subdivision code +-------------------------------------------------------------------------------------- + +For example, accessing all subdivision data for Alūksnes novads, Colón and Northern Cape (LV-007, PA-3, ZA-NC): + +Python Requests: + +.. code-block:: python + + import requests + + base_url = "https://iso3166-2-api.vercel.app/api/" + input_subdivision = "LV-007" #PA-3, ZA-NC + all_data = requests.get(base_url + f'/subdivision/{input_subdivision}').json() + + all_data["LV-007"] #data for LV-007 subdivision + +curl:: + + $ curl -i https://iso3166-2-api.vercel.app/api/subdivision/LV-007 + $ curl -i https://iso3166-2-api.vercel.app/api/subdivision/PA-3 + $ curl -i https://iso3166-2-api.vercel.app/api/subdivision/ZA-NC + +Accessing all subdivision data for a specific country, using its name +--------------------------------------------------------------------- + +For example, accessing all subdivision data for Tajikistan, Seychelles, Uganda: + +Python Requests: + +.. code-block:: python + + import requests + + base_url = "https://iso3166-2-api.vercel.app/api/" + input_country_name = "Tajikistan" #Seychelles, Uganda + all_data = requests.get(base_url + f'/country_name/{input_country_name}').json() all_data["TJ"] #subdivision data for Tajikistan + all_data["SC"] #subdivision data for Seychelles + all_data["UG"] #subdivision data for Uganda curl:: - $ curl -i https://iso3166-2-api.vercel.app/api/name/Tajikistan - $ curl -i https://iso3166-2-api.vercel.app/api/name/Seychelles - $ curl -i https://iso3166-2-api.vercel.app/api/name/Uganda + $ curl -i https://iso3166-2-api.vercel.app/api/country_name/Tajikistan + $ curl -i https://iso3166-2-api.vercel.app/api/country_name/Seychelles + $ curl -i https://iso3166-2-api.vercel.app/api/country_name/Uganda + +Accessing all subdivision data for a specific subdivision, using its subdivision name +------------------------------------------------------------------------------------- + +For example, accessing all subdivision data for Saarland, Brokopondo, Delaware: + +Python Requests: + +.. code-block:: python + + import requests + + base_url = "https://iso3166-2-api.vercel.app/api/" + input_name = "Saarland" #Brokopondo, Delaware (DE-SL, SR-BR, US-DE) + all_data = requests.get(base_url + f'/name/{input_name}').json() + + all_data["DE-SL"] #subdivision data for Saarland + all_data["SR-BR"] #subdivision data for Brokopondo + all_data["US-DE"] #subdivision data for Delaware + +curl:: + + $ curl -i https://iso3166-2-api.vercel.app/api/name/Saarland + $ curl -i https://iso3166-2-api.vercel.app/api/name/Brokopondo + $ curl -i https://iso3166-2-api.vercel.app/api/name/Delaware + +.. **Error: Not Found Response** + +.. { +.. message: "Invalid 2 letter alpha-2 code input: ZZ.", +.. path: "https://iso3166-2-api-amckenna41.vercel.app/api/alpha2/zz", +.. status: 400 +.. } .. note:: A demo of the software and API is available `here `_. \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 263129e..739912a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,9 +7,9 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'iso3166-2' -copyright = '2023, AJ McKenna' +copyright = '2024, AJ McKenna' author = 'AJ McKenna' -release = '1.4.0' +release = '1.5.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration @@ -19,11 +19,9 @@ templates_path = ['_templates'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - - # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output # html_theme = 'alabaster' html_theme = 'sphinx_rtd_theme' -html_static_path = ['_static'] +html_static_path = ['_static'] \ No newline at end of file diff --git a/docs/contributing.rst b/docs/contributing.rst index 8eb6df8..b9d7e2e 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -7,4 +7,4 @@ repository's `Issues `_ tab. If you would like to contribute any functional/feature changes to the software, please make a pull request on the `repository `_. -Any other queries or issues, please contact me via email: amckenna41@qub.ac.uk :) \ No newline at end of file +Any other queries or issues, please contact me via email: amckenna41@qub.ac.uk 😁 \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 066614d..fe9d50c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,10 +8,10 @@ used to access all of the world's ISO 3166-2 subdivision data. Here, subdivision interchangably with regions/states/provinces etc. Currently, the package and API supports data from 250 countries/territories, according to the ISO 3166-1 standard. The software uses another custom-built Python package called `iso3166-updates `_ -to ensure all the subdivision data is accurate, reliable and up-to-date. The ISO 3166-2 was first published in 1998 -and as of November 2023 there are 5,039 codes defined in it. +to ensure all the subdivision data is **accurate, reliable and up-to-date**. The ISO 3166-2 was first published in 1998 +and as of November 2023 there are **5,039** codes defined in it. -There are 7 main data attributes available for each subdivision within the ISO 3166-2 software: +There are 7 main data attributes available for each subdivision within the **iso3166-2** software: * Name - subdivsion name, as it is commonly known in English * Local name - subdivision name in local language @@ -26,36 +26,53 @@ There are 7 main data attributes available for each subdivision within the ISO 3 A demo of the software and accompanying API is available `here `_! - +Last Updated +============ +The ISO 3166-2 data was last updated on March 2023. A log of the latest ISO 3166-2 updates can be seen in the +`UPDATES.md `_ file in the repository. + +License +======= +**iso3166-2** is distributed under the MIT License. -Changelog -========= +.. Changelog +.. ========= + +.. Latest Version: **1.5.0**. -Latest Version: **1.4.0**. +.. ****** +.. v1.5.0 +.. ****** +.. * Change data encapsulation making data accessible only via creating an instance of ISO3166_2 class +.. * Update search functionality algorithm in search functionality - chaning from difflib to thefuzz library +.. * Bug fixes for search by subdivision name algorithm in software and API +.. * Update unit tests to reflect changes made to the softwar +.. * Update name of iso3166_2_updates dir +.. * Update software and API documentation -****** -v1.4.0 -****** -* Add readthedocs documentation -* Add search by subdivision name functionality -* Add custom subdivision functionality -* Update requirements.txt to include iso3166-2 -* Update software pypi description -* Update unit tests to reflect changes made to the software -* Fix issue with accessing the function outputs for test_get_iso3166_2 unit tests +.. ****** +.. v1.4.0 +.. ****** +.. * Add readthedocs documentation +.. * Add search by subdivision name functionality +.. * Add custom subdivision functionality +.. * Update requirements.txt to include iso3166-2 +.. * Update software pypi description +.. * Update unit tests to reflect changes made to the software +.. * Fix issue with accessing the function outputs for test_get_iso3166_2 unit tests -****** -v1.3.0 -****** -* Add UPDATES.md file that outlines all subdivision added to json object -* Add iso3166-2-updates/subdivision_updates.csv file that lists changes that need to be applied to the ISO 3166-2 object taken from the `iso3166-updates `_ software -* Add iso3166-2-updates/local_names.csv that lists the names of subdivisions in their local language -* Add iso3166_2_scripts/update_subdivisions.py script that is used to parse the subdivision_updates.csv file and append the data to the main object -* Update unit tests to reflect changes made to the software -* Update software keywords and description -* Update API.md -* Fix subdivision sorting in main software script -* Fix list of requirements for get_iso3166_2.py script +.. ****** +.. v1.3.0 +.. ****** +.. * Add UPDATES.md file that outlines all subdivision added to json object +.. * Add iso3166-2-updates/subdivision_updates.csv file that lists changes that need to be applied to the ISO 3166-2 object taken from the `iso3166-updates `_ software +.. * Add iso3166-2-updates/local_names.csv that lists the names of subdivisions in their local language +.. * Add iso3166_2_scripts/update_subdivisions.py script that is used to parse the subdivision_updates.csv file and append the data to the main object +.. * Update unit tests to reflect changes made to the software +.. * Update software keywords and description +.. * Update API.md +.. * Fix subdivision sorting in main software script +.. * Fix list of requirements for get_iso3166_2.py script Contents ======== diff --git a/docs/usage.rst b/docs/usage.rst index bb89e6c..fd11730 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -1,6 +1,8 @@ Usage ===== +Below are some usage examples for the various functionalities of the **iso3166-2** software. + .. _installation: Installation @@ -20,89 +22,98 @@ Alternatively, you can clone the repo and run ``setup.py``: cd iso3166_2 python3 setup.py install -Accessing subdivision data per country --------------------------------------- -To access all subdivision data for a given country you need to access the ``country`` object instance of the ``ISO3166_2`` class, -and then passing in the sought 2 letter alpha-2 or 3 letter alpha-3 country code. You can then access a specific subdivision's -data by passing in the subdivision's ISO 3166-2 code. +Accessing subdivision data per country using its ISO 3166-1 alpha codes +----------------------------------------------------------------------- +To access all subdivision data for a given country you should create an instance of the ``ISO3166_2`` class. This instance is subscriptable such that you can get +the sought country subdivision data via its ISO 3166-1 **2 letter alpha-2, 3 letter alpha-3 or numeric country codes**. You can also search for a specific subdivision +via its subdivision name. -For example, accessing all Canadian (CA) subdivision data: +For example, accessing all Canadian (CA, CAN, 124) subdivision data: .. code-block:: python - import iso3166_2 as iso + from iso3166_2 import * - iso.country["CA"] + #crete instance of ISO3166_2 class + iso = ISO3166_2() + + iso["CA"] #{'CA-AB': {'name': 'Alberta', 'localName': 'Alberta', 'type': 'Province', 'parentCode': None,...} #CA-AB Alberta - ca_alberta = iso.country["CA"]["CA-AB"] + ca_alberta = iso["CA"]["CA-AB"] ca_alberta.name #Alberta ca_alberta.type #Province ca_alberta.flag_url #https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/CA/CA-AB.svg #CA-MB Manitoba - ca_manitoba = iso.country["CA"]["CA-MB"] + ca_manitoba = iso["CA"]["CA-MB"] ca_manitoba.name #Manitoba ca_manitoba.parentCode #null ca_manitoba.localName #Manitoba #CA-NS Nova Scotia - ca_nova_scotia = iso.country["CA"]["CA-NS"] + ca_nova_scotia = iso["CA"]["CA-NS"] ca_nova_scotia.name #Nova Scotia ca_nova_scotia.type #Province ca_nova_scotia.flagUrl #https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/CA/CA-NS.svg -Accessing all Danish (DK) subdivision data: +Accessing all Danish (DK, DNK, 208) subdivision data: .. code-block:: python - import iso3166_2 as iso + from iso3166_2 import * + + #crete instance of ISO3166_2 class + iso = ISO3166_2() - iso.country["DK"] + iso["DNK"] #{'DK-81': {'name': 'Nordjylland', 'localName': 'Nordjylland', 'type': 'Region', 'parentCode': None,...} #DK-81 Nordjylland - dk_nordjylland = iso.country["DK"]["DK-81"] + dk_nordjylland = iso["DNK"]["DK-81"] dk_nordjylland.name #Nordjylland dk_nordjylland.latLng #[56.831, 9.493] dk_nordjylland.type #Region #DK-84 Hovedstaden - dk_hovedstaden = iso.country["DK"]["DK-84"] + dk_hovedstaden = iso["DNK"]["DK-84"] dk_hovedstaden.name #Hovedstaden dk_hovedstaden.flagUrl #https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DK/DK-84.svg dk_hovedstaden.parentCode #null #DK-85 Sjælland - dk_sjalland = iso.country["DK"]["DK-85"] + dk_sjalland = iso["DK"]["DNK-85"] dk_sjalland.name #Sjælland dk_sjalland.type #Region dk_sjalland.flagUrl #https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DK/DK-85.svg -Accessing all Estonian (EE) subdivision data: +Accessing all Estonian (EE, EST, 233) subdivision data: .. code-block:: python - import iso3166_2 as iso + from iso3166_2 import * + + #crete instance of ISO3166_2 class + iso = ISO3166_2() - iso.country["EE"] + iso["233"] #{'EE-37': {'name': 'Harjumaa', 'localName': 'Harjumaa', 'type': 'County', 'parentCode': None,...} #EE-39 Hiiumaa - ee_hiiumaa = iso.country["EE"]["EE-39"] + ee_hiiumaa = iso["233"]["EE-39"] ee_hiiumaa.name #Hiiumaa ee_hiiumaa.localName #Hiiumaa ee_hiiumaa.latLng #[58.924, 22.592] #EE-130 Alutaguse - ee_alutaguse = iso.country["EE"]["EE-130"] + ee_alutaguse = iso["233"]["EE-130"] ee_alutaguse.name #Alutaguse ee_alutaguse.parentCode #EE-45 ee_alutaguse.flagUrl #https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/EE/EE-130.svg #EE-338 Kose - ee_kose = iso.country["EE"]["EE-338"] + ee_kose = iso["233"]["EE-338"] ee_kose.name #Kose ee_kose.type #Rural municipality ee_kose.parentCode #EE-37 @@ -110,7 +121,8 @@ Accessing all Estonian (EE) subdivision data: Accessing subdivision data for all countries -------------------------------------------- -To access ALL subdivision data for ALL available countries, you need to access the ``all`` attribute within the ``country`` object instance of the ``ISO3166_2`` class. You can then access an individual country's subdivisiond data by passing in the sought 2 letter alpha-2 or 3 letter alpha-3 country code. +To access ALL subdivision data for ALL available countries, you need to access the ``all`` attribute within the object instance of the ``ISO3166_2`` class. +You can then access an individual country's subdivision data by passing in the sought ISO 3166-1 **2 letter alpha-2, 3 letter alpha-3 or numeric country code**. .. code-block:: python @@ -121,12 +133,14 @@ To access ALL subdivision data for ALL available countries, you need to access t all_data["LU"] #all subdivision data for Luxembourg all_data["PW"] #all subdivision data for Palau all_data["TUV"] #all subdivision data for Tuvalu - all_data["WLF"] #all subdivision data for Wallis & Futuna + all_data["UKR"] #all subdivision data for Ukraine + all_data["876"] #all subdivision data for Wallis & Futuna + all_data["716"] #all subdivision data for Zimbabwe Adding custom subdivisions -------------------------- -Add or delete a custom subdivision to an existing country on the main iso3166-2.json object. The purpose of this functionality is similar to +Add or delete a custom subdivision to an existing country on the main **iso3166-2.json** object. The purpose of this functionality is similar to that of the user-assigned code elements of the ISO 3166-1 standard. Custom subdivisions and subdivision codes can be used for in-house/bespoke applications that are using the **iso3166-2** software but require additional custom subdivisions to be represented. If the input custom subdivision code already exists then an error will be raised, otherwise it will be appended to the object. @@ -136,13 +150,17 @@ parameters but also setting the ``delete`` parameter to 1/True. .. code-block:: python - import iso3166_2 as iso + from iso3166_2 import * #adding custom Belfast province to Ireland (IE) - iso.country.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) + iso.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) #adding custom Mariehamn province to Aland Islands (AX) - iso.country.custom_subdivision("AX", "AX-M", name="Mariehamn", local_name="Maarianhamina", type="province", lat_lng=[60.0969, 19.934], parent_code=None, flag_url=None) + iso.custom_subdivision("AX", "AX-M", name="Mariehamn", local_name="Maarianhamina", type="province", lat_lng=[60.0969, 19.934], parent_code=None, flag_url=None) + + #deleting above custom subdivisions from object + iso.custom_subdivision("IE", "IE-BF", delete=1) + iso.custom_subdivision("AX", "AX-M", delete=1) .. warning:: When adding a custom subdivision the software will be out of sync with the official ISO 3166-2 dataset, therefore its important to keep track @@ -152,19 +170,20 @@ parameters but also setting the ``delete`` parameter to 1/True. Searching for a subdivision --------------------------- -The ``search()`` function allows you to search for a specific subdivision via its subdivision name. The -search functionality will search over all subdivisions in the object, returning either a subdivision -with the exact match or subdivisions whose names approximately match the sought input name. +The ``search()`` function allows you to search for a specific subdivision via its subdivision name. The +search functionality uses a fuzzy search algorithm via "thefuzz" package, searching for subdivisions with +an exact name match or those with an approximate name match, according to a score via the "likeness" input +parameter. .. code-block:: python - import iso3166_2 as iso + from iso3166_2 import * #searching for the Monaghan county in Ireland (IE-MN) - returning exact matching subdivision - iso.country.search("Monaghan", any=False) + iso.search("Monaghan") - #searching for any subdivisions that have "Southern" in their name - iso.country.search("Southern", any=True) + #searching for any subdivisions that have "Southern" in their name, using a likeness score of 0.7 + iso.search("Southern", likeness=0.7) .. note:: A demo of the software and API is available `here `_. \ No newline at end of file diff --git a/iso3166_2/README.md b/iso3166_2/README.md index c99adc7..ce23bbb 100644 --- a/iso3166_2/README.md +++ b/iso3166_2/README.md @@ -4,80 +4,97 @@ [![CircleCI](https://dl.circleci.com/status-badge/img/gh/amckenna41/iso3166-2/tree/main.svg?style=svg&circle-token=f399bc09886e183a1866efe27808ebecb21a5ea9)](https://dl.circleci.com/status-badge/redirect/gh/amckenna41/iso3166-2/tree/main) [![PythonV](https://img.shields.io/pypi/pyversions/iso3166-2?logo=2)](https://pypi.org/project/iso3166-2/) [![Platforms](https://img.shields.io/badge/platforms-linux%2C%20macOS%2C%20Windows-green)](https://pypi.org/project/iso3166-2/) +[![Documentation Status](https://readthedocs.org/projects/iso3166-2/badge/?version=latest)](https://iso3166-2.readthedocs.io/en/latest/?badge=latest) [![License: MIT](https://img.shields.io/github/license/amckenna41/iso3166-2)](https://opensource.org/licenses/MIT) [![Issues](https://img.shields.io/github/issues/amckenna41/iso3166-2)](https://github.com/amckenna41/iso3166-2/issues) +Documentation +------------- +Documentation for installation and usage of the software is availble on the readthedocs platform: + +https://iso3166-2.readthedocs.io/en/latest/ + Usage ----- -The main JSON iso3166-2.json contains each country's ISO 3166-2 subdivision data and attributes. In the main module, iso3166_2.py, all data from the iso3166-2.json is accessible via the `iso.country` object. +The main JSON iso3166-2.json contains each country's ISO 3166-2 subdivision data and attributes. The data can be accessed after creating an instance of the ISO3166_2 class, with the instance being subscriptable such that data can be accessed via their ISO 3166-1 alpha-2, alpha-3 or numeric country codes. -Import ISO3166_2 class and access the subdivision data: +**Import ISO3166_2 class and access the subdivision data:** ```python -import iso3166_2 as iso +from iso3166_2 import * + +#create instance of ISO3166_2 class +iso = ISO3166_2() #access all country's subdivision data -canada_iso3166_2 = iso.country["CA"] -denmark_iso3166_2 = iso.country["DK"] -estonia_iso3166_2 = iso.country["EE"] -fiji_haiti_guyana_iso3166_2 = iso.country["FJ, HT, GY"] -peru_iso3166_2 = iso.country["PE"] +canada_iso3166_2 = iso["CA"] +denmark_iso3166_2 = iso["DK"] +estonia_iso3166_2 = iso["EST"] +peru_iso3166_2 = iso["PER"] +fiji_haiti_guyana_iso3166_2 = iso["FJ, HTI, 328"] ``` -Get a specific subdivision's info: +**Get a specific subdivision's info:** ```python -import iso3166_2 as iso +from iso3166_2 import * + +#create instance of ISO3166_2 class +iso = ISO3166_2() -canada_iso3166_2['CA-AB'] #Alberta subdivision -denmark_iso3166_2['DK-81'] #Nordjylland subdivision -estonia_iso3166_2['EE-899'] #Viljandi subdivision -fiji_haiti_guyana_iso3166_2['FJ-03'] #Cakaudrove subdivision -peru_iso3166_2['PE-AMA'] #Amarumayu subdivision +iso["CA"]['CA-AB'] #Alberta subdivision +iso["DK"]['DK-81'] #Nordjylland subdivision +iso["EST"]['EE-899'] #Viljandi subdivision +iso["FJI"]['FJ-03'] #Cakaudrove subdivision +iso["604"]['PE-AMA'] #Amarumayu subdivision ``` -Get individual attribute values per subdivision: +**Get individual attribute values per subdivision:** ```python -import iso3166_2 as iso +from iso3166_2 import * -canada_iso3166_2['CA-AB'].latLng #Alberta subdivision latitude/longitude -denmark_iso3166_2['DK-81'].flagUrl #Nordjylland subdivision flag URL -estonia_iso3166_2['EE-899'].name #Viljandi subdivision name -fiji_haiti_guyana_iso3166_2['FJ-03'].type #Cakaudrove subdivision type -peru_iso3166_2['PE-AMA'].parentCode #Amarumayu subdivision +#create instance of ISO3166_2 class +iso = ISO3166_2() + +iso["CA"]['CA-AB'].latLng #Alberta subdivision latitude/longitude +iso["DK"]['DK-81'].flagUrl #Nordjylland subdivision flag URL +iso["EST"]['EE-899'].name #Viljandi subdivision name +iso["FJI"]['FJ-03'].type #Cakaudrove subdivision type +iso["604"]['PE-AMA'].parentCode #Amarumayu subdivision ``` -Get all ISO 3166-2 data for all countries: +**Get all ISO 3166-2 data for all countries:** ```python -import iso3166_2 as iso +from iso3166_2 import * + +#create instance of ISO3166_2 class +iso = ISO3166_2() #get all subdivision data using all attribute -iso.country.all +iso.all ``` - -Adding a custom subdivision to the iso3166-2 object. The context for this -functionality is similar to that of the user-assigned code elements of the -ISO 3166-1 standard. Custom subdivisions and subdivision codes can be used -for in-house/bespoke applications that are using the iso3166-2 software but -require additional custom subdivisions to be represented: +**Searching for a specific subdivision via its subdivision name attribute: the search functionality will search over all subdivisions in the object, +returning either a subdivision with the exact match or subdivisions whose +names approximately match the sought input name according to the likeness +input parameter:** ```python -import iso3166_2 as iso +from iso3166_2 import * -#adding custom Belfast province to Ireland -iso.country.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) +#searching for the Monaghan county in Ireland (IE-MN) - returning exact matching subdivision +iso.search("Monaghan") -#adding custom Mariehamn province to Aland Islands (AX) -iso.country.custom_subdivision("AX", "AX-M", name="Mariehamn", local_name="Maarianhamina", type="province", lat_lng=[60.0969, 19.934], parent_code=None, flag_url=None) +#searching for any subdivisions that have "Southern" in their name, using a likeness score of 0.8 +iso.country.search("Southern", likeness=0.8) ``` -Searching for a specific subdivision via its subdivision name attribute. The -search functionality will search over all subdivisions in the object, -returning either a subdivision with the exact match or subdivisions whose -names approximately match the sought input name: +**Adding a custom subdivision to the iso3166-2 object: the context for this functionality is similar to that of the user-assigned +code elements of the ISO 3166-1 standard. Custom subdivisions and subdivision +codes can be used for in-house/bespoke applications that are using the +iso3166-2 software but require additional custom subdivisions to be represented:** ```python -import iso3166_2 as iso +from iso3166_2 import * -#searching for the Monaghan county in Ireland (IE-MN) - returning exact matching subdivision -iso.country.search("Monaghan", any=False) +#adding custom Belfast province to Ireland +iso.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) -#searching for any subdivisions that have "Southern" in their name -iso.country.search("Southern", any=True) +#deleting above custom subdivision from object +iso.custom_subdivision("IE", "IE-BF", delete=1) ``` \ No newline at end of file diff --git a/iso3166_2/__init__.py b/iso3166_2/__init__.py index dbc4fd7..55908c3 100644 --- a/iso3166_2/__init__.py +++ b/iso3166_2/__init__.py @@ -2,7 +2,7 @@ #software metadata __name__ = 'iso3166-2' -__version__ = "1.4.0" +__version__ = "1.5.0" __description__ = "A lightweight Python package, and accompanying API, that can be used to access all of the world's most up-to-date and accurate ISO 3166-2 subdivision data, including: name, local name, code, parent code, type, latitude/longitude and flag." __author__ = 'AJ McKenna, https://github.com/amckenna41' __authorEmail__ = 'amckenna41@qub.ac.uk' @@ -12,5 +12,5 @@ __download_url__ = "https://github.com/amckenna41/iso3166-2/archive/refs/heads/main.zip" __status__ = 'Development' __keywords__ = ["iso", "iso3166", "beautifulsoup", "python", "pypi", "countries", "subdivisions", - "country codes", "iso3166-2", "iso3166-1", "alpha-2", "iso3166-updates"] + "country codes", "iso3166-2", "iso3166-1", "alpha-2", "iso3166-updates", "regions"] __test_suite__ = "tests" \ No newline at end of file diff --git a/iso3166_2/iso3166_2.py b/iso3166_2/iso3166_2.py index 14c3eef..a347383 100644 --- a/iso3166_2/iso3166_2.py +++ b/iso3166_2/iso3166_2.py @@ -2,8 +2,10 @@ import sys import json import iso3166 +from unidecode import unidecode +from thefuzz import process, fuzz +from urllib.parse import unquote_plus import platform -from difflib import get_close_matches import natsort from collections import OrderedDict @@ -11,83 +13,100 @@ class ISO3166_2(): """ This class is used to access all the ISO 3166-2 country subdivision data and attributes. All of the country data is stored in the iso3166-2.json, including the country's subdivision - name, local name, type, code, parent code, lat/longitude and URL to its flag (if applicable). - The JSON is generated using the get_iso3166_2.py script in the iso3166_2_scripts directory. - All of the keys and objects in the JSON are accessible via dot notation via the Map class. + name, local name, type, code, parent code, latitude/longitude and URL to its flag (if applicable). + All of the keys and objects in the JSON are accessible via dot notation via the Map class. The + desired subdivision data can be retrieved using its ISO 3166-1 alpha-2, alpha-3 or numeric + country codes; a comma seperate list of subdivision codes can also be input. Also you can + retrieve subdivisions via a built-in search functionality via its subdivision name, searching + for exact matches or matches based on a 'likeness' score. There is also functionality to add custom subdivisions to the data object, allowing for the utilisation of the iso3166-2 software with custom subdivisions required for in-house/bespoke - applications. Also you can search for a specific subdivision via its name, either searching - for an exact subdivision match or return a list of approximately matching subdivisions. + applications. Currently, this package supports 5,039 individual subdivisions from 250 countries/territories, according to the ISO 3166-1 standard, as of November 2023. Parameters ========== - None + :country_code: str (default="") + ISO 3166-1 alpha-2, alpha-3 or numeric country code to get subdivision data for. A list + of country codes can also be input. If the alpha-3 or numeric codes are input, they are + converted into their alpha-2 counterparts. Methods ======= - subdivision_codes(alpha2_code=""): - return a list or dict of all ISO 3166-2 subdivision codes for one or more - countries specified by their 2 letter alpha-2 code. - subdivision_names(alpha2_code=""): + subdivision_codes(alpha_code=""): + return a list or dict of all ISO 3166-2 subdivision codes for one or more countries + specified by their 2 letter alpha-2 code, 3 letter alpha-3 or numeric code. + subdivision_names(alpha_code=""): return a list or dict of all ISO 3166-2 subdivision names for one or more - countries specified by their 2 letter alpha-2 code. - subdivision_parent_codes(alpha2_code=""): - return a list or dict of all ISO 3166-2 subdivision parent codes for one - or more countries specified by their 2 letter alpha-2 code. - custom_subdivision(alpha2_code="", subdivision_code="", name="", local_name="", type="", + countries specified by their 2 letter alpha-2 code, 3 letter alpha-3 or numeric code. + custom_subdivision(alpha_code="", subdivision_code="", name="", local_name="", type="", lat_lng=[], parent_code=None, flag_url=None, delete=0): add or delete a custom subdivision to an existing country on the main iso3166-2.json object. Custom subdivisions and subdivision codes can be used for in-house/bespoke applications that are using the iso3166-2 software but require additional custom subdivisions to be represented. - search(name="", any=False): + search(name="", likeness=1): searching for a particular subdivision and its data using its name. - __getitem__(alpha2_code): + convert_to_alpha2(alpha_code): + converts an ISO 3166 country's 3 letter alpha-3 code or numeric code into its 2 + letter alpha-2 counterpart. + __getitem__(alpha_code): return all of a ISO 3166 country's subdivision data by making the class - subscriptable, according to its 2 letter alpha-2 code. + subscriptable, according to its 2 letter alpha-2 code, 3 letter alpha-3 or + numeric code. Usage ===== - import iso3166_2 as iso + from iso3166_2 import * + + #get ALL subdivision data for ALL countries + all_subdivisions = ISO3166_2() #get ALL subdivision data for Lithuania, Namibia, Paraguay and Turkmenistan - iso.country['LT'].subdivisions - iso.country['NA'].subdivisions - iso.country['PY'].subdivisions - iso.country['TM'].subdivisions + lt_subdivisions = ISO3166_2("LT") + na_subdivisions = ISO3166_2("NA") + pry_subdivisions = ISO3166_2("PRY") + tm_subdivisions = ISO3166_2("795") #get subdivision names, local names, types, parent codes, flag urls and lat/longitudes # for GB-ANS, GB-BPL, GB-NTH, GB-WGN and GB-ZET subdivision codes for the UK - iso.country['GB'].subdivisions['GB-ANS'].name - iso.country['GB'].subdivisions['GB-BAS'].localName - iso.country['GB'].subdivisions['GB-BPL'].type - iso.country['GB'].subdivisions['GB-NTH'].parentCode - iso.country['GB'].subdivisions['GB-WGN'].flagUrl - iso.country['GB'].subdivisions['GB-ZET'].latLng + gb_subdivisions = ISO3166_2("GB") + + gb_subdivisions['GB-ANS'].name + gb_subdivisions['GB-BAS'].localName + gb_subdivisions['GB-BPL'].type + gb_subdivisions['GB-NTH'].parentCode + gb_subdivisions['GB-WGN'].flagUrl + gb_subdivisions['GB-ZET'].latLng #get list of all subdivision codes for Botswana - iso.subdivision_codes("BW") + bw_subdivisions = ISO3166_2("BW") + bw_subdivisions.subdivision_codes() #get list of all subdivision names for Japan - iso.subdivision_names("JP") + jpn_subdivisions = ISO3166_2("JPN") + jpn_subdivisions.subdivision_names() #adding custom Belfast province to Ireland - iso.country.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) + all_subdivisions = ISO3166_2() + all_subdivisions.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) #searching for the Monaghan county in Ireland (IE-MN) - returning exact matching subdivision - iso.country.search("Monaghan", any=False) + all_subdivisions = ISO3166_2() + all_subdivisions.search("Monaghan") - #searching for any subdivisions that have "Southern" in their name - iso.country.search("Southern", any=True) + #searching for any subdivisions that have "Southern" in their name, with a likeness score of 0.8 + all_subdivisions = ISO3166_2() + all_subdivisions.search("Southern", any=True, likeness=0.8) """ - def __init__(self): + def __init__(self, country_code=""): + self.country_code = country_code self.iso3166_json_filename= "iso3166-2.json" - self.data_folder = "iso3166-2-data" + self.data_folder = "iso3166_2_data" #get module path self.iso3166_2_module_path = os.path.dirname(os.path.abspath(sys.modules[self.__module__].__file__)) @@ -96,7 +115,7 @@ def __init__(self): if not (os.path.isfile(os.path.join(self.iso3166_2_module_path, self.data_folder, self.iso3166_json_filename))): raise OSError("Issue finding {} in data dir {}.".format(self.iso3166_json_filename, self.data_folder)) - #open iso3166-2 json file and load it into class variable, loading in a JSON is different in Windows & Unix/Linux systems + #importing all subdivision data from JSON, open iso3166-2 json file and load it into class variable, loading in a JSON is different in Windows & Unix/Linux systems if (platform.system() != "Windows"): with open(os.path.join(self.iso3166_2_module_path, self.data_folder, self.iso3166_json_filename)) as iso3166_2_json: self.all = json.load(iso3166_2_json) @@ -104,91 +123,128 @@ def __init__(self): with open(os.path.join(self.iso3166_2_module_path, self.data_folder, self.iso3166_json_filename), encoding="utf-8") as fp: self.all = json.loads(fp.read()) + #if input country code param set, iterate over data object and get subdivision data for specified input/inputs + if (self.country_code != ""): + temp_subdivision_data = {} + self.country_code = self.country_code.upper().replace(" ", "").split(',') + for code in range(0, len(self.country_code)): + #if 3 letter alpha-3 or numeric codes input then convert to corresponding alpha-2, if invalid code then raise error + if (len(self.country_code[code]) == 3): + temp_alpha2_code = self.convert_to_alpha2(self.country_code[code]) + if (temp_alpha2_code is None and self.country_code[code].isdigit()): + raise ValueError("Invalid ISO 3166-1 numeric country code input, cannot convert into corresponding alpha-2 code: {}.".format(self.country_code[code])) + if (temp_alpha2_code is None): + raise ValueError("Invalid ISO 3166-1 alpha-3 country code input, cannot convert into corresponding alpha-2 code: {}.".format(self.country_code[code])) + self.country_code[code] = temp_alpha2_code + + #raise error if invalid alpha-2 code input + if not (self.country_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))) or not (self.country_code[code] in list(self.all.keys())): + raise ValueError("Invalid alpha-2 country code input: {}.".format(self.country_code[code])) + + #create temporary subdivision data object + temp_subdivision_data[self.country_code[code]] = {} + temp_subdivision_data[self.country_code[code]] = self.all[self.country_code[code]] + + #delete existing 'all' class attribute that currently has all subdivision data for all countries, which aren't needed if input country is specified + del self.all + + #set 'all' class attribute to the subdivision data from input country/countries + self.all = temp_subdivision_data + #get list of all countries by their 2 letter alpha-2 code - self.alpha_2 = sorted(list(iso3166.countries_by_alpha2.keys())) + #self.alpha_2 = sorted(list(iso3166.countries_by_alpha2.keys())) + + #get list of all countries by their 3 letter alpha-3 code + #self.alpha_3 = sorted(list(iso3166.countries_by_alpha3.keys())) + + #get list of all countries by their numeric code + #self.numeric = sorted(list(iso3166.countries_by_numeric.keys())) #list of data attributes available per subdivision self.attributes = ["name", "localName", "type", "parentCode", "latLng", "flagUrl"] - def subdivision_codes(self, alpha2_code=""): + def subdivision_codes(self, alpha_code=""): """ Return a list or dict of all ISO 3166-2 subdivision codes for one or more - countries specified by their 2 letter alpha-2 code. If a single country - input then return list of subdivison codes, if multiple passed in then return - a dict of all countries subdivision codes. The function can also accept the 3 - letter alpha-3 code for a country which is converted into its 2 letter alpha-2 - counterpart. If no value passed into parameter then return dict of all - subdivision codes for all countries. If invalid country code input then raise - error. + countries specified by their 2 letter alpha-2, 3 letter alpha-3 or numeric + code. If the alpha-3 or numeric codes are input, these are converted into + their corresponding alpha-2 country codes. If a single country input then + return list of subdivison codes, if multiple passed in then return a dict + of all countries subdivision codes. If no value passed into parameter then + return dict of all subdivision codes for all countries. If invalid country + code input then raise error. Parameters ========== - :alpha2_code: str (default="") - one or more 2 letter ISO 3166-1 alpha-2 codes; the 3 letter alpha-3 country - code can also be accepted. If no value input then all alpha-2 country codes - will be used. + :alpha_code: str (default="") + one or more 2 letter ISO 3166-1 alpha-2, 3 letter alpha-3 or numeric + country codes. If no value input then all alpha-2 country codes will + be used. Returns ======= :subdivision_codes_: list/dict list of a country's ISO 3166-2 subdivision codes. Or dict of all country's - subdivision names if no value passed into parameter. + subdivision codes if no value passed into parameter. """ subdivision_codes_ = {} #if no value passed into parameter, return all subdivision codes for all countries - if (alpha2_code == ""): + if (alpha_code == ""): #iterate over all subdivision ISO 3166-2 data, append to subdivision codes dict for key, _ in self.all.items(): subdivision_codes_[key] = list(self.all[key]) - return subdivision_codes_ + #return list of subdivision codes for country if one country in 'self.all' attribute, else return dict + if (len(list(self.all.keys())) == 1): + return subdivision_codes_[list(self.all.keys())[0]] + else: + return subdivision_codes_ else: - #seperate list of alpha-2 codes into iterable comma seperated list - alpha2_code = alpha2_code.replace(' ', '').split(',') - - #iterate over all input alpha-2 codes, append their subdivision codes to dict - for code in range(0, len(alpha2_code)): - - #if 3 letter alpha-3 codes input then convert to corresponding alpha-2, else raise error - if (len(alpha2_code[code]) == 3): - temp_alpha2_code = convert_to_alpha2(alpha2_code[code]) - if not (temp_alpha2_code is None): - alpha2_code[code] = temp_alpha2_code - else: - raise ValueError("Invalid alpha-3 code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha2_code[code])) - + #seperate list of alpha codes into iterable comma seperated list + alpha_code = alpha_code.upper().replace(' ', '').split(',') + + #iterate over all input alpha codes, append their subdivision codes to dict + for code in range(0, len(alpha_code)): + #if 3 letter alpha-3 or numeric codes input then convert to corresponding alpha-2, else raise error + if (len(alpha_code[code]) == 3): + temp_alpha_code = self.convert_to_alpha2(alpha_code[code]) + if (temp_alpha_code is None and alpha_code[code].isdigit()): + raise ValueError("Invalid ISO 3166-1 numeric country code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code[code])) + if (temp_alpha_code is None): + raise ValueError("Invalid ISO 3166-1 alpha-3 country code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code[code])) + alpha_code[code] = temp_alpha_code #raise error if invalid alpha-2 code input - if not (alpha2_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))): - raise ValueError("Invalid alpha-2 code input: {}.".format(alpha2_code[code])) - + if not (alpha_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))) or not (alpha_code[code] in list(self.all.keys())): + raise ValueError("Invalid alpha-2 country code input: {}.".format(alpha_code[code])) + #append list of subdivision codes to dict - subdivision_codes_[alpha2_code[code]] = list(self.all[alpha2_code[code]]) + subdivision_codes_[alpha_code[code]] = list(self.all[alpha_code[code]]) #sort subdivision codes in json objects in alphabetical order - subdivision_codes_[alpha2_code[code]] = sorted(subdivision_codes_[alpha2_code[code]]) + subdivision_codes_[alpha_code[code]] = sorted(subdivision_codes_[alpha_code[code]]) #if only one alpha-2 code input then return list of its subdivison codes else return dict object for all inputs - if len(alpha2_code) == 1: - return subdivision_codes_[alpha2_code[0]] + if len(alpha_code) == 1: + return subdivision_codes_[alpha_code[0]] else: return subdivision_codes_ - def subdivision_names(self, alpha2_code=""): + def subdivision_names(self, alpha_code=""): """ Return a list or dict of all ISO 3166-2 subdivision names for one or more countries - specified by their 2 letter alpha-2 code. If a single country input then return - list of input country's subdivision names, if multiple passed in return a dict of all - input countries subdivision names. The function can also accept the 3 letter alpha-3 - code for a country which is converted into its 2 letter alpha-2 counterpart. If no value - passed into parameter then return dict of all subdivision names for all countries. If - invalid country code input then raise error. + specified by their 2 letter alpha-2, 3 letter alpha-3 or numeric country code. If the + alpha-3 or numeric codes are input, these are converted into their corresponding + alpha-2 country codes. If a single country input then return list of input country's + subdivision names, if multiple passed in return a dict of all input countries + subdivision names. If no value passed into parameter then return dict of all subdivision + names for all countries. If invalid country code input then raise error. Parameters ========== - :alpha2_code: str (default="") - one or more 2 letter ISO 3166-1 alpha-2 codes; the 3 letter alpha-3 country - code can also be accepted. If no value input then all alpha-2 country codes - will be used. + :alpha_code: str (default="") + one or more 2 letter ISO 3166-1 alpha-2, 3 letter alpha-3 or numeric + country codes. If no value input then all alpha-2 country codes will + be used. Returns ======= @@ -199,260 +255,114 @@ def subdivision_names(self, alpha2_code=""): subdivision_names_ = {} #if no value passed into parameter, return all subdivision names for all countries - if (alpha2_code == ""): + if (alpha_code == ""): #iterate over all subdivision ISO 3166-2 data, append to subdivision names dict for key, _ in self.all.items(): subdivision_names_[key] = [self.all[key][country]["name"] for country in self.all[key]] - return subdivision_names_ + subdivision_names_[key] = sorted(subdivision_names_[key]) + #return list of subdivision names for country if one country in 'self.all' attribute, else return dict + if (len(list(self.all.keys())) == 1): + return subdivision_names_[list(self.all.keys())[0]] + else: + return subdivision_names_ else: - #seperate list of alpha-2 codes into iterable comma seperated list - alpha2_code = alpha2_code.replace(' ', '').split(',') - - #iterate over all input alpha-2 codes, append their subdivision names to dict - for code in range(0, len(alpha2_code)): - - #if 3 letter alpha-3 codes input then convert to corresponding alpha-2, else raise error - if (len(alpha2_code[code]) == 3): - temp_alpha2_code = convert_to_alpha2(alpha2_code[code]) - if not (temp_alpha2_code is None): - alpha2_code[code] = temp_alpha2_code - else: - raise ValueError("Invalid alpha-3 code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha2_code[code])) + #seperate list of alpha codes into iterable comma seperated list + alpha_code = alpha_code.upper().replace(' ', '').split(',') + + #iterate over all input alpha codes, append their subdivision names to dict + for code in range(0, len(alpha_code)): + #if 3 letter alpha-3 or numeric codes input then convert to corresponding alpha-2, else raise error + if (len(alpha_code[code]) == 3): + temp_alpha_code = self.convert_to_alpha2(alpha_code[code]) + if (temp_alpha_code is None and alpha_code[code].isdigit()): + raise ValueError("Invalid ISO 3166-1 numeric country code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code[code])) + if (temp_alpha_code is None): + raise ValueError("Invalid ISO 3166-1 alpha-3 country code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code[code])) + alpha_code[code] = temp_alpha_code #raise error if invalid alpha-2 code input - if not (alpha2_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))): - raise ValueError("Invalid alpha-2 code input: {}.".format(alpha2_code[code])) - + if not (alpha_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))) or not (alpha_code[code] in list(self.all.keys())): + raise ValueError("Invalid alpha-2 country code input: {}.".format(alpha_code[code])) + #append list of subdivision names to dict - subdivision_names_[alpha2_code[code]] = [self.all[alpha2_code[code]][x]["name"] for x in self.all[alpha2_code[code]]] + subdivision_names_[alpha_code[code]] = [self.all[alpha_code[code]][x]["name"] for x in self.all[alpha_code[code]]] #sort subdivision names in json objects in alphabetical order - subdivision_names_[alpha2_code[code]] = sorted(subdivision_names_[alpha2_code[code]]) + subdivision_names_[alpha_code[code]] = sorted(subdivision_names_[alpha_code[code]]) #if only one alpha-2 code input then return list of its subdivison names else return dict object for all inputs - if len(alpha2_code) == 1: - return subdivision_names_[alpha2_code[0]] + if len(alpha_code) == 1: + return subdivision_names_[alpha_code[0]] else: return subdivision_names_ - def subdivision_local_names(self, alpha2_code=""): + def subdivision_local_names(self, alpha_code=""): """ Return a list or dict of all ISO 3166-2 subdivision local names for one or more countries - specified by their 2 letter alpha-2 code. If a single country input then return - list of input country's subdivision local names, if multiple passed in return a dict of all - input countries subdivision local names. The function can also accept the 3 letter alpha-3 - code for a country which is converted into its 2 letter alpha-2 counterpart. If no value - passed into parameter then return dict of all subdivision local names for all countries. - If invalid country code input then raise error. + specified by their 2 letter alpha-2, 3 letter alpha-3 or numeric codes. If the alpha-3 + or numeric codes are input, these are converted into their corresponding alpha-2 country + codes. If a single country input then return list of input country's subdivision local + names, if multiple passed in return a dict of all input countries subdivision local names. + If no value passed into parameter then return dict of all subdivision local names for all + countries. If invalid country code input then raise error. Parameters ========== - :alpha2_code: str (default="") - one or more 2 letter ISO 3166-1 alpha-2 codes; the 3 letter alpha-3 country - code can also be accepted. If no value input then all alpha-2 country codes - will be used. + :alpha_code: str (default="") + one or more 2 letter ISO 3166-1 alpha-2 codes; the 3 letter alpha-3 country code can also + be accepted. If no value input then all alpha-2 country codes will be used. Returns ======= :subdivision_local_names_: list/dict - list or dict of input country's subdivision local names, if no value passed into - parameter then all country subdivision local name data is returned. + list or dict of input country's subdivision local names, if no value passed into parameter + then all country subdivision local name data is returned. """ subdivision_local_names_ = {} #if no value passed into parameter, return all subdivision local names for all countries - if (alpha2_code == ""): + if (alpha_code == ""): #iterate over all subdivision ISO 3166-2 data, append to subdivision local names dict for key, _ in self.all.items(): subdivision_local_names_[key] = [self.all[key][country]["localName"] for country in self.all[key]] - return subdivision_local_names_ + subdivision_local_names_[key] = sorted(subdivision_local_names_[key]) + #return list of subdivision local names for country if one country in 'self.all' attribute, else return dict + if (len(list(self.all.keys())) == 1): + return subdivision_local_names_[list(self.all.keys())[0]] + else: + return subdivision_local_names_ else: - #seperate list of alpha-2 codes into iterable comma seperated list - alpha2_code = alpha2_code.replace(' ', '').split(',') - - #iterate over all input alpha-2 codes, append their subdivision local names to dict - for code in range(0, len(alpha2_code)): - - #if 3 letter alpha-3 codes input then convert to corresponding alpha-2, else raise error - if (len(alpha2_code[code]) == 3): - temp_alpha2_code = convert_to_alpha2(alpha2_code[code]) - if not (temp_alpha2_code is None): - alpha2_code[code] = temp_alpha2_code - else: - raise ValueError("Invalid alpha-3 code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha2_code[code])) + #seperate list of alpha codes into iterable comma seperated list + alpha_code = alpha_code.upper().replace(' ', '').split(',') + + #iterate over all input alpha codes, append their subdivision local names to dict + for code in range(0, len(alpha_code)): + #if 3 letter alpha-3 or numeric codes input then convert to corresponding alpha-2, else raise error + if (len(alpha_code[code]) == 3): + temp_alpha_code = self.convert_to_alpha2(alpha_code[code]) + if (temp_alpha_code is None and alpha_code[code].isdigit()): + raise ValueError("Invalid ISO 3166-1 numeric country code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code[code])) + if (temp_alpha_code is None): + raise ValueError("Invalid ISO 3166-1 alpha-3 country code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code[code])) + alpha_code[code] = temp_alpha_code #raise error if invalid alpha-2 code input - if not (alpha2_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))): - raise ValueError("Invalid alpha-2 code input: {}.".format(alpha2_code[code])) - + if not (alpha_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))) or not (alpha_code[code] in list(self.all.keys())): + raise ValueError("Invalid ISO 3166-1 alpha-2 country code input: {}.".format(alpha_code[code])) + #append list of subdivision local names to dict - subdivision_local_names_[alpha2_code[code]] = [self.all[alpha2_code[code]][x]["localName"] for x in self.all[alpha2_code[code]]] + subdivision_local_names_[alpha_code[code]] = [self.all[alpha_code[code]][x]["localName"] for x in self.all[alpha_code[code]]] #sort subdivision local names in json objects in alphabetical order - subdivision_local_names_[alpha2_code[code]] = sorted(subdivision_local_names_[alpha2_code[code]]) + subdivision_local_names_[alpha_code[code]] = sorted(subdivision_local_names_[alpha_code[code]]) #if only one alpha-2 code input then return list of its subdivison local names else return dict object for all inputs - if len(alpha2_code) == 1: - return subdivision_local_names_[alpha2_code[0]] + if len(alpha_code) == 1: + return subdivision_local_names_[alpha_code[0]] else: return subdivision_local_names_ - - def subdivision_parent_codes(self, alpha2_code=""): - """ - Return a list or dict of all ISO 3166-2 subdivision parent codes for one or more countries - specified by their 2 letter alpha-2 code. If a single country is input then return list of - input country's subdivision parent codes, if multiple passed in return a dict of all input - country's subdivision parent codes. The function can also accept the 3 letter alpha-3 - code for a country which is converted into its 2 letter alpha-2 counterpart. If no value - passed into parameter then return dict of all subdivision parent codes for all countries. - If invalid country code/codes input then raise error. - - Parameters - ========== - :alpha2_code: str (default="") - one or more 2 letter ISO 3166-1 alpha-2 codes; the 3 letter alpha-3 country - code can also be accepted. If no value input then all alpha-2 country codes - will be used. - - Returns - ======= - :subdivision_parent_codes_: list/dict - list or dict of input country's subdivision parent codes, if no value passed into - parameter then all country subdivision name data is returned. - """ - subdivision_parent_codes_ = {} - - #if no value passed into parameter, return all subdivision parent codes for all countries - if (alpha2_code == ""): - #iterate over all subdivision ISO 3166-2 data, append to subdivision parent codes dict - for key, _ in self.all.items(): - subdivision_parent_codes_[key] = [self.all[key][country]["parentCode"] for country in self.all[key]] - return subdivision_parent_codes_ - else: - #seperate list of alpha-2 codes into iterable comma seperated list - alpha2_code = alpha2_code.replace(' ', '').upper().split(',') - - #iterate over all input alpha-2 codes, append their subdivision parent codes to dict - for code in range(0, len(alpha2_code)): - - #if 3 letter alpha-3 codes input then convert to corresponding alpha-2, else raise error - if (len(alpha2_code[code]) == 3): - temp_alpha2_code = convert_to_alpha2(alpha2_code[code]) - if not (temp_alpha2_code is None): - alpha2_code[code] = temp_alpha2_code - else: - raise ValueError("Invalid alpha-3 code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha2_code[code])) - - #raise error if invalid alpha-2 code input - if not (alpha2_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))): - raise ValueError("Invalid alpha-2 code input: {}.".format(alpha2_code[code])) - - #append list of subdivision parent codes to dict - subdivision_parent_codes_[alpha2_code[code]] = set([self.all[alpha2_code[code]][x]["parentCode"] for x in self.all[alpha2_code[code]]]) - - #remove any null values from list - subdivision_parent_codes_[alpha2_code[code]] = [parent_code for parent_code in subdivision_parent_codes_[alpha2_code[code]] if parent_code is not None] - #sort subdivision parent codes in json objects into alphabetical order - subdivision_parent_codes_[alpha2_code[code]] = sorted(subdivision_parent_codes_[alpha2_code[code]]) - - #if only one alpha-2 code input then return list of its subdivison parent codes else return dict object for all inputs - if len(alpha2_code) == 1: - return subdivision_parent_codes_[alpha2_code[0]] - else: - return subdivision_parent_codes_ - - def __getitem__(self, alpha2_code): - """ - Return all of a country's and subdivision data by making the class - subscriptable. A list of country data can be returned if a comma - seperated list of alpha-2 codes are input. The 2 letter alpha-2 code - is expected as input, although for redundancy, the 3 letter alpha-3 - code can also be input which will be converted into its alpha-2 - counterpart. If no value input then an error is raised. - - Parameters - ========== - :alpha2_code: str - 2 letter alpha-2 code for sought country/territory e.g (AD, EG, DE). - Multiple country codes can be input in a comma seperated list. Can - also accept the 3 letter alpha-3 code e.g (AND, EGT, DEU), this will - be converted into its alpha-2 counterpart. If no value input then an - error will be raised - - Returns - ======= - :country[alpha2_code]: dict - dict object of country/subdivision info for inputted alpha2_code. - - Usage - ===== - import iso3166_2 as iso - - #get subdivision info for Ethiopia - iso.country["ET"] - iso.country["ETH"] - iso.country["et"] - - #get subdivision info for Gabon - iso.country["GA"] - iso.country["GAB"] - iso.country["ga"] - - #get subdivision info for Rwanda - iso.country["RW"] - iso.country["RWA"] - iso.country["rw"] - - #get subdivision info for Haiti, Monaco and Namibia - iso.country["HT, MC, NA"] - iso.country["HTI, MCO, NAM"] - iso.country["ht, mc, na"] - """ - #raise type error if input isn't a string - if not (isinstance(alpha2_code, str)): - raise TypeError('Input parameter {} is not of correct datatype string, got {}.'\ - .format(alpha2_code, type(alpha2_code))) - - #stripping input of whitespace, uppercasing and seperating into comma seperated list - alpha2_code = alpha2_code.replace(' ', '').upper().split(',') - - #object to store country data - country = {} - - #iterate over all input alpha-2 codes, append their country and subdivision data to output object - for code in range(0, len(alpha2_code)): - - #if 3 letter alpha-3 codes input then convert to corresponding alpha-2, else raise error - if (len(alpha2_code[code]) == 3): - temp_alpha2_code = convert_to_alpha2(alpha2_code[code]) - if not (temp_alpha2_code is None): - alpha2_code[code] = temp_alpha2_code - else: - raise ValueError("Invalid alpha-3 code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha2_code[code])) - - #raise error if invalid alpha-2 code input - if not (alpha2_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))): - raise ValueError("Invalid alpha-2 code input: {}.".format(code)) - - #create instance of Map class so dict can be accessed via dot notation - country[alpha2_code[code]] = Map(self.all[alpha2_code[code]]) - - #iterate over nested dicts, convert into instances of Map class so they can be accessed via dot notation - for key in country[alpha2_code[code]].keys(): - if (isinstance(country[alpha2_code[code]][key], dict)): - country[alpha2_code[code]][key] = Map(country[alpha2_code[code]][key]) - - #convert country data object into Map class so it can be accessed via dot notation - country = Map(country) - - #if only one alpha-2 code input then return list of its country data and attributes else return dict object for all inputs - if len(alpha2_code) == 1: - return country[alpha2_code[0]] - else: - return country - - def custom_subdivision(self, alpha2_code="", subdivision_code="", name="", local_name="", type="", + def custom_subdivision(self, alpha_code="", subdivision_code="", name="", local_name="", type="", lat_lng=[], parent_code=None, flag_url=None, delete=0): """ Add or delete a custom subdivision to an existing country on the main iso3166-2.json @@ -463,14 +373,19 @@ def custom_subdivision(self, alpha2_code="", subdivision_code="", name="", local subdivision code already exists then an error will be raised, otherwise it will be appended to the object. + Note that this is a destructive yet temporary functionality. Adding a new custom + subdivision will make the dataset out of sync with the official ISO 3166-2 data, + therefore it is the user's responsibility to keep track of any custom subdivisions + and delete them when neccessary. + If the added subdivision is required to be deleted from the object, then you can call the same function with the alpha-2 and subdivision codes' parameters but also setting the 'delete' parameter to 1/True. Parameters ========== - :alpha2_code: str (default="") - 2 letter alpha-2 country code. + :alpha_code: str (default="") + ISO 3166-1 2 letter alpha-2, 3 letter alpha-3 or numeric country code. :subdivision_code: str (default="") custom subdivision code. :name: str (default="") @@ -495,17 +410,20 @@ def custom_subdivision(self, alpha2_code="", subdivision_code="", name="", local Usage ===== - import iso3166_2 as iso + from iso3166_2 import * + + #create instance of ISO3166_2() class + iso = ISO3166_2() #adding custom Republic of Molossia state to United States - iso.country.custom_subdivision("US", "US-ML", name="Republic of Molossia", local_name="", type="State", lat_lng=[39.236, -119.588], parent_code=None, flag_url="https://upload.wikimedia.org/wikipedia/commons/c/c3/Flag_of_the_Republic_of_Molossia.svg") + iso.custom_subdivision("US", "US-ML", name="Republic of Molossia", local_name="", type="State", lat_lng=[39.236, -119.588], parent_code=None, flag_url="https://upload.wikimedia.org/wikipedia/commons/c/c3/Flag_of_the_Republic_of_Molossia.svg") #adding custom Belfast province to Ireland - iso.country.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) + iso.custom_subdivision("IE", "IE-BF", name="Belfast", local_name="Béal Feirste", type="province", lat_lng=[54.596, -5.931], parent_code=None, flag_url=None) #deleting above custom subdivisions - iso.country.custom_subdivision("US", "US-ML", delete=1) - iso.country.custom_subdivision("IE", "IE-BF", delete=1) + iso.custom_subdivision("US", "US-ML", delete=1) + iso.custom_subdivision("IE", "IE-BF", delete=1) """ #open iso3166-2 json file and load it into class variable, loading in a JSON is different in Windows & Unix/Linux systems if (platform.system() != "Windows"): @@ -515,34 +433,12 @@ def custom_subdivision(self, alpha2_code="", subdivision_code="", name="", local with open(os.path.join(self.iso3166_2_module_path, self.data_folder, self.iso3166_json_filename), encoding="utf-8") as fp: all_subdivision_data = json.loads(fp.read()) - def convert_to_alpha2(alpha3_code): - """ - Convert an ISO 3166 country's 3 letter alpha-3 code into its - 2 letter alpha-2 counterpart. - - Parameters - ========== - :alpha3_code: str - 3 letter ISO 3166-1 alpha-3 country code. - - Returns - ======= - :iso3166.countries_by_alpha3[alpha3_code].alpha2: str - 2 letter ISO 3166 alpha-2 country code. - """ - #return None if 3 letter alpha-3 code not found - if not (alpha3_code in list(iso3166.countries_by_alpha3.keys())): - return None - else: - #use iso3166 package to find corresponding alpha-2 code from its alpha-3 code - return iso3166.countries_by_alpha3[alpha3_code].alpha2 - #raise type error if input isn't a string - if not (isinstance(alpha2_code, str)): - raise TypeError('Input alpha2_code parameter {} is not of correct datatype string, got {}.'.format(alpha2_code, type(alpha2_code))) + if not (isinstance(alpha_code, str)): + raise TypeError('Input alpha_code parameter {} is not of correct datatype string, got {}.'.format(alpha_code, type(alpha_code))) #uppercase and remove whitespace - alpha2_code = alpha2_code.upper().replace(' ', '') + alpha_code = alpha_code.upper().replace(' ', '') #raise type error if input isn't a string if not (isinstance(subdivision_code, str)): @@ -555,137 +451,297 @@ def convert_to_alpha2(alpha3_code): if not (isinstance(type, str)): raise TypeError('Input subdivision name parameter {} is not of correct datatype string, got {}.'.format(type, type(type))) - #if 3 letter alpha-3 codes input then convert to corresponding alpha-2, else raise error - if (len(alpha2_code) == 3): - temp_alpha2_code = convert_to_alpha2(alpha2_code) - if not (temp_alpha2_code is None): - alpha2_code = temp_alpha2_code - else: - raise ValueError("Invalid alpha-3 code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha2_code)) + #if 3 letter alpha-3 or numeric codes input then convert to corresponding alpha-2, else raise error + if (len(alpha_code) == 3): + temp_alpha_code = self.convert_to_alpha2(alpha_code) + if (temp_alpha_code is None and alpha_code.isdigit()): + raise ValueError("Invalid ISO 3166-1 numeric country code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code)) + if (temp_alpha_code is None): + raise ValueError("Invalid ISO 3166-1 alpha-3 country code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code)) + alpha_code = temp_alpha_code #raise error if invalid alpha-2 code input - if not (alpha2_code in sorted(list(iso3166.countries_by_alpha2.keys()))): - raise ValueError("Invalid alpha-2 code input: {}.".format(alpha2_code)) - - #delete subdivision if delete parameter set, raise error if code not found + if not (alpha_code in sorted(list(iso3166.countries_by_alpha2.keys()))) or not (alpha_code in list(self.all.keys())): + raise ValueError("Invalid ISO 3166-1 alpha-2 country code input: {}.".format(alpha_code)) + + #delete subdivision if delete parameter set if (delete): - if (subdivision_code in list(all_subdivision_data[alpha2_code].keys())): - del all_subdivision_data[alpha2_code][subdivision_code] + if (subdivision_code in list(all_subdivision_data[alpha_code].keys())): + del all_subdivision_data[alpha_code][subdivision_code] else: #adding new subdivision data to object from input parameters custom_subdivision_data = {"name": name, "localName": local_name, "type": type, "parentCode": parent_code, "latLng": lat_lng, "flagUrl": flag_url} #reorder subdivision attributes - all_subdivision_data[alpha2_code][subdivision_code] = {k: custom_subdivision_data[k] for k in ["name", "localName", "type", "parentCode", "flagUrl", "latLng"]} + all_subdivision_data[alpha_code][subdivision_code] = {k: custom_subdivision_data[k] for k in ["name", "localName", "type", "parentCode", "flagUrl", "latLng"]} #sort subdivision codes in json objects in natural alphabetical/numerical order using natsort library - all_subdivision_data[alpha2_code] = dict(OrderedDict(natsort.natsorted(all_subdivision_data[alpha2_code].items()))) + all_subdivision_data[alpha_code] = dict(OrderedDict(natsort.natsorted(all_subdivision_data[alpha_code].items()))) #export the updated custom subdivision object with open(os.path.join(self.iso3166_2_module_path, self.data_folder, self.iso3166_json_filename), 'w', encoding='utf-8') as output_json: json.dump(all_subdivision_data, output_json, ensure_ascii=False, indent=4) - def search(self, name="", any=False): + def search(self, name="", likeness=1.0): """ - Search for a subdivision and its corresponding data using it's subdivision name. The 'any' - input parameter determines if the function searches for an exact subdivision name or searches - for 1 or more matching subdivisions, by default it will search for an exact match, set 'any' - to True if searching for approximate matching subdivisions. If a single subdivision is found - the dict of the subdivision data will be returned, if one or more similarly named subdivisions - are found then a list of dicts will be returned. + Search for a subdivision and its corresponding data using it's subdivision name. + The 'likeness' input parameter determines if the function searches for an exact + subdivision name or searches for 1 or more matching subdivisions, by default it + will search for an exact match (likeness=1). Setting the likeness score between + 0 and 1 will be a percentage score that names in the subdivision dataset have + to meet with that of the input subdivision name. A fuzzy search algorithm is + used to acquire the matching subdivisions via "thefuzz" package. + + If the default likeness score is input and no exact matching subdivision is found, + the likeness score will be reduced to 0.9 and then if no match is found after this + then an error will be raised. If multiple matching subdivisions are found then a + list of dicts will be returned. Parameters ========== :name: str (default="") subdivision name to search for. - :any: bool (default=False) - if False then only return subdivision data if an exact match found otherwise return - 1 or more subdivisions that appoximately match input value. + :likeness: float (default=1.0) + likeness score between 0 and 1 that sets the percentage of likeness the input + subdivision name is to the list of subdivision names in the dataset. The default + value of 1.0 will look for exact matches to the input name. Returns ======= :dict/list - if one subdivision found then a dict of its data will be returned. If one more more + if one subdivision found then a dict of its data will be returned. If one or more subdivisions found then a list of dicts will be returned. """ #raise error if name parameter isn't a string if not (isinstance(name, str)): raise TypeError("Input subdivision name should be of type str, got {}.".format(type(name))) + #function can also accept likeness value between 1 and 100, this will be divided to get it between 0 and 1 + if (1 < likeness < 100): + likeness = likeness/100 + + #set likeness value to 1 if invalid or out of range value input + if not (0 < likeness < 1): + likeness = 1 + #object to store the subdivision name and its corresponding alpha-2 code (name: alpha_2) all_subdivision_names = {} #list to store all subdivision names for all countries all_subdivision_names_list = [] + #list of subdivision names with comma in their official name from dataset, required if multiple subdivison names are input e.g - Murcia, Regiónde, Newry, Mourne and Down + subdivision_name_expections = [] + + #seperate list to keep track if any of input subdivsion names are exceptions (have comma in their official them) + subdivision_name_expections_input = [] + #iterate over all ISO 3166-2 subdivision data, appending each subdivision's name, country code and - #subdivision code to object that will be used to search through + #subdivision code to object that will be used to search through, lowercase, remove whitespace and any accents, + #if a comma is in the official subdivision name then append to the exception list only if comma is in input parameter for alpha_2 in self.all: for subd in self.all[alpha_2]: - all_subdivision_names[self.all[alpha_2][subd]["name"].lower()] = {"alpha2": alpha_2, "code": subd} - all_subdivision_names_list.append(self.all[alpha_2][subd]["name"].lower()) - - #the 'any' default parameter value (False) will only return data if exact match found, otherwise - #it will return 1 or more approximate subdivisions that match the input name, if 'any' = True - if (any): - #get closest subdivision name matches with a cutoff of 0.8 - subdivision_name_matches = get_close_matches(name.lower(), all_subdivision_names_list, cutoff=0.8) - else: - #get closest subdivision name matches with a cutoff of 1 - exact match - subdivision_name_matches = get_close_matches(name.lower(), all_subdivision_names_list, cutoff=1) - - #return empty object if no matches found - if (subdivision_name_matches == []): - return {} - #return subdivision data if one match found - elif (len(subdivision_name_matches) == 1): - #add country code and subdivision code to subdivision data object - temp_subdivision = self.all[all_subdivision_names[subdivision_name_matches[0]]["alpha2"]][all_subdivision_names[subdivision_name_matches[0]]["code"]] - temp_subdivision["alpha_2"] = all_subdivision_names[subdivision_name_matches[0]]["alpha2"] - temp_subdivision["subdivision_code"] = all_subdivision_names[subdivision_name_matches[0]]["code"] - return temp_subdivision - #return list of subdivision data objects for all matches found + + #append object of the subdivison's alpha-2 code and subdivision code with its name as the key + if not (unidecode(self.all[alpha_2][subd]["name"].lower().replace(' ', '')) in list(all_subdivision_names.keys())): + all_subdivision_names[unidecode(unquote_plus(self.all[alpha_2][subd]["name"]).lower().replace(' ', ''))] = [] + all_subdivision_names[unidecode(unquote_plus(self.all[alpha_2][subd]["name"]).lower().replace(' ', ''))].append({"alpha2": alpha_2, "code": subd}) + else: + all_subdivision_names[unidecode(unquote_plus(self.all[alpha_2][subd]["name"]).lower().replace(' ', ''))].append({"alpha2": alpha_2, "code": subd}) + + #append subdivision name to list of subdivision names for searching + all_subdivision_names_list.append(unidecode(unquote_plus(self.all[alpha_2][subd]["name"]).lower().replace(' ', ''))) + + #if comma in official subdivision name, append to the exception list, which is needed if a comma seperated list of names are input + if (',' in name): + if (',' in unidecode(unquote_plus(self.all[alpha_2][subd]["name"]).lower().replace(' ', ''))): + subdivision_name_expections.append(unidecode(unquote_plus(self.all[alpha_2][subd]["name"]).lower().replace(' ', ''))) + + #sort exceptions list alphabetically + subdivision_name_expections.sort() + + #decode any unicode or accent characters using utf-8 encoding, lowercase and remove additional whitespace + name = unidecode(unquote_plus(name)).replace(' ', '').lower() + + #only execute subdivision name exception code if comma is in input param + if (',' in name): + #temp var to track input subdivision name + temp_subdivision_name = name + + #iterate over all subdivision names exceptions (those with a comma in their official name), append to seperate list if input param is one + for sub_name in subdivision_name_expections: + if (sub_name in temp_subdivision_name): + subdivision_name_expections_input.append(sub_name) + #remove current subdivision name from temp var, strip of commas + name = temp_subdivision_name.replace(sub_name, '').strip(',') + + #sort all subdivision names codes + subdivision_names = sorted([name]) + + #split multiple subdivision names into list + subdivision_names = subdivision_names[0].split(',') + + #extend subdivsion names list if any subdivision name exceptions are present in input param + if (subdivision_name_expections_input != []): + subdivision_names.extend(subdivision_name_expections_input) + + #object to keep track of matching subdivisions and their data + output_subdivisions = {} + + #iterate over all input subdivision names, and find matching subdivision in data object + for subdiv in subdivision_names: + + #using thefuzz library, get all subdivisions that match the input subdivision names + all_subdivision_name_matches = process.extract(subdiv, all_subdivision_names_list, scorer=fuzz.ratio) #partial_ratio + name_matches = [] + + #iterate over all found subdivision name matches, look for exact matches, if none found then look for ones that have likeness score>=90 + for match in all_subdivision_name_matches: + #use default likeness score of 100 (exact) followed by 90 if no exact matches found + if (likeness == 1): + if (match[1] == 100): + name_matches.append(match[0]) + elif (match[1] >= 90): + name_matches.append(match[0]) + else: + if (match[1] >= likeness * 100): + name_matches.append(match[0]) + + #iterate over all subdivision name mathces and get corresponding subdivision object from dataset + for subd in range(0, len(name_matches)): + for obj in range(0, len(all_subdivision_names[name_matches[subd]])): + #create temp object for subdivision and its data attributes, with its subdivision code as key + subdivision = self.all[all_subdivision_names[name_matches[subd]][obj]["alpha2"]][all_subdivision_names[name_matches[subd]][obj]["code"]] + #append subdivision data and its attributes to the output object + output_subdivisions[all_subdivision_names[name_matches[subd]][obj]["code"]] = subdivision + + #return object of matching subdivisions and their data + return output_subdivisions + + def convert_to_alpha2(self, alpha_code): + """ + Auxillary function that converts an ISO 3166 country's 3 letter alpha-3 + or numeric code into its 2 letter alpha-2 counterpart. + + Parameters + ========== + :alpha3_code: str + 3 letter ISO 3166-1 alpha-3 or numeric country code. + + Returns + ======= + :iso3166.countries_by_alpha3[alpha3_code].alpha2: str + 2 letter ISO 3166 alpha-2 country code. + """ + if (alpha_code.isdigit()): + #return error if numeric code not found + if not (alpha_code in list(iso3166.countries_by_numeric.keys())): + return None + else: + #use iso3166 package to find corresponding alpha-2 code from its numeric code + return iso3166.countries_by_numeric[alpha_code].alpha2 + + #return error if 3 letter alpha-3 code not found + if not (alpha_code in list(iso3166.countries_by_alpha3.keys())): + return None else: - subdivision_list = [] - for subd in range(0, len(subdivision_name_matches)): - #add country code and subdivision code to subdivision data object - temp_subdivision = self.all[all_subdivision_names[subdivision_name_matches[subd]]["alpha2"]][all_subdivision_names[subdivision_name_matches[subd]]["code"]] - temp_subdivision["alpha_2"] = all_subdivision_names[subdivision_name_matches[subd]]["alpha2"] - temp_subdivision["subdivision_code"] = all_subdivision_names[subdivision_name_matches[subd]]["code"] + #use iso3166 package to find corresponding alpha-2 code from its alpha-3 code + return iso3166.countries_by_alpha3[alpha_code].alpha2 - #append data object to list - subdivision_list.append(temp_subdivision) + def __getitem__(self, alpha_code): + """ + Return all of a country's subdivision data by making the class subscriptable via + its alpha-2, alpha-3 or numeric country codes. A list of country data can be returned + if a comma seperated list of alpha codes are input. If the alpha-3 or numeric codes + are input these will be converted into their alpha-2 counterpart. If no value input + then an error is raised. - return subdivision_list + Parameters + ========== + :alpha_code: str + 2 letter alpha-2, alpha-3 or numeric country codes for sought country/territory + e.g (AD, EGY, 276). Multiple country codes of different types can be input via + a comma seperated list. The alpha-3 or numeric codes will be converted into their + alpha-2 counterpart. If no value input then an error will be raised + + Returns + ======= + :country[alpha_code]: dict + dict object of country/subdivision info for inputted alpha_code. + + Usage + ===== + from iso3166_2 import * + iso = ISO3166_2() + + #get subdivision info for Ethiopia + iso["ET"] + iso["ETH"] + iso["231"] + + #get subdivision info for Gabon + iso["GA"] + iso["GAB"] + iso["266"] + + #get subdivision info for Rwanda + iso["RW"] + iso["RWA"] + iso["646"] + + #get subdivision info for Haiti, Monaco and Namibia + iso["HT, MC, NA"] + iso["HTI, MCO, NAM"] + iso["ht, mc, 516"] + """ + #raise type error if input isn't a string + if not (isinstance(alpha_code, str)): + raise TypeError('Input parameter {} is not of correct datatype string, got {}.'.format(alpha_code, type(alpha_code))) + + #stripping input of whitespace, uppercasing and seperating into comma seperated list + alpha_code = alpha_code.replace(' ', '').upper().split(',') + + #object to store country data + country = {} + #iterate over all input alpha codes, append their country and subdivision data to output object + for code in range(0, len(alpha_code)): + + #if 3 letter alpha-3 or numeric codes input then convert to corresponding alpha-2, else raise error + if (len(alpha_code[code]) == 3): + temp_alpha_code = self.convert_to_alpha2(alpha_code[code]) + if not (temp_alpha_code is None): + alpha_code[code] = temp_alpha_code + else: + raise ValueError("Invalid alpha-3 code input, cannot convert into corresponding alpha-2 code: {}.".format(alpha_code[code])) + + #raise error if invalid alpha-2 code input + if not (alpha_code[code] in sorted(list(iso3166.countries_by_alpha2.keys()))) or not (alpha_code[code] in list(self.all.keys())): + raise ValueError("Invalid alpha-2 code input: {}.".format(alpha_code[code])) + + #create instance of Map class so dict can be accessed via dot notation + country[alpha_code[code]] = Map(self.all[alpha_code[code]]) + + #iterate over nested dicts, convert into instances of Map class so they can be accessed via dot notation + for key in country[alpha_code[code]].keys(): + if (isinstance(country[alpha_code[code]][key], dict)): + country[alpha_code[code]][key] = Map(country[alpha_code[code]][key]) + + #convert country data object into Map class so it can be accessed via dot notation + country = Map(country) + + #if only one alpha-2 code input then return list of its country data and attributes else return dict object for all inputs + if len(alpha_code) == 1: + return country[alpha_code[0]] + else: + return country + def __str__(self): - return "Instance of ISO 3166-2 class." + return "Instance of ISO3166_2 class." def __sizeof__(self): """ Return size of instance of ISO3166_2 class. """ return self.__sizeof__() - -def convert_to_alpha2(alpha3_code): - """ - Auxillary function that converts an ISO 3166 country's 3 letter alpha-3 - code into its 2 letter alpha-2 counterpart. - - Parameters - ========== - :alpha3_code: str - 3 letter ISO 3166-1 alpha-3 country code. - - Returns - ======= - :iso3166.countries_by_alpha3[alpha3_code].alpha2: str - 2 letter ISO 3166 alpha-2 country code. - """ - #return None if 3 letter alpha-3 code not found - if not (alpha3_code in list(iso3166.countries_by_alpha3.keys())): - return None - else: - #use iso3166 package to find corresponding alpha-2 code from its alpha-3 code - return iso3166.countries_by_alpha3[alpha3_code].alpha2 class Map(dict): """ @@ -744,7 +800,4 @@ def __delattr__(self, item): def __delitem__(self, key): super(Map, self).__delitem__(key) - del self.__dict__[key] - -#create instance of ISO3166_2 class -country = ISO3166_2() \ No newline at end of file + del self.__dict__[key] \ No newline at end of file diff --git a/iso3166_2/iso3166-2-data/README.md b/iso3166_2/iso3166_2_data/README.md similarity index 100% rename from iso3166_2/iso3166-2-data/README.md rename to iso3166_2/iso3166_2_data/README.md diff --git a/iso3166_2/iso3166-2-data/iso3166-2.json b/iso3166_2/iso3166_2_data/iso3166-2.json similarity index 99% rename from iso3166_2/iso3166-2-data/iso3166-2.json rename to iso3166_2/iso3166_2_data/iso3166-2.json index fb581a5..3c9e7bf 100644 --- a/iso3166_2/iso3166-2-data/iso3166-2.json +++ b/iso3166_2/iso3166_2_data/iso3166-2.json @@ -13223,8 +13223,8 @@ }, "ES": { "ES-A": { - "name": "Alacant*", - "localName": "Alacant*", + "name": "Alicante", + "localName": "Alacant", "type": "Province", "parentCode": "ES-VC", "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-A.svg", @@ -13454,8 +13454,8 @@ ] }, "ES-CS": { - "name": "Castelló*", - "localName": "Castelló*", + "name": "Castellón", + "localName": "Castellón", "type": "Province", "parentCode": "ES-VC", "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-CS.svg", @@ -13707,8 +13707,8 @@ ] }, "ES-NA": { - "name": "Nafarroa*", - "localName": "Nafarroa*", + "name": "Navarra", + "localName": "Navarra", "type": "Province", "parentCode": "ES-NC", "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-NA.svg", @@ -13718,8 +13718,8 @@ ] }, "ES-NC": { - "name": "Nafarroako Foru Komunitatea*", - "localName": "Nafarroako Foru Komunitatea*", + "name": "Nafarroako Foru Komunitatea", + "localName": "Navarra, Comunidad Foral de", "type": "Autonomous community", "parentCode": null, "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-NC.svg", @@ -13785,7 +13785,7 @@ }, "ES-PV": { "name": "Euskal Herria", - "localName": "Euskal Herria", + "localName": "País Vasco", "type": "Autonomous community", "parentCode": null, "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-PV.svg", @@ -13939,7 +13939,7 @@ }, "ES-VC": { "name": "Valenciana, Comunidad", - "localName": "Valenciana, Comunidad", + "localName": "Valenciana, Comunitat", "type": "Autonomous community", "parentCode": null, "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-VC.svg", @@ -13949,8 +13949,8 @@ ] }, "ES-VI": { - "name": "Araba*", - "localName": "Araba*", + "name": "Araba", + "localName": "Álava", "type": "Province", "parentCode": "ES-PV", "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-VI.svg", diff --git a/iso3166_2_scripts/get_iso3166_2.py b/iso3166_2_scripts/get_iso3166_2.py index 4403763..9580155 100644 --- a/iso3166_2_scripts/get_iso3166_2.py +++ b/iso3166_2_scripts/get_iso3166_2.py @@ -13,7 +13,7 @@ from collections import OrderedDict import pandas as pd import numpy as np -from .update_subdivisions import * +from iso3166_2_scripts.update_subdivisions import * #initialise version __version__ = metadata('iso3166-2')['version'] @@ -25,11 +25,12 @@ #initialise google maps client with API key gmaps = googlemaps.Client(key=os.environ["GOOGLE_MAPS_API_KEY"]) -def export_iso3166_2(alpha2_codes="", output_folder="test-iso3166-2-output", json_filename="test-iso3166-2", verbose=1): +def export_iso3166_2(alpha_codes="", output_folder="test-iso3166-2-output", json_filename="test-iso3166-2", verbose=1): """ - Export all ISO 3166-2 subdivision related data using the ISO 3166-2 library to a JSON. Also get the - lat/longitude info for each subdivision using the Google Maps API. The iso3166-2.json stores all - the subdivision data including: subdivison code, name, type, parent code, flag and latitude/longitude. + Export all ISO 3166-2 subdivision related data using the ISO 3166-2 library to a JSON and CSV. Also + get the lat/longitude info for each subdivision using the Google Maps API. The iso3166-2.json stores + all the subdivision data including: subdivison code, name, local name, type, parent code, flag and + latitude/longitude. The generated JSON is used as a baseline for the ISO 3166-2 data object, but additional and more up-to-date and accurate data is added with the help of the iso3166-updates software/API @@ -37,8 +38,9 @@ def export_iso3166_2(alpha2_codes="", output_folder="test-iso3166-2-output", jso Parameters ========== - :alpha2_codes: str (default="") - string of 1 or more 2 letter alpha-2 country codes to extract their latest ISO 3166-2 data. + :alpha_codes: str (default="") + string of 1 or more 2 letter alpha-2, 3 letter alpha-3 or numeric country codes to extract their + latest ISO 3166-2 data. :output_folder: str (default="iso3166-2-output") output folder to store exported iso3166-2 data. :json_filename: str (default="iso3166-2") @@ -50,52 +52,56 @@ def export_iso3166_2(alpha2_codes="", output_folder="test-iso3166-2-output", jso ======= None """ - def convert_to_alpha2(alpha3_code): + def convert_to_alpha2(alpha_code): """ - Convert an ISO 3166 country's 3 letter alpha-3 code into its 2 letter - alpha-2 counterpart. + Auxillary function that converts an ISO 3166 country's 3 letter alpha-3 + or numeric code into its 2 letter alpha-2 counterpart. Parameters ========== :alpha3_code: str - 3 letter ISO 3166-1 alpha-3 country code. + 3 letter ISO 3166-1 alpha-3 or numeric country code. Returns ======= :iso3166.countries_by_alpha3[alpha3_code].alpha2: str 2 letter ISO 3166 alpha-2 country code. """ - #return None if 3 letter alpha-3 code not found - if not (alpha3_code in list(iso3166.countries_by_alpha3.keys())): + if (alpha_code.isdigit()): + #return error if numeric code not found + if not (alpha_code in list(iso3166.countries_by_numeric.keys())): + return None + else: + #use iso3166 package to find corresponding alpha-2 code from its numeric code + return iso3166.countries_by_numeric[alpha_code].alpha2 + + #return error if 3 letter alpha-3 code not found + if not (alpha_code in list(iso3166.countries_by_alpha3.keys())): return None else: #use iso3166 package to find corresponding alpha-2 code from its alpha-3 code - return iso3166.countries_by_alpha3[alpha3_code].alpha2 + return iso3166.countries_by_alpha3[alpha_code].alpha2 - #split multiple alpha-2 codes into list, remove any whitespace - alpha2_codes = alpha2_codes.replace(' ', '').split(',') - - #bool to keep track if getting data for all countries - using_all_data = False + #split multiple alpha codes into list, remove any whitespace + alpha_codes = alpha_codes.replace(' ', '').split(',') - #parse input alpha2_codes parameter, use all alpha-2 codes if parameter not set - if (alpha2_codes == ['']): + #parse input alpha_codes parameter, use all alpha-2 codes if parameter not set + if (alpha_codes == ['']): #use list of all 2 letter alpha-2 codes, according to ISO 3166-1 all_alpha2 = sorted(list(iso3166.countries_by_alpha2.keys())) - using_all_data = True else: #iterate over all codes, checking they're valid, convert alpha-3 to alpha-2 if applicable - for code in range(0, len(alpha2_codes)): + for code in range(0, len(alpha_codes)): - #convert 3 letter alpha-3 code into its 2 letter alpha-2 counterpart - if len(alpha2_codes[code]) == 3: - alpha2_codes[code] = convert_to_alpha2(alpha2_codes[code]) + #convert 3 letter alpha-3 or numeric code into its 2 letter alpha-2 counterpart + if len(alpha_codes[code]) == 3: + alpha_codes[code] = convert_to_alpha2(alpha_codes[code]) #raise error if invalid alpha-2 code found - if (alpha2_codes[code] not in list(iso3166.countries_by_alpha2.keys())): - raise ValueError("Input alpha-2 country code {} not found.".format(alpha2_codes[code])) + if (alpha_codes[code] not in list(iso3166.countries_by_alpha2.keys())): + raise ValueError("Input alpha-2 country code {} not found.".format(alpha_codes[code])) - all_alpha2 = alpha2_codes + all_alpha2 = alpha_codes #if 10 or less alpha-2 codes input then append to filename if (len(all_alpha2) <= 10): @@ -107,12 +113,15 @@ def convert_to_alpha2(alpha3_code): #append json extension to output filename if (os.path.splitext(json_filepath) != ".json"): json_filepath = json_filepath + ".json" + + #get path to CSV of output data + csv_filepath = os.path.splitext(json_filepath)[0] + ".csv" if (verbose): print("Exporting {} ISO 3166-2 country's data to folder {}".format(len(all_alpha2), output_folder)) print('################################################################\n') - #objects to store all country output data + #objects to store all country output data in json and csv format all_country_data = {} #create output dir if doesn't exist @@ -131,7 +140,7 @@ def convert_to_alpha2(alpha3_code): flag_icons_base_url = "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/" #reading in, as dataframe, the csv that stores the local names for each subdivision - local_name_df = pd.read_csv(os.path.join("iso3166-2-updates", "local_names.csv")) + local_name_df = pd.read_csv(os.path.join("iso3166_2_updates", "local_names.csv")) #replace any Nan values with None local_name_df = local_name_df.replace(np.nan, None) @@ -161,7 +170,7 @@ def convert_to_alpha2(alpha3_code): #create country key in json object all_country_data[alpha2] = {} - #iterate over all countrys' subdivisions, assigning subdiv code, name, type and parent code and flag URL, where applicable for the json object + #iterate over all countrys' subdivisions, assigning subdivision code, name, type, parent code and flag URL, where applicable for the json object for subd in all_subdivisions: #get subdivision coordinates using googlemaps api python client @@ -173,6 +182,10 @@ def convert_to_alpha2(alpha3_code): else: subdivision_coords = None + #raise error if subdivision code already in output object + if (subd.code in list(all_country_data[alpha2].keys())): + raise ValueError("Subdivision code already present in output object: {}.".format(subd.code)) + #initialise subdivision code object all_country_data[alpha2][subd.code] = {} all_country_data[alpha2][subd.code]["name"] = subd.name @@ -220,14 +233,32 @@ def convert_to_alpha2(alpha3_code): with open(json_filepath, 'r', encoding='utf-8') as input_json: all_country_data = json.load(input_json) - #call update_subdivision() function if using all alpha-2 codes - if (using_all_data): - #append latest subdivision updates/changes from /iso3166-2-updates folder to the iso3166-2 object - all_country_data = update_subdivision(iso3166_2_filename=json_filepath, subdivision_csv=os.path.join("iso3166-2-updates", "subdivision_updates.csv"), export=0) + #append latest subdivision updates/changes from /iso3166_2_updates folder to the iso3166-2 object + all_country_data = update_subdivision(iso3166_2_filename=json_filepath, subdivision_csv=os.path.join("iso3166_2_updates", "subdivision_updates.csv"), export=0) #add local names for each subdivision from local_names.csv file all_country_data = add_local_names(all_country_data) + #initialise array to store each individual subdivision object + all_country_csv = [] + + #iterate over country data object, for each subdivision object append its subdivision and country code, append subdivision object to array + for country in all_country_data: + for subd in all_country_data[country]: + temp_all_country_data = all_country_data[country][subd].copy() + temp_all_country_data["subdivision_code"] = subd + temp_all_country_data["country_code"] = country + all_country_csv.append(temp_all_country_data) + + #convert array of objects into a dataframe with each subdivision being a row, reorder columns and sort by subdivision then country code + all_country_csv_df = pd.DataFrame(all_country_csv) + all_country_csv_df = all_country_csv_df.reindex(columns=['country_code', 'subdivision_code', 'name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng']) + all_country_csv_df = all_country_csv_df.sort_values('subdivision_code').reset_index(drop=True) + all_country_csv_df = all_country_csv_df.sort_values('country_code').reset_index(drop=True) + + #export dataframe of subdivision objects to CSV + all_country_csv_df.to_csv(csv_filepath, index=False) + #write json data with all updated country info, including local name, to json output file with open(json_filepath, 'w', encoding='utf-8') as f: json.dump(all_country_data, f, ensure_ascii=False, indent=4) @@ -246,7 +277,7 @@ def convert_to_alpha2(alpha3_code): #parse input arguments using ArgParse parser = argparse.ArgumentParser(description='Script for exporting iso3166-2 country data using pycountry package.') - parser.add_argument('-alpha2_codes', '--alpha2_codes', type=str, required=False, default="", + parser.add_argument('-alpha_codes', '--alpha_codes', type=str, required=False, default="", help='One or more 2 letter alpha-2 country codes, by default all ISO 3166-2 subdivision data for all countries will be exported.') parser.add_argument('-json_filename', '--json_filename', type=str, required=False, default="test_iso3166-2", help='Output filename for both iso3166-2 JSON.') @@ -257,9 +288,9 @@ def convert_to_alpha2(alpha3_code): #parse input args args = parser.parse_args() - alpha2_codes = args.alpha2_codes + alpha_codes = args.alpha_codes output_folder = args.output_folder json_filename = args.json_filename verbose = args.verbose - export_iso3166_2(alpha2_codes=alpha2_codes, output_folder=output_folder, json_filename=json_filename, verbose=verbose) \ No newline at end of file + export_iso3166_2(alpha_codes=alpha_codes, output_folder=output_folder, json_filename=json_filename, verbose=verbose) \ No newline at end of file diff --git a/iso3166_2_scripts/update_subdivisions.py b/iso3166_2_scripts/update_subdivisions.py index 5553ee6..d23e744 100644 --- a/iso3166_2_scripts/update_subdivisions.py +++ b/iso3166_2_scripts/update_subdivisions.py @@ -237,16 +237,20 @@ def convert_to_alpha2(alpha3_code): #sort dataframe rows by their country code and reindex rows # subdivision_df = subdivision_df.sort_values('country_code').reset_index(drop=True) - + #iterate through all dataframe rows, adding the subdivision attributes to respective subdivisions in object for index, row in subdivision_df.iterrows(): #uppercase and remove whitespace for country code country_code = row['country_code'].replace(' ', '').upper() + #skip to next row in subdivisions csv if country code not in data object + if not (country_code in list(all_subdivision_data.keys())): + continue + #if brackets are in subdivision_code parameter than the subdivision code is to be updated itself, keeping its existing data if ('(' and ')' in row['code']): - + #parse current and new subdivision code which should be put in brackets in the subdivision code parameter current_subdivision_code = row['code'].split('(', 1)[0].replace(' ', '').upper() new_subdivision_code = row['code'][row['code'].find("(")+1:row['code'].find(")")].replace(' ', '').upper() @@ -274,10 +278,6 @@ def convert_to_alpha2(alpha3_code): else: continue else: - #raise error if alpha-2 country code not found in object - if not (country_code in list(all_subdivision_data.keys())): - raise ValueError("Country code {} in row not found in list of object country codes:\n{}.".format(country_code, list(all_subdivision_data.keys()))) - #if delete column is set then delete respective subdivision from object according to its subdivision code, skip to next iteration if (row['delete']): if (row['code'] in list(all_subdivision_data[country_code].keys())): @@ -348,7 +348,7 @@ def convert_to_alpha2(alpha3_code): def add_local_names(all_subdivision_data): """ - Adding subdivision's local name taken from iso3166-2-updates/local_names.csv. Most + Adding subdivision's local name taken from iso3166_2_updates/local_names.csv. Most subdivision's local name is the same as it's current sudivision name attribute, but many country's also have their own non-english local variation. @@ -363,7 +363,7 @@ def add_local_names(all_subdivision_data): input subdivision data object with local names for the subdivisions added. """ #reading in, as dataframe, the csv that stores the local names for each subdivision - local_name_df = pd.read_csv(os.path.join("iso3166-2-updates", "local_names.csv")) + local_name_df = pd.read_csv(os.path.join("iso3166_2_updates", "local_names.csv")) #replace any Nan values with None local_name_df = local_name_df.replace(np.nan, None) diff --git a/iso3166-2-updates/README.md b/iso3166_2_updates/README.md similarity index 100% rename from iso3166-2-updates/README.md rename to iso3166_2_updates/README.md diff --git a/iso3166-2-updates/UPDATES.md b/iso3166_2_updates/UPDATES.md similarity index 100% rename from iso3166-2-updates/UPDATES.md rename to iso3166_2_updates/UPDATES.md diff --git a/iso3166-2-updates/local_names.csv b/iso3166_2_updates/local_names.csv similarity index 100% rename from iso3166-2-updates/local_names.csv rename to iso3166_2_updates/local_names.csv diff --git a/iso3166-2-updates/subdivision_updates.csv b/iso3166_2_updates/subdivision_updates.csv similarity index 100% rename from iso3166-2-updates/subdivision_updates.csv rename to iso3166_2_updates/subdivision_updates.csv diff --git a/setup.cfg b/setup.cfg index 4c54cf3..4aee125 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = iso3166-2 -version = 1.4.0 +version = 1.5.0 description = A lightweight Python package, and accompanying API, that can be used to access all of the worlds most up-to-date and accurate ISO 3166-2 subdivision data, including: name, local name, code, parent code, type, latitude/longitude and flag. author = AJ McKenna author_email = amckenna41@qub.ac.uk @@ -23,7 +23,8 @@ keywords = iso3166-1 alpha-2 iso3166-updates - rest-countries + subdivisions + regions # https://pypi.org/pypi?%3Aaction=list_classifiers classifiers = @@ -54,6 +55,7 @@ packages = find: install_requies = iso3166 natsort + unidecode [options.extras_require] test = diff --git a/setup.py b/setup.py index 51135ad..6ddbcf6 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ #software metadata __name__ = 'iso3166-2' -__version__ = "1.4.0" +__version__ = "1.5.0" __description__ = "A lightweight Python package, and accompanying API, that can be used to access all of the world's most up-to-date and accurate ISO 3166-2 subdivision data, including: name, local name, code, parent code, type, latitude/longitude and flag." __author__ = 'AJ McKenna, https://github.com/amckenna41' __authorEmail__ = 'amckenna41@qub.ac.uk' @@ -17,7 +17,7 @@ __download_url__ = "https://github.com/amckenna41/iso3166-2/archive/refs/heads/main.zip" __status__ = 'Production' __keywords__ = ["iso", "iso3166", "beautifulsoup", "python", "pypi", "countries", "country codes", \ - "iso3166-2", "iso3166-1", "alpha-2", "iso3166-updates", "subdivisions"] + "iso3166-2", "iso3166-1", "alpha-2", "iso3166-updates", "subdivisions", "regions"] __test_suite__ = "tests" #get path to README file @@ -57,7 +57,8 @@ ], install_requires=[ 'iso3166', - 'natsort' + 'natsort', + 'unidecode' ], test_suite=__test_suite__, packages=find_packages(), diff --git a/tests/test_get_iso3166_2.py b/tests/test_get_iso3166_2.py index 6e6311d..db57f5b 100644 --- a/tests/test_get_iso3166_2.py +++ b/tests/test_get_iso3166_2.py @@ -7,6 +7,7 @@ import unittest unittest.TestLoader.sortTestMethodsUsing = None +# @unittest.skip("") class Get_ISO3166_2_Tests(unittest.TestCase): """ Test suite for testing get_iso3166_2.py script that pulls all the ISO 3166-2 @@ -37,36 +38,37 @@ def setUp(self): def test_export_iso3166_2(self): """ Testing export functionality for getting ISO 3166-2 subdivision data from data sources. """ - test_alpha2_dk = "DK" #Denmark - test_alpha2_fi = "FI" #Finland - test_alpha2_gd = "GD" #Grenada - test_alpha2_kg_mg_nr = "KG,MG,NR" #Kyrgyzstan, Madagascar, Nauru - test_alpha2_bv_fo_gs_hk = ["BV", "FO", "GS", "HK"] #Bouvet Island, Faroe Islands, South Georgia, Hong Kong - test_alpha2_error1 = "ZZ" - test_alpha2_error2 = "ABCDEF" - test_alpha2_error3 = 1234 + test_alpha_dk = "DK" #Denmark + test_alpha_fi = "FI" #Finland + test_alpha_gd = "GRD" #Grenada + test_alpha_kg_mg_nr = "KG,MDG,520" #Kyrgyzstan, Madagascar, Nauru + test_alpha_error1 = "ZZ" + test_alpha_error2 = "ABCDEF" + test_alpha_error3 = 1234 #1.) - export_iso3166_2(alpha2_codes=test_alpha2_dk, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #Denmark + export_iso3166_2(alpha_codes=test_alpha_dk, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #Denmark #open exported jsons - with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + test_alpha2_dk + ".json")) as output_json: + with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + test_alpha_dk + ".json")) as output_json: test_iso3166_2_dk = json.load(output_json) self.assertIsInstance(test_iso3166_2_dk, dict, "Expected output to be type dict, got {}.".format(type(test_iso3166_2_dk))) self.assertEqual(len(test_iso3166_2_dk["DK"]), 5, "Expected 5 subdivisions to be in output dict, got {}.".format(len(test_iso3166_2_dk["DK"]))) self.assertEqual(list(test_iso3166_2_dk["DK"].keys()), ['DK-81', 'DK-82', 'DK-83', 'DK-84', 'DK-85'], - "Expected list of subdivision codes doesn't match output:\n{}.".format(list(test_iso3166_2_dk["DK"].keys()))) + "Expected list of subdivision codes doesn't match output:\n{}.".format(list(test_iso3166_2_dk["DK"].keys()))) + self.assertTrue(os.path.isfile(os.path.join(self.test_output_dir, os.path.splitext(self.test_output_filename)[0] + "-DK.csv")), + "Expected subdivision data to be exported to a CSV: {}.".format(os.path.join(self.test_output_dir, os.path.splitext(self.test_output_filename)[0] + "-DK.csv"))) for subd in test_iso3166_2_dk["DK"]: self.assertIsNot(test_iso3166_2_dk["DK"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_iso3166_2_dk["DK"][subd]["name"])) self.assertEqual(test_iso3166_2_dk["DK"][subd]["name"], test_iso3166_2_dk["DK"][subd]["localName"], - "Expected subdivision's name and local name to be the same.") + "Expected subdivision name and local name to be the same.") if not (test_iso3166_2_dk["DK"][subd]["parentCode"] is None): self.assertIn(test_iso3166_2_dk["DK"][subd]["parentCode"], list(test_iso3166_2_dk["DK"][subd].keys()), "Parent code {} not found in list of subdivision codes:\n{}.".format(test_iso3166_2_dk["DK"][subd]["parentCode"]), list(test_iso3166_2_dk["DK"][subd].keys())) if not (test_iso3166_2_dk["DK"][subd]["flagUrl"] is None): self.assertEqual(os.path.splitext(test_iso3166_2_dk["DK"][subd]["flagUrl"])[0], self.flag_icons_base_url + "DK/" + subd, - "Expected flag url to be {}, got {}.".format(self.flag_icons_base_url + "DK/" + subd, os.path.splitext(test_iso3166_2_dk["DK"][subd]["flagUrl"])[0])) + "Expected flag URL to be {}, got {}.".format(self.flag_icons_base_url + "DK/" + subd, os.path.splitext(test_iso3166_2_dk["DK"][subd]["flagUrl"])[0])) self.assertEqual(requests.get(test_iso3166_2_dk["DK"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_iso3166_2_dk["DK"][subd]["flagUrl"])) for key in list(test_iso3166_2_dk["DK"][subd].keys()): @@ -81,10 +83,10 @@ def test_export_iso3166_2(self): "Expected subdivision parent code to be None, got {}.".format(test_iso3166_2_dk["DK"]["DK-81"]["parentCode"])) self.assertEqual(test_iso3166_2_dk["DK"]["DK-81"]["type"], "Region", "Expected subdivision type to be Region, got {}.".format(test_iso3166_2_dk["DK"]["DK-81"]["type"])) - # self.assertEqual(test_iso3166_2_dk["DK"]["DK-81"]["latLng"], [56.831, 9.493], - # "Expected subdivision latLng to be [56.831, 9.493], got {}.".format(test_iso3166_2_dk["DK"]["DK-81"]["latLng"])) + self.assertEqual(test_iso3166_2_dk["DK"]["DK-81"]["latLng"], [56.831, 9.493], + "Expected subdivision latLng to be [56.831, 9.493], got {}.".format(test_iso3166_2_dk["DK"]["DK-81"]["latLng"])) self.assertEqual(test_iso3166_2_dk["DK"]["DK-81"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DK/DK-81.svg", - "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DK/DK-81.svg, got {}.".format(test_iso3166_2_dk["DK"]["DK-81"]["flagUrl"])) + "Expected subdivision flag URL to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DK/DK-81.svg, got {}.".format(test_iso3166_2_dk["DK"]["DK-81"]["flagUrl"])) #DK-82 - Midtjylland self.assertEqual(test_iso3166_2_dk["DK"]["DK-82"]["name"], "Midtjylland", "Expected subdivsion name to be Midtjylland, got {}.".format(test_iso3166_2_dk["DK"]["DK-82"]["name"])) @@ -94,15 +96,15 @@ def test_export_iso3166_2(self): "Expected subdivision parent code to be None, got {}.".format(test_iso3166_2_dk["DK"]["DK-82"]["parentCode"])) self.assertEqual(test_iso3166_2_dk["DK"]["DK-82"]["type"], "Region", "Expected subdivision type to be Region, got {}.".format(test_iso3166_2_dk["DK"]["DK-82"]["type"])) - # self.assertEqual(test_iso3166_2_dk["DK"]["DK-82"]["latLng"], [56.302, 9.303], - # "Expected subdivision latLng to be [56.302, 9.303], got {}.".format(test_iso3166_2_dk["DK"]["DK-82"]["latLng"])) + self.assertEqual(test_iso3166_2_dk["DK"]["DK-82"]["latLng"], [56.302, 9.303], + "Expected subdivision latLng to be [56.302, 9.303], got {}.".format(test_iso3166_2_dk["DK"]["DK-82"]["latLng"])) self.assertEqual(test_iso3166_2_dk["DK"]["DK-82"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DK/DK-82.svg", - "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DK/DK-82.svg, got {}.".format(test_iso3166_2_dk["DK"]["DK-82"]["flagUrl"])) + "Expected subdivision flag URL to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DK/DK-82.svg, got {}.".format(test_iso3166_2_dk["DK"]["DK-82"]["flagUrl"])) #2.) - export_iso3166_2(alpha2_codes=test_alpha2_fi, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #Finland + export_iso3166_2(alpha_codes=test_alpha_fi, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #Finland #open exported jsons - with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + test_alpha2_fi + ".json")) as output_json: + with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + test_alpha_fi + ".json")) as output_json: test_iso3166_2_fi = json.load(output_json) self.assertIsInstance(test_iso3166_2_fi, dict, "Expected output to be type dict, got {}.".format(type(test_iso3166_2_fi))) @@ -110,70 +112,74 @@ def test_export_iso3166_2(self): self.assertEqual(list(test_iso3166_2_fi["FI"].keys()), ["FI-01", "FI-02", "FI-03", "FI-04", "FI-05", "FI-06", "FI-07", "FI-08", "FI-09", "FI-10", "FI-11", "FI-12", "FI-13", "FI-14", "FI-15", "FI-16", "FI-17", "FI-18", "FI-19"], "Expected list of subdivision codes doesn't match output:\n{}.".format(list(test_iso3166_2_fi["FI"].keys()))) + self.assertTrue(os.path.isfile(os.path.join(self.test_output_dir, os.path.splitext(self.test_output_filename)[0] + "-FI.csv")), + "Expected subdivision data to be exported to a CSV: {}.".format(os.path.join(self.test_output_dir, os.path.splitext(self.test_output_filename)[0] + "-FI.csv"))) for subd in test_iso3166_2_fi["FI"]: self.assertIsNot(test_iso3166_2_fi["FI"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_iso3166_2_fi["FI"][subd]["name"])) self.assertEqual(test_iso3166_2_fi["FI"][subd]["name"], test_iso3166_2_fi["FI"][subd]["localName"], - "Expected subdivision's name and local name to be the same.") + "Expected subdivision name and local name to be the same.") if not (test_iso3166_2_fi["FI"][subd]["parentCode"] is None): self.assertIn(test_iso3166_2_fi["FI"][subd]["parentCode"], list(test_iso3166_2_fi["FI"][subd].keys()), "Parent code {} not found in list of subdivision codes.".format(test_iso3166_2_fi["FI"][subd]["parentCode"])) if not (test_iso3166_2_fi["FI"][subd]["flagUrl"] is None): self.assertEqual(os.path.splitext(test_iso3166_2_fi["FI"][subd]["flagUrl"])[0], self.flag_icons_base_url + "FI/" + subd, - "Expected flag url to be {}, got {}.".format(self.flag_icons_base_url + "FI/" + subd, os.path.splitext(test_iso3166_2_fi["FI"][subd]["flagUrl"])[0])) + "Expected flag URL to be {}, got {}.".format(self.flag_icons_base_url + "FI/" + subd, os.path.splitext(test_iso3166_2_fi["FI"][subd]["flagUrl"])[0])) self.assertEqual(requests.get(test_iso3166_2_fi["FI"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_iso3166_2_fi["FI"][subd]["flagUrl"])) for key in list(test_iso3166_2_fi["FI"][subd].keys()): self.assertIn(key, self.correct_output_attributes, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_output_attributes)) - #FI-01 - Ahvenanmaan maakunta - self.assertEqual(test_iso3166_2_fi["FI"]["FI-01"]["name"], "Åland", - "Expected subdivsion name to be Åland, got {}.".format(test_iso3166_2_fi["FI"]["FI-01"]["name"])) - self.assertEqual(test_iso3166_2_fi["FI"]["FI-01"]["localName"], "Åland", - "Expected subdivsion local name to be Åland, got {}.".format(test_iso3166_2_fi["FI"]["FI-01"]["localName"])) - self.assertEqual(test_iso3166_2_fi["FI"]["FI-01"]["parentCode"], None, - "Expected subdivision parent code to be None, got {}.".format(test_iso3166_2_fi["FI"]["FI-01"]["parentCode"])) - self.assertEqual(test_iso3166_2_fi["FI"]["FI-01"]["type"], "Region", - "Expected subdivision type to be Region, got {}.".format(test_iso3166_2_fi["FI"]["FI-01"]["type"])) - # self.assertEqual(test_iso3166_2_fi["FI"]["FI-01"]["latLng"], [61.924, 25.748], - # "Expected subdivision latLng to be [61.924, 25.748], got {}.".format(test_iso3166_2_fi["FI"]["FI-01"]["latLng"])) - self.assertEqual(test_iso3166_2_fi["FI"]["FI-01"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/FI/FI-01.svg", - "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/FI/FI-01.svg, got {}.".format(test_iso3166_2_fi["FI"]["FI-01"]["flagUrl"])) + #FI-02 - Etelä-Karjala + self.assertEqual(test_iso3166_2_fi["FI"]["FI-02"]["name"], "Etelä-Karjala", + "Expected subdivsion name to be Etelä-Karjala, got {}.".format(test_iso3166_2_fi["FI"]["FI-02"]["name"])) + self.assertEqual(test_iso3166_2_fi["FI"]["FI-02"]["localName"], "Etelä-Karjala", + "Expected subdivsion local name to be Etelä-Karjala, got {}.".format(test_iso3166_2_fi["FI"]["FI-02"]["localName"])) + self.assertEqual(test_iso3166_2_fi["FI"]["FI-02"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_iso3166_2_fi["FI"]["FI-02"]["parentCode"])) + self.assertEqual(test_iso3166_2_fi["FI"]["FI-02"]["type"], "Region", + "Expected subdivision type to be Region, got {}.".format(test_iso3166_2_fi["FI"]["FI-02"]["type"])) + self.assertEqual(test_iso3166_2_fi["FI"]["FI-02"]["latLng"], [61.203, 28.623], + "Expected subdivision latLng to be [61.203, 28.623], got {}.".format(test_iso3166_2_fi["FI"]["FI-02"]["latLng"])) + self.assertEqual(test_iso3166_2_fi["FI"]["FI-02"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/FI/FI-02.svg", + "Expected subdivision flag URL to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/FI/FI-02.svg, got {}.".format(test_iso3166_2_fi["FI"]["FI-02"]["flagUrl"])) #FI-17 - Satakunda self.assertEqual(test_iso3166_2_fi["FI"]["FI-17"]["name"], "Satakunta", - "Expected subdivsion name to be Satakunda, got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["name"])) + "Expected subdivsion name to be Satakunta, got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["name"])) self.assertEqual(test_iso3166_2_fi["FI"]["FI-17"]["localName"], "Satakunta", - "Expected subdivsion local name to be Satakunda, got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["localName"])) + "Expected subdivsion local name to be Satakunta, got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["localName"])) self.assertEqual(test_iso3166_2_fi["FI"]["FI-17"]["parentCode"], None, "Expected subdivision parent code to be None, got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["parentCode"])) self.assertEqual(test_iso3166_2_fi["FI"]["FI-17"]["type"], "Region", "Expected subdivision type to be Region, got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["type"])) - # self.assertEqual(test_iso3166_2_fi["FI"]["FI-17"]["latLng"], [61.593, 22.148], - # "Expected subdivision latLng to be [61.593, 22.148], got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["latLng"])) + self.assertEqual(test_iso3166_2_fi["FI"]["FI-17"]["latLng"], [61.593, 22.148], + "Expected subdivision latLng to be [61.593, 22.148], got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["latLng"])) self.assertEqual(test_iso3166_2_fi["FI"]["FI-17"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/FI/FI-17.svg", - "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/FI/FI-17.svg, got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["flagUrl"])) + "Expected subdivision flag URL to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/FI/FI-17.svg, got {}.".format(test_iso3166_2_fi["FI"]["FI-17"]["flagUrl"])) #3.) - export_iso3166_2(alpha2_codes=test_alpha2_gd, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #Grenada + export_iso3166_2(alpha_codes=test_alpha_gd, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #Grenada #open exported jsons - with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + test_alpha2_gd + ".json")) as output_json: + with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + "GD" + ".json")) as output_json: test_iso3166_2_gd = json.load(output_json) self.assertIsInstance(test_iso3166_2_gd, dict, "Expected output to be type dict, got {}.".format(type(test_iso3166_2_gd))) self.assertEqual(len(test_iso3166_2_gd["GD"]), 7, "Expected 7 keys/attributes in output dict, got {}.".format(len(test_iso3166_2_gd))) self.assertEqual(list(test_iso3166_2_gd["GD"].keys()), ["GD-01", "GD-02", "GD-03", "GD-04", "GD-05", "GD-06", "GD-10"], - "Expected list of subdivision codes doesn't match output:\n{}.".format(list(test_iso3166_2_gd["GD"].keys()))) + "Expected list of subdivision codes doesn't match output:\n{}.".format(list(test_iso3166_2_gd["GD"].keys()))) + self.assertTrue(os.path.isfile(os.path.join(self.test_output_dir, os.path.splitext(self.test_output_filename)[0] + "-GD.csv")), + "Expected subdivision data to be exported to a CSV: {}.".format(os.path.join(self.test_output_dir, os.path.splitext(self.test_output_filename)[0] + "-GD.csv"))) for subd in test_iso3166_2_gd["GD"]: self.assertIsNot(test_iso3166_2_gd["GD"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_iso3166_2_gd["GD"][subd]["name"])) self.assertEqual(test_iso3166_2_gd["GD"][subd]["name"], test_iso3166_2_gd["GD"][subd]["localName"], - "Expected subdivision's name and local name to be the same.") + "Expected subdivision name and local name to be the same.") if not (test_iso3166_2_gd["GD"][subd]["parentCode"] is None): self.assertIn(test_iso3166_2_gd["GD"][subd]["parentCode"], list(test_iso3166_2_gd["GD"][subd].keys()), "Parent code {} not found in list of subdivision codes.".format(test_iso3166_2_gd["GD"][subd]["parentCode"])) if not (test_iso3166_2_gd["GD"][subd]["flagUrl"] is None): self.assertEqual(os.path.splitext(test_iso3166_2_gd["GD"][subd]["flagUrl"])[0], self.flag_icons_base_url + "GD/" + subd, - "Expected flag url to be {}, got {}.".format(self.flag_icons_base_url + "GD/" + subd, os.path.splitext(test_iso3166_2_gd["GD"][subd]["flagUrl"])[0])) + "Expected flag URL to be {}, got {}.".format(self.flag_icons_base_url + "GD/" + subd, os.path.splitext(test_iso3166_2_gd["GD"][subd]["flagUrl"])[0])) self.assertEqual(requests.get(test_iso3166_2_gd["GD"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_iso3166_2_gd["GD"][subd]["flagUrl"])) for key in list(test_iso3166_2_gd["GD"][subd].keys()): @@ -191,7 +197,7 @@ def test_export_iso3166_2(self): self.assertEqual(test_iso3166_2_gd["GD"]["GD-03"]["latLng"], [12.056, -61.749], "Expected subdivision latLng to be [12.056, -61.749], got {}.".format(test_iso3166_2_gd["GD"]["GD-03"]["latLng"])) self.assertEqual(test_iso3166_2_gd["GD"]["GD-03"]["flagUrl"], None, - "Expected subdivision flag url to be None, got {}.".format(test_iso3166_2_gd["GD"]["GD-03"]["flagUrl"])) + "Expected subdivision flag URL to be None, got {}.".format(test_iso3166_2_gd["GD"]["GD-03"]["flagUrl"])) #GD-10 - Southern Grenadine Islands self.assertEqual(test_iso3166_2_gd["GD"]["GD-10"]["name"], "Southern Grenadine Islands", "Expected subdivsion name to be Southern Grenadine Islands, got {}.".format(test_iso3166_2_gd["GD"]["GD-10"]["name"])) @@ -201,19 +207,18 @@ def test_export_iso3166_2(self): "Expected subdivision parent code to be None, got {}.".format(test_iso3166_2_gd["GD"]["GD-10"]["parentCode"])) self.assertEqual(test_iso3166_2_gd["GD"]["GD-10"]["type"], "Dependency", "Expected subdivision type to be Dependency, got {}.".format(test_iso3166_2_gd["GD"]["GD-10"]["type"])) - # self.assertEqual(test_iso3166_2_gd["GD"]["GD-10"]["latLng"], [12.479, -61.449], - # "Expected subdivision latLng to be [12.479, -61.449], got {}.".format(test_iso3166_2_gd["GD"]["GD-10"]["latLng"])) + self.assertEqual(test_iso3166_2_gd["GD"]["GD-10"]["latLng"], [12.479, -61.449], + "Expected subdivision latLng to be [12.479, -61.449], got {}.".format(test_iso3166_2_gd["GD"]["GD-10"]["latLng"])) self.assertEqual(test_iso3166_2_gd["GD"]["GD-10"]["flagUrl"], None, - "Expected subdivision flag url to be None, got {}.".format(test_iso3166_2_gd["GD"]["GD-10"]["flagUrl"])) + "Expected subdivision flag URL to be None, got {}.".format(test_iso3166_2_gd["GD"]["GD-10"]["flagUrl"])) #4.) - export_iso3166_2(alpha2_codes=test_alpha2_kg_mg_nr, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #Kyrgyzstan, Madagascar, Nauru + export_iso3166_2(alpha_codes=test_alpha_kg_mg_nr, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #Kyrgyzstan, Madagascar, Nauru #open exported jsons - with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + test_alpha2_kg_mg_nr + ".json")) as output_json: + with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + "KG,MG,NR" + ".json")) as output_json: test_iso3166_2_kg_mg_nr = json.load(output_json) self.assertIsInstance(test_iso3166_2_kg_mg_nr, dict, "Expected output to be type dict, got {}.".format(type(test_iso3166_2_kg_mg_nr))) - self.assertEqual(len(test_iso3166_2_kg_mg_nr["KG"]), 9, "Expected 9 subdivisions in output dict, got {}.".format(len(test_iso3166_2_kg_mg_nr["KG"]))) self.assertEqual(len(test_iso3166_2_kg_mg_nr["MG"]), 6, "Expected 6 subdivisions in output dict, got {}.".format(len(test_iso3166_2_kg_mg_nr["MG"]))) self.assertEqual(len(test_iso3166_2_kg_mg_nr["NR"]), 14, "Expected 14 subdivisions in output dict, got {}.".format(len(test_iso3166_2_kg_mg_nr["NR"]))) @@ -223,6 +228,8 @@ def test_export_iso3166_2(self): "Expected list of subdivision codes doesn't match output.") self.assertEqual(list(test_iso3166_2_kg_mg_nr["NR"].keys()), ['NR-01', 'NR-02', 'NR-03', 'NR-04', 'NR-05', 'NR-06', 'NR-07', 'NR-08', 'NR-09', 'NR-10', 'NR-11', 'NR-12', 'NR-13', 'NR-14'], "Expected list of subdivision codes doesn't match output.") + self.assertTrue(os.path.isfile(os.path.join(self.test_output_dir, os.path.splitext(self.test_output_filename)[0] + "-KG,MG,NR.csv")), + "Expected subdivision data to be exported to a CSV: {}.".format(os.path.join(self.test_output_dir, os.path.splitext(self.test_output_filename)[0] + "-KG,MG,NR.csv"))) for country in test_iso3166_2_kg_mg_nr: for subd in test_iso3166_2_kg_mg_nr[country]: self.assertIsNot(test_iso3166_2_kg_mg_nr[country][subd]["name"], None, @@ -232,7 +239,7 @@ def test_export_iso3166_2(self): "Parent code {} not found in list of subdivision codes.".format(test_iso3166_2_kg_mg_nr[country][subd]["parentCode"])) if not (test_iso3166_2_kg_mg_nr[country][subd]["flagUrl"] is None): self.assertEqual(os.path.splitext(test_iso3166_2_kg_mg_nr[country][subd]["flagUrl"])[0], self.flag_icons_base_url + country + "/" + subd, - "Expected flag url to be {}, got {}.".format(self.flag_icons_base_url + country + "/" + subd, os.path.splitext(test_iso3166_2_kg_mg_nr[country][subd]["flagUrl"])[0])) + "Expected flag URL to be {}, got {}.".format(self.flag_icons_base_url + country + "/" + subd, os.path.splitext(test_iso3166_2_kg_mg_nr[country][subd]["flagUrl"])[0])) self.assertEqual(requests.get(test_iso3166_2_kg_mg_nr[country][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_iso3166_2_kg_mg_nr[country][subd]["flagUrl"])) for key in list(test_iso3166_2_kg_mg_nr[country][subd].keys()): @@ -250,7 +257,7 @@ def test_export_iso3166_2(self): self.assertEqual(test_iso3166_2_kg_mg_nr["KG"]["KG-GB"]["latLng"], [42.875, 74.57], "Expected subdivision latLng to be [42.875, 74.57], got {}.".format(test_iso3166_2_kg_mg_nr["KG"]["KG-GB"]["latLng"])) self.assertEqual(test_iso3166_2_kg_mg_nr["KG"]["KG-GB"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/KG/KG-GB.svg", - "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/KG/KG-GB.svg, got {}.".format(test_iso3166_2_kg_mg_nr["KG"]["KG-GB"]["flagUrl"])) + "Expected subdivision flag URL to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/KG/KG-GB.svg, got {}.".format(test_iso3166_2_kg_mg_nr["KG"]["KG-GB"]["flagUrl"])) #MG-D - Antsiranana self.assertEqual(test_iso3166_2_kg_mg_nr["MG"]["MG-D"]["name"], "Antsiranana", "Expected subdivsion name to be Antsiranana, got {}.".format(test_iso3166_2_kg_mg_nr["MG"]["MG-D"]["name"])) @@ -263,7 +270,7 @@ def test_export_iso3166_2(self): self.assertEqual(test_iso3166_2_kg_mg_nr["MG"]["MG-D"]["latLng"], [-12.323, 49.294], "Expected subdivision latLng to be [-12.323, 49.294], got {}.".format(test_iso3166_2_kg_mg_nr["MG"]["MG-D"]["latLng"])) self.assertEqual(test_iso3166_2_kg_mg_nr["MG"]["MG-D"]["flagUrl"], None, - "Expected subdivision flag url to be None, got {}.".format(test_iso3166_2_kg_mg_nr["MG"]["MG-D"]["flagUrl"])) + "Expected subdivision flag URL to be None, got {}.".format(test_iso3166_2_kg_mg_nr["MG"]["MG-D"]["flagUrl"])) #NR-02 - Anabar self.assertEqual(test_iso3166_2_kg_mg_nr["NR"]["NR-02"]["name"], "Anabar", "Expected subdivsion name to be Anabar, got {}.".format(test_iso3166_2_kg_mg_nr["NR"]["NR-02"]["name"])) @@ -276,18 +283,12 @@ def test_export_iso3166_2(self): self.assertEqual(test_iso3166_2_kg_mg_nr["NR"]["NR-02"]["latLng"], [-0.513, 166.948], "Expected subdivision latLng to be [-0.513, 166.948], got {}.".format(test_iso3166_2_kg_mg_nr["NR"]["NR-02"]["latLng"])) self.assertEqual(test_iso3166_2_kg_mg_nr["NR"]["NR-02"]["flagUrl"], None, - "Expected subdivision flag url to be None, got {}.".format(test_iso3166_2_kg_mg_nr["NR"]["NR-02"]["flagUrl"])) + "Expected subdivision flag URL to be None, got {}.".format(test_iso3166_2_kg_mg_nr["NR"]["NR-02"]["flagUrl"])) #5.) with (self.assertRaises(ValueError)): - export_iso3166_2(alpha2_codes=test_alpha2_error1, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #ZZ - export_iso3166_2(alpha2_codes=test_alpha2_error2, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #ABCDEF - export_iso3166_2(alpha2_codes=test_alpha2_error3, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #1234 -#6.) - # for alpha_2 in test_alpha2_bv_fo_gs_hk: - # export_iso3166_2(alpha2_codes=alpha_2, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) - # with (self.assertRaises(FileNotFoundError)): - # with open(os.path.join(self.test_output_dir, self.test_output_filename + "-" + alpha_2 + ".json")) as output_json: - # json.load(output_json) + export_iso3166_2(alpha_codes=test_alpha_error1, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #ZZ + export_iso3166_2(alpha_codes=test_alpha_error2, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #ABCDEF + export_iso3166_2(alpha_codes=test_alpha_error3, output_folder=self.test_output_dir, json_filename=self.test_output_filename, verbose=0) #1234 def tearDown(self): """ Delete any exported json folder. """ diff --git a/tests/test_iso3166_2.py b/tests/test_iso3166_2.py index 7b7b3a2..302c311 100644 --- a/tests/test_iso3166_2.py +++ b/tests/test_iso3166_2.py @@ -1,4 +1,4 @@ -import iso3166_2 as iso +from iso3166_2 import * import iso3166 import requests import getpass @@ -9,7 +9,6 @@ import unittest unittest.TestLoader.sortTestMethodsUsing = None -# @unittest.skip("") class ISO3166_2_Tests(unittest.TestCase): """ Test suite for testing the iso3166-2 Python software package. @@ -19,8 +18,7 @@ class ISO3166_2_Tests(unittest.TestCase): test_iso3166_2_metadata: testing correct software metdata for the iso3166-2 package. test_iso3166_2: - testing correct data returned from the "subdivisions" object in the ISO3166_2 class of the - iso3166-2 package. + testing correct data returned from the ISO3166_2 class of the iso3166-2 package. test_iso3166_2_json: testing correct objects are returned from the ISO 3166-2 JSON, using a variety of inputs. test_subdivision_names: @@ -29,8 +27,6 @@ class ISO3166_2_Tests(unittest.TestCase): testing correct ISO 3166-2 subdivision local names are returned from the subdivision_local_names() class function. test_subdivision_codes: testing correct ISO 3166-2 subdivision codes are returned from the subdivision_codes() class function. - test_subdivision_parent_codes: - testing correct ISO 3166-2 subdivision parent codes are returned from the subdivision_parent_codes() class function. test_custom_subdivision: testing custom_subdivision function that adds or deletes custom subdivisions to the main iso3166-2.json object. test_search: @@ -49,6 +45,9 @@ def setUp(self): #list of data attributes for main iso3166-2 json self.correct_output_attributes = ['name', 'localName', 'type', 'parentCode', 'latLng', 'flagUrl'] + #class instance with all ISO 3166-2 data + self.all_iso3166_2 = ISO3166_2() + #create test output directory - remove if already present self.test_output_dir = "test_output" if (os.path.isdir(self.test_output_dir)): @@ -57,16 +56,16 @@ def setUp(self): def test_iso3166_2_metadata(self): """ Testing correct iso3166-2 software version and metadata. """ - # self.assertEqual(metadata('iso3166-2')['version'], "1.4.0", - # "iso3166-2 version is not correct, expected 1.4.0, got {}.".format(metadata('iso3166-2')['version'])) + # self.assertEqual(metadata('iso3166-2')['version'], "1.5.0", + # "iso3166-2 version is not correct, expected 1.5.0, got {}.".format(metadata('iso3166-2')['version'])) self.assertEqual(metadata('iso3166-2')['name'], "iso3166-2", "iso3166-2 software name is not correct, expected iso3166-2, got {}.".format(metadata('iso3166-2')['name'])) self.assertEqual(metadata('iso3166-2')['author'], "AJ McKenna, https://github.com/amckenna41", "iso3166-2 author is not correct, expected AJ McKenna, got {}.".format(metadata('iso3166-2')['author'])) self.assertEqual(metadata('iso3166-2')['author-email'], "amckenna41@qub.ac.uk", "iso3166-2 author email is not correct, expected amckenna41@qub.ac.uk, got {}.".format(metadata('iso3166-2')['author-email'])) - # self.assertEqual(metadata('iso3166-2')['summary'], "A lightweight Python package, and accompanying API, that can be used to access all of the world's most up-to-date and accurate ISO 3166-2 subdivision data, including: name, local name, code, parent code, type, latitude/longitude and flag.", - # "iso3166-2 package summary is not correct, got: {}.".format(metadata('iso3166-2')['summary'])) + self.assertEqual(metadata('iso3166-2')['summary'], "A lightweight Python package, and accompanying API, that can be used to access all of the world's most up-to-date and accurate ISO 3166-2 subdivision data, including: name, local name, code, parent code, type, latitude/longitude and flag.", + "iso3166-2 package summary is not correct, got: {}.".format(metadata('iso3166-2')['summary'])) self.assertEqual(metadata('iso3166-2')['keywords'], "iso,iso3166,beautifulsoup,python,pypi,countries,country codes,iso3166-2,iso3166-1,alpha-2,iso3166-updates,subdivisions", "iso3166-2 keywords are not correct, got:\n{}.".format(metadata('iso3166-2')['keywords'])) self.assertEqual(metadata('iso3166-2')['home-page'], "https://github.com/amckenna41/iso3166-2", @@ -78,158 +77,216 @@ def test_iso3166_2_metadata(self): def test_iso3166_2(self): """ Test ISO 3166-2 class and its methods and attributes. """ - #testing class using iso3166-2.json file as input - self.assertIsInstance(iso.country.alpha_2, list, - "Expected alpha-2 attribute to be a list, got {}.".format(type(iso.country.alpha_2))) - self.assertEqual(len(iso.country.alpha_2), 250, - "Expected 250 alpha-2 codes, got {}.".format(len(iso.country.alpha_2))) - self.assertIsInstance(iso.country.all, dict, - "Expected ISO 3166-2 data object to be a dict, got {}.".format(type(iso.country.all))) - self.assertEqual(len(iso.country.all), 250, - "Expected 250 countrys in ISO 3166-2 data object, got {}.".format(len(iso.country.all))) - self.assertIsInstance(iso.country.attributes, list, - "Expected attributes class variable to be a list, got {}.".format(type(iso.country.attributes))) - self.assertEqual(set(iso.country.attributes), set(self.correct_output_attributes), - "List of attributes in class do not match expected, got:\n{}.".format(iso.country.attributes)) - for code in iso.country.all: - self.assertIn(code, iso.country.alpha_2, - "Alpha-2 code {} not found in list of available 2 letter codes.".format(code)) - + self.assertIsInstance(self.all_iso3166_2.all, dict, + "Expected ISO 3166-2 data object to be a dict, got {}.".format(type(self.all_iso3166_2.all))) + self.assertEqual(len(self.all_iso3166_2.all), 250, + "Expected 250 countrys in ISO 3166-2 data object, got {}.".format(len(self.all_iso3166_2.all))) + self.assertIsInstance(self.all_iso3166_2.attributes, list, + "Expected attributes class variable to be a list, got {}.".format(type(self.all_iso3166_2.attributes))) + self.assertEqual(set(self.all_iso3166_2.attributes), set(self.correct_output_attributes), + "List of attributes in class do not match expected, got:\n{}.".format(self.all_iso3166_2.attributes)) + # self.assertIsInstance(self.all_iso3166_2.alpha_2, list, + # "Expected alpha-2 attribute to be a list, got {}.".format(type(self.all_iso3166_2.alpha_2))) + # self.assertEqual(len(self.all_iso3166_2.alpha_2), 250, + # "Expected 250 alpha-2 codes, got {}.".format(len(self.all_iso3166_2.alpha_2))) + # for code in self.all_iso3166_2.all: + # self.assertIn(code, self.all_iso3166_2.alpha_2, + # "Alpha-2 code {} not found in list of available 2 letter codes.".format(code)) + # self.assertIsInstance(self.all_iso3166_2.alpha_3, list, + # "Expected alpha-3 attribute to be a list, got {}.".format(type(self.all_iso3166_2.alpha_3))) + # self.assertEqual(len(self.all_iso3166_2.alpha_3), 250, + # "Expected 250 alpha-3 codes, got {}.".format(len(self.all_iso3166_2.alpha_3))) + # self.assertIsInstance(self.all_iso3166_2.numeric, list, + # "Expected numeric attribute to be a list, got {}.".format(type(self.all_iso3166_2.numeric))) + # self.assertEqual(len(self.all_iso3166_2.numeric), 250, + # "Expected 250 numeric codes, got {}.".format(len(self.all_iso3166_2.numeric))) + def test_iso3166_2_json(self): """ Testing ISO 3166-2 JSON contents and data. """ - test_alpha2_ba = iso.country["BA"] #Bosnia and Herzegovina - test_alpha2_cy = iso.country["CY"] #Cyprus - test_alpha2_ga = iso.country["GA"] #Gabon - test_alpha2_rw_vu = iso.country["RW, VU"] #Rwanda, Vanuatu - test_alpha2_gg_kw_vc = iso.country["GG, KW, VC"] #Guernsey, Kuwait, St Vincent & the Grenadines + test_alpha_ba = self.all_iso3166_2["BA"] #Bosnia and Herzegovina + test_alpha_cy = self.all_iso3166_2["CY"] #Cyprus + test_alpha_gab = self.all_iso3166_2["GAB"] #Gabon + test_alpha_rw_548 = self.all_iso3166_2["RW, 548"] #Rwanda, Vanuatu + test_alpha_gg_kwt_670 = self.all_iso3166_2["GG, KWT, 670"] #Guernsey, Kuwait, St Vincent & the Grenadines #1.) ba_subdivision_codes = ['BA-BIH', 'BA-BRC', 'BA-SRP'] ba_subdivision_names = ['Federacija Bosne i Hercegovine', 'Brčko distrikt', 'Republika Srpska'] - self.assertIsInstance(test_alpha2_ba, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha2_ba))) - self.assertEqual(len(test_alpha2_ba), 3, "Expected 3 total subdivision outputs, got {}.".format(len(test_alpha2_ba))) - self.assertEqual(list(test_alpha2_ba.keys()), ba_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha2_ba.keys()))) - self.assertEqual(list(test_alpha2_ba['BA-BIH'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha2_ba['BA-BIH'].keys()))) - for key in test_alpha2_ba: - self.assertIn(test_alpha2_ba[key].name, ba_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha2_ba[key].name, ba_subdivision_names)) - if (not (test_alpha2_ba[key].flagUrl is None) and (test_alpha2_ba[key].flagUrl != "")): - self.assertEqual(requests.get(test_alpha2_ba[key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha2_ba[key].flagUrl)) - if not (test_alpha2_ba[key]["parentCode"] is None): - self.assertIn(test_alpha2_ba[key]["parentCode"], list(test_alpha2_ba[key].keys()), - "Parent code {} not found in list of subdivision codes:\n.".format(test_alpha2_ba[key]["parentCode"], list(test_alpha2_ba[key].keys()))) - self.assertEqual(len(test_alpha2_ba[key].latLng), 2, "Expected key should have both lat/longitude.") + self.assertIsInstance(test_alpha_ba, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha_ba))) + self.assertEqual(len(test_alpha_ba), 3, "Expected 3 total subdivision outputs, got {}.".format(len(test_alpha_ba))) + self.assertEqual(list(test_alpha_ba.keys()), ba_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha_ba.keys()))) + self.assertEqual(list(test_alpha_ba['BA-BIH'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha_ba['BA-BIH'].keys()))) + for key in test_alpha_ba: + self.assertIn(test_alpha_ba[key].name, ba_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha_ba[key].name, ba_subdivision_names)) + if (not (test_alpha_ba[key].flagUrl is None) and (test_alpha_ba[key].flagUrl != "")): + self.assertEqual(requests.get(test_alpha_ba[key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha_ba[key].flagUrl)) + if not (test_alpha_ba[key]["parentCode"] is None): + self.assertIn(test_alpha_ba[key]["parentCode"], list(test_alpha_ba[key].keys()), + "Parent code {} not found in list of subdivision codes:\n.".format(test_alpha_ba[key]["parentCode"], list(test_alpha_ba[key].keys()))) + self.assertEqual(len(test_alpha_ba[key].latLng), 2, "Expected key should have both lat/longitude.") #2.) cy_subdivision_codes = ['CY-01', 'CY-02', 'CY-03', 'CY-04', 'CY-05', 'CY-06'] cy_subdivision_names = ['Lefkosia', 'Lemesos', 'Larnaka', 'Ammochostos', 'Baf', 'Girne'] - self.assertIsInstance(test_alpha2_cy, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha2_cy))) - self.assertEqual(len(test_alpha2_cy), 6, "Expected 6 total subdivision outputs, got {}.".format(len(test_alpha2_cy))) - self.assertEqual(list(test_alpha2_cy.keys()), cy_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha2_cy.keys()))) - self.assertEqual(list(test_alpha2_cy['CY-01'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha2_cy['CY-01'].keys()))) - for key in test_alpha2_cy: - self.assertIn(test_alpha2_cy[key].name, cy_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha2_cy[key].name, cy_subdivision_names)) - if (not (test_alpha2_cy[key].flagUrl is None) and (test_alpha2_cy[key].flagUrl != "")): - self.assertEqual(requests.get(test_alpha2_cy[key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha2_cy[key].flagUrl)) - if not (test_alpha2_cy[key]["parentCode"] is None): - self.assertIn(test_alpha2_cy[key]["parentCode"], list(test_alpha2_cy[key].keys()), - "Parent code {} not found in list of subdivision codes:\n.".format(test_alpha2_cy[key]["parentCode"], list(test_alpha2_cy[key].keys()))) - self.assertEqual(len(test_alpha2_cy[key].latLng), 2, "Expected key should have both lat/longitude.") + self.assertIsInstance(test_alpha_cy, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha_cy))) + self.assertEqual(len(test_alpha_cy), 6, "Expected 6 total subdivision outputs, got {}.".format(len(test_alpha_cy))) + self.assertEqual(list(test_alpha_cy.keys()), cy_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha_cy.keys()))) + self.assertEqual(list(test_alpha_cy['CY-01'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha_cy['CY-01'].keys()))) + for key in test_alpha_cy: + self.assertIn(test_alpha_cy[key].name, cy_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha_cy[key].name, cy_subdivision_names)) + if (not (test_alpha_cy[key].flagUrl is None) and (test_alpha_cy[key].flagUrl != "")): + self.assertEqual(requests.get(test_alpha_cy[key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha_cy[key].flagUrl)) + if not (test_alpha_cy[key]["parentCode"] is None): + self.assertIn(test_alpha_cy[key]["parentCode"], list(test_alpha_cy[key].keys()), + "Parent code {} not found in list of subdivision codes:\n.".format(test_alpha_cy[key]["parentCode"], list(test_alpha_cy[key].keys()))) + self.assertEqual(len(test_alpha_cy[key].latLng), 2, "Expected key should have both lat/longitude.") #3.) ga_subdivision_codes = ['GA-1', 'GA-2', 'GA-3', 'GA-4', 'GA-5', 'GA-6', 'GA-7', 'GA-8', 'GA-9'] ga_subdivision_names = ['Estuaire', 'Haut-Ogooué', 'Moyen-Ogooué', 'Ngounié', 'Nyanga', 'Ogooué-Ivindo', 'Ogooué-Lolo', 'Ogooué-Maritime', 'Woleu-Ntem'] - self.assertIsInstance(test_alpha2_ga, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha2_ga))) - self.assertEqual(len(test_alpha2_ga), 9, "Expected 9 total subdivision outputs, got {}.".format(len(test_alpha2_ga))) - self.assertEqual(list(test_alpha2_ga.keys()), ga_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha2_ga.keys()))) - self.assertEqual(list(test_alpha2_ga['GA-1'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha2_ga['GA-1'].keys()))) - for key in test_alpha2_ga: - self.assertIn(test_alpha2_ga[key].name, ga_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha2_ga[key].name, ga_subdivision_names)) - if (not (test_alpha2_ga[key].flagUrl is None) and (test_alpha2_ga[key].flagUrl != "")): - self.assertEqual(requests.get(test_alpha2_ga[key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha2_ga[key].flagUrl)) - if not (test_alpha2_ga[key]["parentCode"] is None): - self.assertIn(test_alpha2_ga[key]["parentCode"], list(test_alpha2_ga[key].keys()), - "Parent code {} not found in list of subdivision codes:\n.".format(test_alpha2_ga[key]["parentCode"], list(test_alpha2_ga[key].keys()))) - self.assertEqual(len(test_alpha2_ga[key].latLng), 2, "Expected key should have both lat/longitude.") + self.assertIsInstance(test_alpha_gab, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha_gab))) + self.assertEqual(len(test_alpha_gab), 9, "Expected 9 total subdivision outputs, got {}.".format(len(test_alpha_gab))) + self.assertEqual(list(test_alpha_gab.keys()), ga_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha_gab.keys()))) + self.assertEqual(list(test_alpha_gab['GA-1'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha_gab['GA-1'].keys()))) + for key in test_alpha_gab: + self.assertIn(test_alpha_gab[key].name, ga_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha_gab[key].name, ga_subdivision_names)) + if (not (test_alpha_gab[key].flagUrl is None) and (test_alpha_gab[key].flagUrl != "")): + self.assertEqual(requests.get(test_alpha_gab[key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha_gab[key].flagUrl)) + if not (test_alpha_gab[key]["parentCode"] is None): + self.assertIn(test_alpha_gab[key]["parentCode"], list(test_alpha_gab[key].keys()), + "Parent code {} not found in list of subdivision codes:\n.".format(test_alpha_gab[key]["parentCode"], list(test_alpha_gab[key].keys()))) + self.assertEqual(len(test_alpha_gab[key].latLng), 2, "Expected key should have both lat/longitude.") #4.) rw_subdivision_codes = ['RW-01', 'RW-02', 'RW-03', 'RW-04', 'RW-05'] vu_subdivision_codes = ['VU-MAP', 'VU-PAM', 'VU-SAM', 'VU-SEE', 'VU-TAE', 'VU-TOB'] rw_subdivision_names = ['City of Kigali', 'Eastern', 'Northern', 'Western', 'Southern'] vu_subdivision_names = ['Malampa', 'Pénama', 'Sanma', 'Shéfa', 'Taféa', 'Torba'] - self.assertIsInstance(test_alpha2_rw_vu, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha2_rw_vu))) - self.assertEqual(list(test_alpha2_rw_vu.keys()), ['RW', 'VU'], "Expected output keys to be RW and VU, got {}.".format(list(test_alpha2_rw_vu.keys()))) - self.assertEqual(len(test_alpha2_rw_vu["RW"]), 5, "Expected 5 total subdivision outputs, got {}.".format(len(test_alpha2_rw_vu["RW"]))) - self.assertEqual(len(test_alpha2_rw_vu["VU"]), 6, "Expected 6 total subdivision outputs, got {}.".format(len(test_alpha2_rw_vu["VU"]))) - self.assertEqual(list(test_alpha2_rw_vu["RW"].keys()), rw_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha2_rw_vu["RW"].keys()))) - self.assertEqual(list(test_alpha2_rw_vu["VU"].keys()), vu_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha2_rw_vu["VU"].keys()))) - self.assertEqual(list(test_alpha2_rw_vu["RW"]['RW-01'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha2_rw_vu["RW"]['RW-01'].keys()))) - self.assertEqual(list(test_alpha2_rw_vu["VU"]['VU-MAP'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha2_rw_vu["VU"]['VU-MAP'].keys()))) - for key in test_alpha2_rw_vu["RW"]: - self.assertIn(test_alpha2_rw_vu["RW"][key].name, rw_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha2_rw_vu["RW"][key].name, rw_subdivision_names)) - if (not (test_alpha2_rw_vu["RW"].flagUrl is None) and (test_alpha2_rw_vu["RW"].flagUrl != "")): - self.assertEqual(requests.get(test_alpha2_rw_vu["RW"][key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha2_rw_vu["RW"][key].flagUrl)) - self.assertEqual(len(test_alpha2_rw_vu["RW"][key].latLng), 2, "Expected key should have both lat/longitude.") - for key in test_alpha2_rw_vu["VU"]: - self.assertIn(test_alpha2_rw_vu["VU"][key].name, vu_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha2_rw_vu["VU"][key].name, vu_subdivision_names)) - if (not (test_alpha2_rw_vu["VU"].flagUrl is None) and (test_alpha2_rw_vu["VU"].flagUrl != "")): - self.assertEqual(requests.get(test_alpha2_rw_vu["VU"][key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha2_rw_vu["VU"][key].flagUrl)) - self.assertEqual(len(test_alpha2_rw_vu["VU"][key].latLng), 2, "Expected key should have both lat/longitude.") + self.assertIsInstance(test_alpha_rw_548, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha_rw_548))) + self.assertEqual(list(test_alpha_rw_548.keys()), ['RW', 'VU'], "Expected output keys to be RW and VU, got {}.".format(list(test_alpha_rw_548.keys()))) + self.assertEqual(len(test_alpha_rw_548["RW"]), 5, "Expected 5 total subdivision outputs, got {}.".format(len(test_alpha_rw_548["RW"]))) + self.assertEqual(len(test_alpha_rw_548["VU"]), 6, "Expected 6 total subdivision outputs, got {}.".format(len(test_alpha_rw_548["VU"]))) + self.assertEqual(list(test_alpha_rw_548["RW"].keys()), rw_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha_rw_548["RW"].keys()))) + self.assertEqual(list(test_alpha_rw_548["VU"].keys()), vu_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha_rw_548["VU"].keys()))) + self.assertEqual(list(test_alpha_rw_548["RW"]['RW-01'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha_rw_548["RW"]['RW-01'].keys()))) + self.assertEqual(list(test_alpha_rw_548["VU"]['VU-MAP'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], "Expected keys for output dict don't match\n{}.".format(list(test_alpha_rw_548["VU"]['VU-MAP'].keys()))) + for key in test_alpha_rw_548["RW"]: + self.assertIn(test_alpha_rw_548["RW"][key].name, rw_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha_rw_548["RW"][key].name, rw_subdivision_names)) + if (not (test_alpha_rw_548["RW"].flagUrl is None) and (test_alpha_rw_548["RW"].flagUrl != "")): + self.assertEqual(requests.get(test_alpha_rw_548["RW"][key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha_rw_548["RW"][key].flagUrl)) + self.assertEqual(len(test_alpha_rw_548["RW"][key].latLng), 2, "Expected key should have both lat/longitude.") + for key in test_alpha_rw_548["VU"]: + self.assertIn(test_alpha_rw_548["VU"][key].name, vu_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha_rw_548["VU"][key].name, vu_subdivision_names)) + if (not (test_alpha_rw_548["VU"].flagUrl is None) and (test_alpha_rw_548["VU"].flagUrl != "")): + self.assertEqual(requests.get(test_alpha_rw_548["VU"][key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha_rw_548["VU"][key].flagUrl)) + self.assertEqual(len(test_alpha_rw_548["VU"][key].latLng), 2, "Expected key should have both lat/longitude.") #5.) kw_subdivision_codes = ['KW-AH', 'KW-FA', 'KW-HA', 'KW-JA', 'KW-KU', 'KW-MU'] kw_subdivision_names = ['Al Aḩmadī', 'Al Farwānīyah', 'Ḩawallī', "Al Jahrā’", "Al ‘Āşimah", 'Mubārak al Kabīr'] vc_subdivision_codes = ['VC-01', 'VC-02', 'VC-03', 'VC-04', 'VC-05', 'VC-06'] vc_subdivision_names = ['Charlotte', 'Saint Andrew', 'Saint David', 'Saint George', 'Saint Patrick', 'Grenadines'] - self.assertIsInstance(test_alpha2_gg_kw_vc, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha2_gg_kw_vc))) - self.assertEqual(list(test_alpha2_gg_kw_vc.keys()), ['GG', 'KW', 'VC'], "Expected output keys to be GG, KW and VC, got {}.".format(list(test_alpha2_gg_kw_vc.keys()))) - self.assertEqual(len(test_alpha2_gg_kw_vc["GG"]), 0, "Expected 0 total subdivision outputs, got {}.".format(len(test_alpha2_gg_kw_vc["GG"]))) - self.assertEqual(len(test_alpha2_gg_kw_vc["KW"]), 6, "Expected 6 total subdivision outputs, got {}.".format(len(test_alpha2_gg_kw_vc["KW"]))) - self.assertEqual(len(test_alpha2_gg_kw_vc["VC"]), 6, "Expected 6 total subdivision outputs, got {}.".format(len(test_alpha2_gg_kw_vc["VC"]))) - self.assertEqual(list(test_alpha2_gg_kw_vc["GG"].keys()), [], "Expected no subdivision codes, got {}.".format(list(test_alpha2_gg_kw_vc["GG"].keys()))) - self.assertEqual(list(test_alpha2_gg_kw_vc["KW"].keys()), kw_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha2_gg_kw_vc["KW"].keys()))) - self.assertEqual(list(test_alpha2_gg_kw_vc["VC"].keys()), vc_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha2_gg_kw_vc["VC"].keys()))) - self.assertEqual(list(test_alpha2_gg_kw_vc["KW"]['KW-AH'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], - "Expected keys do not match output:\n{}.".format(list(test_alpha2_gg_kw_vc["KW"]['KW-AH'].keys()))) - self.assertEqual(list(test_alpha2_gg_kw_vc["VC"]['VC-01'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], - "Expected keys do not match output:\n{}.".format(list(test_alpha2_gg_kw_vc["VC"]['VC-01'].keys()))) - for key in test_alpha2_gg_kw_vc["KW"]: - self.assertIn(test_alpha2_gg_kw_vc["KW"][key].name, kw_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha2_gg_kw_vc["KW"][key].name, kw_subdivision_names)) - if (not (test_alpha2_gg_kw_vc["KW"].flagUrl is None) and (test_alpha2_gg_kw_vc["KW"].flagUrl != "")): - self.assertEqual(requests.get(test_alpha2_gg_kw_vc["KW"][key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha2_gg_kw_vc["KW"][key].flagUrl)) - self.assertEqual(len(test_alpha2_gg_kw_vc["KW"][key].latLng), 2, "Expected key should have both lat/longitude.") - for key in test_alpha2_gg_kw_vc["VC"]: - self.assertIn(test_alpha2_gg_kw_vc["VC"][key].name, vc_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha2_gg_kw_vc["VC"][key].name, vc_subdivision_names)) - if (not (test_alpha2_gg_kw_vc["VC"].flagUrl is None) and (test_alpha2_gg_kw_vc["VC"].flagUrl != "")): - self.assertEqual(requests.get(test_alpha2_gg_kw_vc["VC"][key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha2_gg_kw_vc["VC"][key].flagUrl)) - self.assertEqual(len(test_alpha2_gg_kw_vc["VC"][key].latLng), 2, "Expected key should have both lat/longitude.") + self.assertIsInstance(test_alpha_gg_kwt_670, dict, "Expected output object to be of type dict, got {}.".format(type(test_alpha_gg_kwt_670))) + self.assertEqual(list(test_alpha_gg_kwt_670.keys()), ['GG', 'KW', 'VC'], "Expected output keys to be GG, KW and VC, got {}.".format(list(test_alpha_gg_kwt_670.keys()))) + self.assertEqual(len(test_alpha_gg_kwt_670["GG"]), 0, "Expected 0 total subdivision outputs, got {}.".format(len(test_alpha_gg_kwt_670["GG"]))) + self.assertEqual(len(test_alpha_gg_kwt_670["KW"]), 6, "Expected 6 total subdivision outputs, got {}.".format(len(test_alpha_gg_kwt_670["KW"]))) + self.assertEqual(len(test_alpha_gg_kwt_670["VC"]), 6, "Expected 6 total subdivision outputs, got {}.".format(len(test_alpha_gg_kwt_670["VC"]))) + self.assertEqual(list(test_alpha_gg_kwt_670["GG"].keys()), [], "Expected no subdivision codes, got {}.".format(list(test_alpha_gg_kwt_670["GG"].keys()))) + self.assertEqual(list(test_alpha_gg_kwt_670["KW"].keys()), kw_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha_gg_kwt_670["KW"].keys()))) + self.assertEqual(list(test_alpha_gg_kwt_670["VC"].keys()), vc_subdivision_codes, "Subdivison codes do not equal expected codes:\n{}.".format(list(test_alpha_gg_kwt_670["VC"].keys()))) + self.assertEqual(list(test_alpha_gg_kwt_670["KW"]['KW-AH'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], + "Expected keys do not match output:\n{}.".format(list(test_alpha_gg_kwt_670["KW"]['KW-AH'].keys()))) + self.assertEqual(list(test_alpha_gg_kwt_670["VC"]['VC-01'].keys()), ['name', 'localName', 'type', 'parentCode', 'flagUrl', 'latLng'], + "Expected keys do not match output:\n{}.".format(list(test_alpha_gg_kwt_670["VC"]['VC-01'].keys()))) + for key in test_alpha_gg_kwt_670["KW"]: + self.assertIn(test_alpha_gg_kwt_670["KW"][key].name, kw_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha_gg_kwt_670["KW"][key].name, kw_subdivision_names)) + if (not (test_alpha_gg_kwt_670["KW"].flagUrl is None) and (test_alpha_gg_kwt_670["KW"].flagUrl != "")): + self.assertEqual(requests.get(test_alpha_gg_kwt_670["KW"][key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha_gg_kwt_670["KW"][key].flagUrl)) + self.assertEqual(len(test_alpha_gg_kwt_670["KW"][key].latLng), 2, "Expected key should have both lat/longitude.") + for key in test_alpha_gg_kwt_670["VC"]: + self.assertIn(test_alpha_gg_kwt_670["VC"][key].name, vc_subdivision_names, "Subdivision name {} not found in list of subdivision names:\n{}.".format(test_alpha_gg_kwt_670["VC"][key].name, vc_subdivision_names)) + if (not (test_alpha_gg_kwt_670["VC"].flagUrl is None) and (test_alpha_gg_kwt_670["VC"].flagUrl != "")): + self.assertEqual(requests.get(test_alpha_gg_kwt_670["VC"][key].flagUrl, headers=self.user_agent_header).status_code, 200, "Flag URL invalid: {}.".format(test_alpha_gg_kwt_670["VC"][key].flagUrl)) + self.assertEqual(len(test_alpha_gg_kwt_670["VC"][key].latLng), 2, "Expected key should have both lat/longitude.") #6.) - for country in iso.country.all: #testing that all subdivisions have a subdivision name - for subd in iso.country.all[country]: - self.assertTrue((iso.country.all[country][subd]["name"] != "" and iso.country.all[country][subd]["name"] != []), "") + for country in self.all_iso3166_2.all: #testing that all subdivisions have a subdivision name, local name, type and lat/lng value + for subd in self.all_iso3166_2.all[country]: + self.assertTrue((self.all_iso3166_2.all[country][subd]["name"] != "" and self.all_iso3166_2.all[country][subd]["name"] != []), "") + self.assertTrue((self.all_iso3166_2.all[country][subd]["localName"] != "" and self.all_iso3166_2.all[country][subd]["localName"] != []), "") + self.assertTrue((self.all_iso3166_2.all[country][subd]["type"] != "" and self.all_iso3166_2.all[country][subd]["type"] != []), "") + self.assertTrue((self.all_iso3166_2.all[country][subd]["latLng"] != "" and self.all_iso3166_2.all[country][subd]["latLng"] != []), "") #7.) - for country in iso.country.all: #testing that all subdivisions have a subdivision local name - for subd in iso.country.all[country]: - self.assertTrue((iso.country.all[country][subd]["localName"] != "" and iso.country.all[country][subd]["localName"] != []), "") -#8.) - for country in iso.country.all: #testing that all subdivisions have a subdivision type - for subd in iso.country.all[country]: - self.assertTrue((iso.country.all[country][subd]["type"] != "" and iso.country.all[country][subd]["type"] != []), "") -#9.) - for country in iso.country.all: #testing that all subdivisions have a latitude/longitude - for subd in iso.country.all[country]: - self.assertTrue((iso.country.all[country][subd]["latLng"] != "" and iso.country.all[country][subd]["latLng"] != []), "") -#10.) with (self.assertRaises(ValueError)): - iso.country["ZZ"] - iso.country["XY"] - iso.country["XYZ"] - iso.country["AB, CD, EF"] + ISO3166_2("ZZ") + ISO3166_2("XY") + ISO3166_2("XYZ") + ISO3166_2("AB, CD, EF") + ISO3166_2("56789") + self.all_iso3166_2["ZZ"] + self.all_iso3166_2["XY"] + self.all_iso3166_2["XYZ"] + self.all_iso3166_2["AB, CD, EF"] #11.) with (self.assertRaises(TypeError)): - iso.country[123] - iso.country[0.5] - iso.country[False] + self.all_iso3166_2[123] + self.all_iso3166_2[0.5] + self.all_iso3166_2[False] + + def test_subdivision_codes(self): + """ Testing functionality for getting list of all ISO 3166-2 subdivision codes. """ + expected_bq_subdivision_codes = ['BQ-BO', 'BQ-SA', 'BQ-SE'] + expected_sz_subdivision_codes = ['SZ-HH', 'SZ-LU', 'SZ-MA', 'SZ-SH'] + expected_sm_subdivision_codes = ['SM-01', 'SM-02', 'SM-03', 'SM-04', 'SM-05', 'SM-06', 'SM-07', 'SM-08', 'SM-09'] + expected_pw_subdivision_codes = ['PW-002', 'PW-004', 'PW-010', 'PW-050', 'PW-100', 'PW-150', 'PW-212', 'PW-214', \ + 'PW-218', 'PW-222', 'PW-224', 'PW-226', 'PW-227', 'PW-228', 'PW-350', 'PW-370'] + expected_wf_subdivision_codes = ['WF-AL', 'WF-SG', 'WF-UV'] + expected_mg_sb_subdivision_codes = {"MG": ["MG-A", "MG-D", "MG-F", "MG-M", "MG-T", "MG-U"], + "SB": ["SB-CE", "SB-CH", "SB-CT", "SB-GU", "SB-IS", "SB-MK", "SB-ML", "SB-RB", "SB-TE", "SB-WE"]} + expected_gnq_tca_subdivision_codes = {"GQ": ["GQ-AN", "GQ-BN", "GQ-BS", "GQ-C", "GQ-CS", "GQ-DJ", "GQ-I", "GQ-KN", "GQ-LI", "GQ-WN"], + "TC": []} +#1.) + bg_instance = ISO3166_2("BQ") #Bonaire, Sint Eustatius and Saba + bq_subdivision_codes = bg_instance.subdivision_codes() + self.assertEqual(bq_subdivision_codes, expected_bq_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_bq_subdivision_codes)) +#2.) + sz_instance = ISO3166_2("SZ") #Eswatini + sz_subdivision_codes = sz_instance.subdivision_codes() + self.assertEqual(sz_subdivision_codes, expected_sz_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_sz_subdivision_codes)) +#3.) + sm_instance = ISO3166_2("SMR") #San Marino + sm_subdivision_codes = sm_instance.subdivision_codes() + self.assertEqual(sm_subdivision_codes, expected_sm_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_sm_subdivision_codes)) +#4.) + pw_instance = ISO3166_2("PLW") #Palau + pw_subdivision_codes = pw_instance.subdivision_codes() + self.assertEqual(pw_subdivision_codes, expected_pw_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_pw_subdivision_codes)) +#5.) + wf_instance = ISO3166_2("876") #Wallis and Futuna + wf_subdivision_codes = wf_instance.subdivision_codes() + self.assertEqual(wf_subdivision_codes, expected_wf_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_wf_subdivision_codes)) +#6.) + mg_sb_instance = ISO3166_2("MG, 090") #Madagascar, Solomon Islands + mg_sb_subdivision_codes = mg_sb_instance.subdivision_codes() + self.assertEqual(mg_sb_subdivision_codes, expected_mg_sb_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_mg_sb_subdivision_codes)) +#7.) + gnq_tca_instance = ISO3166_2("GNQ, TCA") #Equitorial Guinea, Turks and Caicos Islands + gnq_tca_subdivision_codes = gnq_tca_instance.subdivision_codes() + self.assertEqual(gnq_tca_subdivision_codes, expected_gnq_tca_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_gnq_tca_subdivision_codes)) +#8.) + all_subdivision_codes = self.all_iso3166_2.subdivision_codes() + self.assertEqual(len(all_subdivision_codes), 250, "Expected 250 total country objects in output, got {}.".format(len(all_subdivision_codes))) + for key, val in all_subdivision_codes.items(): + self.assertIn(key, list(iso3166.countries_by_alpha2.keys()), "Country code {} not found in list of ISO 3166 alpha-2 codes.".format(key)) + self.assertIsInstance(val, list, "Expected output of subdivision names to be of type list, got {}.".format(type(val))) +#9.) + with (self.assertRaises(ValueError)): + self.all_iso3166_2.subdivision_codes("ABCD") + self.all_iso3166_2.subdivision_codes("Z") + self.all_iso3166_2.subdivision_codes("1234") + bg_instance.subdivision_codes("AD") + sz_instance.subdivision_codes("KM") + wf_instance.subdivision_codes("090") def test_subdivision_names(self): """ Testing functionality for getting list of all ISO 3166-2 subdivision names. """ @@ -243,39 +300,50 @@ def test_subdivision_names(self): expected_dj_va_subdivision_names = {"DJ": ['Ali Sabieh', 'Arta', 'Awbūk', 'Dikhil', 'Djibouti', 'Tadjourah'], "VA": []} #1.) - km_subdivision_names = iso.country.subdivision_names("KM") #Comoros + km_instance = ISO3166_2("KM") #Comoros + km_subdivision_names = km_instance.subdivision_names() self.assertEqual(km_subdivision_names, expected_km_subdivision_names, "Expected subdivison names don't match output:\n{}.".format(expected_km_subdivision_names)) #2.) - er_subdivision_names = iso.country.subdivision_names("ER") #Eritrea + er_instance = ISO3166_2("ER") #Eritrea + er_subdivision_names = er_instance.subdivision_names() self.assertEqual(er_subdivision_names, expected_er_subdivision_names, "Expected subdivison names don't match output:\n{}.".format(expected_er_subdivision_names)) #3.) - gl_subdivision_names = iso.country.subdivision_names("GL") #Greenland + gl_instance = ISO3166_2("GRL") #Greenland + gl_subdivision_names = gl_instance.subdivision_names() self.assertEqual(gl_subdivision_names, expected_gl_subdivision_names, "Expected subdivison names don't match output:\n{}.".format(expected_gl_subdivision_names)) #4.) - ls_subdivision_names = iso.country.subdivision_names("LS") #Lesotho + ls_instance = ISO3166_2("LSO") #Lesotho + ls_subdivision_names = ls_instance.subdivision_names() self.assertEqual(ls_subdivision_names, expected_ls_subdivision_names, "Expected subdivison names don't match output:\n{}.".format(expected_ls_subdivision_names)) #5.) - zm_subdivision_names = iso.country.subdivision_names("ZM") #Zambia + zm_instance = ISO3166_2("894") #Zambia + zm_subdivision_names = zm_instance.subdivision_names() self.assertEqual(zm_subdivision_names, expected_zm_subdivision_names, "Expected subdivison names don't match output:\n{}.".format(expected_zm_subdivision_names)) #6.) - ag_bn_subdivision_names = iso.country.subdivision_names("AG, BN") #Antigua and Barbuda, Brunei + ag_bn_instance = ISO3166_2("028, BN") #Antigua and Barbuda, Brunei + ag_bn_subdivision_names = ag_bn_instance.subdivision_names() self.assertEqual(ag_bn_subdivision_names, expected_ag_bn_subdivision_names, "Expected subdivison names don't match output:\n{}.".format(expected_ag_bn_subdivision_names)) #7.) - dj_va_subdivision_names = iso.country.subdivision_names("DJI, VAT") #Djibouti, Vatican City + dj_va_instance = ISO3166_2("DJI, VAT") #Djibouti, Vatican City + dj_va_subdivision_names = dj_va_instance.subdivision_names() self.assertEqual(dj_va_subdivision_names, expected_dj_va_subdivision_names, "Expected subdivison names don't match output:\n{}.".format(expected_dj_va_subdivision_names)) #8.) - all_subdivision_names = iso.country.subdivision_names() - self.assertEqual(len(all_subdivision_names), 250, "Expected 250 total subdivisions, got {}.".format(len(all_subdivision_names))) + all_subdivision_names = self.all_iso3166_2.subdivision_names() + self.assertEqual(len(all_subdivision_names), 250, "Expected 250 total country output objects, got {}.".format(len(all_subdivision_names))) for key, val in all_subdivision_names.items(): - self.assertIn(key, list(iso3166.countries_by_alpha2.keys()), "Subdivision code {} not found in list of ISO 3166 alpha-2 codes.".format(key)) + self.assertIn(key, list(iso3166.countries_by_alpha2.keys()), "Country code {} not found in list of ISO 3166 alpha-2 codes.".format(key)) self.assertIsInstance(val, list, "Expected output of subdivision names to be of type list, got {}.".format(type(val))) #9.) with (self.assertRaises(ValueError)): - iso.country.subdivision_names("ABCD") - iso.country.subdivision_names("Z") - iso.country.subdivision_names("1234") - iso.country.subdivision_names("blah, blah, blah") - iso.country.subdivision_names(False) + self.all_iso3166_2.subdivision_names("ABCD") + self.all_iso3166_2.subdivision_names("Z") + self.all_iso3166_2.subdivision_names("1234") + self.all_iso3166_2.subdivision_names("blah, blah, blah") + self.all_iso3166_2.subdivision_names(False) + km_instance.subdivision_names('ES') + er_instance.subdivision_names("DO") + zm_instance.subdivision_names("CPV") + dj_va_instance.subdivision_names("218") def test_subdivision_local_names(self): """ Testing functionality for getting list of all ISO 3166-2 subdivision local names. """ @@ -288,156 +356,84 @@ def test_subdivision_local_names(self): expected_pr_sc_local_names = {'PR': [], 'SC': ['Anse Boileau', 'Anse Etoile', 'Anse Royale', 'Anse aux Pins', 'Au Cap', 'Baie Lazare', 'Baie Sainte Anne', 'Beau Vallon', 'Bel Air', 'Bel Ombre',\ 'Cascade', 'English River', 'Glacis', 'Grand Anse Mahe', 'Grand Anse Praslin', 'Ile Perseverance I', 'Ile Perseverance II', 'La Digue', 'Les Mamelles',\ 'Mont Buxton', 'Mont Fleuri', 'Plaisance', 'Pointe Larue', 'Port Glaud', 'Roche Caiman', 'Saint Louis', 'Takamaka']} + expected_ws_local_names = ["A'ana", "Aiga-i-le-Tai", "Atua", "Fa'asaleleaga", "Gaga'emauga", "Gagaifomauga", "Palauli", "Satupa'itea", "Tuamasaga", "Va'a-o-Fonoti", "Vaisigano"] + expected_sg_sj_local_names = {"SG": ["Central Singapore", "North East", "North West", "South East", "South West"], "SJ": []} #1.) - bb_subdivision_local_names = iso.country.subdivision_local_names("BB") #Barbados + bb_instance = ISO3166_2("BB") #Barbados + bb_subdivision_local_names = bb_instance.subdivision_local_names() self.assertEqual(bb_subdivision_local_names, expected_bb_local_names, "Expected subdivison local names don't match output:\n{}.".format(expected_bb_local_names)) #2.) - lr_subdivision_local_names = iso.country.subdivision_local_names("LR") #Liberia + lr_instance = ISO3166_2("LR") #Liberia + lr_subdivision_local_names = lr_instance.subdivision_local_names() self.assertEqual(lr_subdivision_local_names, expected_lr_local_names, "Expected subdivison local names don't match output:\n{}.".format(expected_lr_local_names)) #3.) - na_subdivision_local_names = iso.country.subdivision_local_names("NA") #Namibia + na_instance = ISO3166_2("NAM") #Namibia + na_subdivision_local_names = na_instance.subdivision_local_names() self.assertEqual(na_subdivision_local_names, expected_na_local_names, "Expected subdivison local names don't match output:\n{}.".format(expected_na_local_names)) #4.) - nr_subdivision_local_names = iso.country.subdivision_local_names("NR") #Nauru + nr_instance = ISO3166_2("NRU") #Nauru + nr_subdivision_local_names = nr_instance.subdivision_local_names() self.assertEqual(nr_subdivision_local_names, expected_nr_local_names, "Expected subdivison local names don't match output:\n{}.".format(expected_nr_local_names)) #5.) - pr_sc_subdivision_local_names = iso.country.subdivision_local_names("PR,SC") #Puerto Rico, Seychelles - self.assertEqual(pr_sc_subdivision_local_names, expected_pr_sc_local_names, "Expected subdivison local names don't match output:\n{}.".format(expected_pr_sc_local_names)) + ws_instance = ISO3166_2("882") #Western Samoa + ws_subdivision_local_names = ws_instance.subdivision_local_names() + self.assertEqual(ws_subdivision_local_names, expected_ws_local_names, "Expected subdivison local names don't match output:\n{}.".format(expected_na_local_names)) #6.) - all_subdivision_local_names = iso.country.subdivision_names() - self.assertEqual(len(all_subdivision_local_names), 250, "Expected 250 total subdivisions, got {}.".format(len(all_subdivision_local_names))) - for key, val in all_subdivision_local_names.items(): - self.assertIn(key, list(iso3166.countries_by_alpha2.keys()), "Subdivision code {} not found in list of ISO 3166 alpha-2 codes.".format(key)) - self.assertIsInstance(val, list, "Expected output of subdivision local names to be of type list, got {}.".format(type(val))) + sg_sj_instance = ISO3166_2("702,SJ") #Singapore, Svalbard + sg_sj_subdivision_local_names = sg_sj_instance.subdivision_local_names() + self.assertEqual(sg_sj_subdivision_local_names, expected_sg_sj_local_names, "Expected subdivison local names don't match output:\n{}.".format(expected_na_local_names)) #7.) - with (self.assertRaises(ValueError)): - iso.country.subdivision_local_names("ABCD") - iso.country.subdivision_local_names("Z") - iso.country.subdivision_local_names("1234") - iso.country.subdivision_local_names("blah, blah, blah") - iso.country.subdivision_local_names(False) - - def test_subdivision_codes(self): - """ Testing functionality for getting list of all ISO 3166-2 subdivision codes. """ - expected_bq_subdivision_codes = ['BQ-BO', 'BQ-SA', 'BQ-SE'] - expected_sz_subdivision_codes = ['SZ-HH', 'SZ-LU', 'SZ-MA', 'SZ-SH'] - expected_sm_subdivision_codes = ['SM-01', 'SM-02', 'SM-03', 'SM-04', 'SM-05', 'SM-06', 'SM-07', 'SM-08', 'SM-09'] - expected_pw_subdivision_codes = ['PW-002', 'PW-004', 'PW-010', 'PW-050', 'PW-100', 'PW-150', 'PW-212', 'PW-214', \ - 'PW-218', 'PW-222', 'PW-224', 'PW-226', 'PW-227', 'PW-228', 'PW-350', 'PW-370'] - expected_wf_subdivision_codes = ['WF-AL', 'WF-SG', 'WF-UV'] - expected_mg_sb_subdivision_codes = {"MG": ["MG-A", "MG-D", "MG-F", "MG-M", "MG-T", "MG-U"], - "SB": ["SB-CE", "SB-CH", "SB-CT", "SB-GU", "SB-IS", "SB-MK", "SB-ML", "SB-RB", "SB-TE", "SB-WE"]} - expected_gnq_tca_subdivision_codes = {"GQ": ["GQ-AN", "GQ-BN", "GQ-BS", "GQ-C", "GQ-CS", "GQ-DJ", "GQ-I", "GQ-KN", "GQ-LI", "GQ-WN"], - "TC": []} -#1.) - bq_subdivision_codes = iso.country.subdivision_codes("BQ") #Bonaire, Sint Eustatius and Saba - self.assertEqual(bq_subdivision_codes, expected_bq_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_bq_subdivision_codes)) -#2.) - sz_subdivision_codes = iso.country.subdivision_codes("SZ") #Eswatini - self.assertEqual(sz_subdivision_codes, expected_sz_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_sz_subdivision_codes)) -#3.) - sm_subdivision_codes = iso.country.subdivision_codes("SM") #San Marino - self.assertEqual(sm_subdivision_codes, expected_sm_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_sm_subdivision_codes)) -#4.) - pw_subdivision_codes = iso.country.subdivision_codes("PW") #Palau - self.assertEqual(pw_subdivision_codes, expected_pw_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_pw_subdivision_codes)) -#5.) - wf_subdivision_codes = iso.country.subdivision_codes("WF") #Wallis and Futuna - self.assertEqual(wf_subdivision_codes, expected_wf_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_wf_subdivision_codes)) -#6.) - mg_sb_subdivision_codes = iso.country.subdivision_codes("MG, SB") #Madagascar, Solomon Islands - self.assertEqual(mg_sb_subdivision_codes, expected_mg_sb_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_mg_sb_subdivision_codes)) -#7.) - gnq_tca_subdivision_codes = iso.country.subdivision_codes("GNQ, TCA") #Equitorial Guinea, Turks and Caicos Islands - self.assertEqual(gnq_tca_subdivision_codes, expected_gnq_tca_subdivision_codes, "Expected subdivison codes don't match output:\n{}.".format(expected_gnq_tca_subdivision_codes)) + pr_sc_instance = ISO3166_2("PRI,690") + pr_sc_subdivision_local_names = pr_sc_instance.subdivision_local_names() #Puerto Rico, Seychelles + self.assertEqual(pr_sc_subdivision_local_names, expected_pr_sc_local_names, "Expected subdivison local names don't match output:\n{}.".format(expected_pr_sc_local_names)) #8.) - all_subdivision_codes = iso.country.subdivision_codes() - self.assertEqual(len(all_subdivision_codes), 250, "Expected 250 total subdivisions, got {}.".format(len(all_subdivision_codes))) - for key, val in all_subdivision_codes.items(): - self.assertIn(key, list(iso3166.countries_by_alpha2.keys()), "Subdivision code {} not found in list of ISO 3166 alpha-2 codes.".format(key)) - self.assertIsInstance(val, list, "Expected output of subdivision parent codes to be of type list, got {}.".format(type(val))) + all_subdivision_local_names = self.all_iso3166_2.subdivision_names() + self.assertEqual(len(all_subdivision_local_names), 250, "Expected 250 total country output objects, got {}.".format(len(all_subdivision_local_names))) + for key, val in all_subdivision_local_names.items(): + self.assertIn(key, list(iso3166.countries_by_alpha2.keys()), "Country code {} not found in list of ISO 3166 alpha-2 codes.".format(key)) + self.assertIsInstance(val, list, "Expected output of subdivision local names to be of type list, got {}.".format(type(val))) #9.) with (self.assertRaises(ValueError)): - iso.country.subdivision_codes("ABCD") - iso.country.subdivision_codes("Z") - iso.country.subdivision_codes("1234") - iso.country.subdivision_codes(False) - - def test_subdivision_parent_codes(self): - """ Testing functionality for getting list of all ISO 3166-2 subdivision parent codes. """ - expected_lt_parent_codes = ["LT-AL", "LT-KL", "LT-KU", "LT-MR", "LT-PN", "LT-SA", "LT-TA", "LT-TE", "LT-UT", "LT-VL"] - expected_mw_parent_codes = ["MW-C", "MW-N", "MW-S"] - expected_rs_parent_codes = ["RS-KM", "RS-VO"] - expected_ug_parent_codes = ["UG-C", "UG-E", "UG-N", "UG-W"] - expected_za_parent_codes = [] -#1.) - lt_parentCodes = iso.country.subdivision_parent_codes("LT") - for code in lt_parentCodes: - self.assertIn(code, list(iso.country["LT"].keys()), "Parent code not found in list of country's subdivision codes:\n{}.".format(list(iso.country["LT"].keys()))) - self.assertEqual(lt_parentCodes, expected_lt_parent_codes, "Expected subdivison parent codes don't match output:\n{}.".format(expected_lt_parent_codes)) -#2.) - mw_parentCodes = iso.country.subdivision_parent_codes("MW") - for code in mw_parentCodes: - self.assertIn(code, list(iso.country["MW"].keys()), "Parent code not found in list of country's subdivision codes:\n{}.".format(list(iso.country["MW"].keys()))) - self.assertEqual(mw_parentCodes, expected_mw_parent_codes, "Expected subdivison parent codes don't match output:\n{}.".format(expected_mw_parent_codes)) -#3.) - rs_parentCodes = iso.country.subdivision_parent_codes("RS") - for code in rs_parentCodes: - self.assertIn(code, list(iso.country["RS"].keys()), "Parent code not found in list of country's subdivision codes:\n{}.".format(list(iso.country["RS"].keys()))) - self.assertEqual(rs_parentCodes, expected_rs_parent_codes, "Expected subdivison parent codes don't match output:\n{}.".format(expected_rs_parent_codes)) -#4.) - ug_parentCodes = iso.country.subdivision_parent_codes("UG") - for code in ug_parentCodes: - self.assertIn(code, list(iso.country["UG"].keys()), "Parent code not found in list of country's subdivision codes:\n{}.".format(list(iso.country["UG"].keys()))) - self.assertEqual(ug_parentCodes, expected_ug_parent_codes, "Expected subdivison parent codes don't match output:\n{}.".format(expected_ug_parent_codes)) -#5.) - za_parentCodes = iso.country.subdivision_parent_codes("ZA") - for code in za_parentCodes: - self.assertIn(code, list(iso.country["ZA"].keys()), "Parent code not found in list of country's subdivision codes:\n{}.".format(list(iso.country["ZA"].keys()))) - self.assertEqual(za_parentCodes, expected_za_parent_codes, "Expected subdivison parent codes don't match output:\n{}.".format(expected_za_parent_codes)) -#6.) - all_subdivision_parent_codes = iso.country.subdivision_parent_codes() - self.assertEqual(len(all_subdivision_parent_codes), 250, "Expected 250 total subdivision parent codes, got {}.".format(len(all_subdivision_parent_codes))) - for key, val in all_subdivision_parent_codes.items(): - self.assertIn(key, list(iso3166.countries_by_alpha2.keys()), "Subdivision code {} not found in list of ISO 3166 alpha-2 codes.".format(key)) - self.assertIsInstance(val, list, "Expected output to be of type list, got {}.".format(type(val))) -#7.) - with (self.assertRaises(ValueError)): - iso.country.subdivision_parent_codes("ABCD") - iso.country.subdivision_parent_codes("Z") - iso.country.subdivision_parent_codes("1234") - iso.country.subdivision_parent_codes(False) + self.all_iso3166_2.subdivision_local_names("ABCD") + self.all_iso3166_2.subdivision_local_names("Z") + self.all_iso3166_2.subdivision_local_names("1234") + self.all_iso3166_2.subdivision_local_names("blah, blah, blah") + self.all_iso3166_2.subdivision_local_names(False) + bb_instance.subdivision_local_names("GY") + na_instance.subdivision_local_names("DE") + sg_sj_instance.subdivision_local_names("RO") + pr_sc_instance.subdivision_local_names("TR") @unittest.skip("Skipping to not change the main iso3166-2 object during other unit tests running.") def test_custom_subdivision(self): """ Testing custom_subdivision function that adds or deletes custom subdivisions to the main iso3166-2.json object. """ #add below test subdivisions to respective country objects - iso.country.custom_subdivision("AD", "AD-ZZ", name="Bogus Subdivision", local_name="Bogus Subdivision", type="District", lat_lng=[42.520, 1.657], parent_code=None, flag_url=None) - iso.country.custom_subdivision("DE", "DE-100", name="Made up subdivision", local_name="Made up subdivision", type="Land", lat_lng=[48.84, 11.479], parent_code=None, flag_url=None,) - iso.country.custom_subdivision("GY", "GY-ABC", name="New Guyana subdivision", local_name="New Guyana subdivision", type="Region", lat_lng=[6.413, -60.123], parent_code=None, flag_url=None) - iso.country.custom_subdivision("ZA", "ZA-123", name="Zambian province", local_name="Zambian province", type="Province", lat_lng=[-28.140, 26.777], parent_code=None, flag_url=None) + self.all_iso3166_2.custom_subdivision("AD", "AD-ZZ", name="Bogus Subdivision", local_name="Bogus Subdivision", type="District", lat_lng=[42.520, 1.657], parent_code=None, flag_url=None) + self.all_iso3166_2.custom_subdivision("DE", "DE-100", name="Made up subdivision", local_name="Made up subdivision", type="Land", lat_lng=[48.84, 11.479], parent_code=None, flag_url=None,) + self.all_iso3166_2.custom_subdivision("GY", "GY-ABC", name="New Guyana subdivision", local_name="New Guyana subdivision", type="Region", lat_lng=[6.413, -60.123], parent_code=None, flag_url=None) + self.all_iso3166_2.custom_subdivision("ZA", "ZA-123", name="Zambian province", local_name="Zambian province", type="Province", lat_lng=[-28.140, 26.777], parent_code=None, flag_url=None) #open test json with new subdivisions added with open(os.path.join("iso3166_2", "iso3166-2-data", "iso3166-2.json"), 'r', encoding='utf-8') as input_json: test_all_subdivision_data = json.load(input_json) #1.) self.assertEqual(test_all_subdivision_data["AD"]["AD-ZZ"], {'flagUrl': None, 'latLng': [42.52, 1.657], 'name': 'Bogus Subdivision', 'localName': 'Bogus Subdivision', 'parentCode': None, 'type': 'District'}, - "Expected dict for AD-ZZ added subdivision does not match output:\n{}.".format(test_all_subdivision_data["AD"]["AD-ZZ"])) + "Expected dict for custom AD-ZZ subdivision does not match output:\n{}.".format(test_all_subdivision_data["AD"]["AD-ZZ"])) #2.) self.assertEqual(test_all_subdivision_data["DE"]["DE-100"], {'flagUrl': None, 'latLng': [48.84, 11.479], 'name': 'Made up subdivision', 'localName': 'Made up subdivision', 'parentCode': None, 'type': 'Land'}, - "Expected dict for DE-100 added subdivision does not match output:\n{}.".format(test_all_subdivision_data["DE"]["DE-100"])) + "Expected dict for custom DE-100 subdivision does not match output:\n{}.".format(test_all_subdivision_data["DE"]["DE-100"])) #3.) self.assertEqual(test_all_subdivision_data["GY"]["GY-ABC"], {'flagUrl': None, 'latLng': [6.413, -60.123], 'name': 'New Guyana subdivision', 'localName': 'New Guyana subdivision', 'parentCode': None, 'type': 'Region'}, - "Expected dict for GY-ABC added subdivision does not match output:\n{}.".format(test_all_subdivision_data["GY"]["GY-ABC"])) + "Expected dict for custom GY-ABC subdivision does not match output:\n{}.".format(test_all_subdivision_data["GY"]["GY-ABC"])) #4.) self.assertEqual(test_all_subdivision_data["ZA"]["ZA-123"], {'flagUrl': None, 'latLng': [-28.14, 26.777], 'name': 'Zambian province', 'localName': 'Zambian province', 'parentCode': None, 'type': 'Province'}, - "Expected dict for ZA-123 added subdivision does not match output:\n{}.".format(test_all_subdivision_data["ZA"]["ZA-123"])) + "Expected dict for custom ZA-123 subdivision does not match output:\n{}.".format(test_all_subdivision_data["ZA"]["ZA-123"])) #delete above custom subdivisions - iso.country.custom_subdivision("AD", subdivision_code="AD-ZZ", delete=1) - iso.country.custom_subdivision("DE", subdivision_code="DE-100", delete=1) - iso.country.custom_subdivision("GY", subdivision_code="GY-ABC", delete=1) - iso.country.custom_subdivision("ZA", subdivision_code="ZA-123", delete=1) + self.all_iso3166_2.custom_subdivision("AD", subdivision_code="AD-ZZ", delete=1) + self.all_iso3166_2.custom_subdivision("DE", subdivision_code="DE-100", delete=1) + self.all_iso3166_2.custom_subdivision("GY", subdivision_code="GY-ABC", delete=1) + self.all_iso3166_2.custom_subdivision("ZA", subdivision_code="ZA-123", delete=1) #open test json with new subdivisions added with open(os.path.join("iso3166_2", "iso3166-2-data", "iso3166-2.json"), 'r', encoding='utf-8') as input_json: @@ -452,98 +448,104 @@ def test_custom_subdivision(self): self.assertNotIn("ZA-123", list(test_all_subdivision_data["ZA"].keys()), "Custom ZA-123 subdivision should not be in object for ZA.") #9.) with self.assertRaises(ValueError): - iso.country.custom_subdivision("IE", "IE-CN") - iso.country.custom_subdivision("JM", "JM-01") - iso.country.custom_subdivision("TV", "TV-NIT") - iso.country.custom_subdivision("UZ", "UZ-AN") - iso.country.custom_subdivision("ABC", "blah") - iso.country.custom_subdivision("ZZ", "blahblahblah") - iso.country.custom_subdivision("123", "idfuiwf") + self.all_iso3166_2.custom_subdivision("IE", "IE-CN") + self.all_iso3166_2.custom_subdivision("JM", "JM-01") + self.all_iso3166_2.custom_subdivision("TV", "TV-NIT") + self.all_iso3166_2.custom_subdivision("UZ", "UZ-AN") + self.all_iso3166_2.custom_subdivision("ABC", "blah") + self.all_iso3166_2.custom_subdivision("ZZ", "blahblahblah") + self.all_iso3166_2.custom_subdivision("123", "idfuiwf") #10.) with self.assertRaises(TypeError): - iso.country.custom_subdivision(123, 10.5) - iso.country.custom_subdivision(name=False) - iso.country.custom_subdivision("AD", "AD-01", type=123) + self.all_iso3166_2.custom_subdivision(123, 10.5) + self.all_iso3166_2.custom_subdivision(name=False) + self.all_iso3166_2.custom_subdivision("AD", "AD-01", type=123) def test_search(self): """ Testing searching by subdivision name functionality. """ test_search_1 = "Monaghan" #IE test_search_2 = "Olaines Novads" #LV - test_search_3 = "Ohangwena" #NA + test_search_3 = "Armagh City, Banbridge and Craigavon, Berlin" #GB, DE test_search_4 = "North" test_search_5 = "Eastern" test_search_6 = "" test_search_7 = "zzzzzzzz" - test_search_8 = True + test_search_8 = "West Carolina" + test_search_9 = True + test_search_10 = 4.6 #1.) - search_results_1 = iso.country.search(test_search_1, any=False) - expected_search_result_1 = {'alpha_2': "IE", "subdivision_code": "IE-MN", 'name': 'Monaghan', 'localName': 'Monaghan', 'type': 'County', + search_results_1 = self.all_iso3166_2.search(test_search_1, likeness=1) #IE + expected_search_result_1 = {"IE-MN": {'name': 'Monaghan', 'localName': 'Monaghan', 'type': 'County', 'parentCode': 'IE-U', 'flagUrl': 'https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/IE/IE-MN.png', - 'latLng': [54.249, -6.968]} - self.assertIsInstance(search_results_1, dict, - "Expected output object to be a dict, got {}.".format(type(search_results_1))) + 'latLng': [54.249, -6.968]}} self.assertEqual(search_results_1, expected_search_result_1, "Observed and expected output objects do not match:\n{}.".format(search_results_1)) #2.) - search_results_2 = iso.country.search(test_search_2, any=False) - expected_search_result_2 = {'alpha_2': "LV", "subdivision_code": "LV-068", 'name': 'Olaines novads', 'localName': 'Olaines novads', 'type': 'Municipality', + search_results_2 = self.all_iso3166_2.search(test_search_2, likeness=100) + expected_search_result_2 = {"LV-068": {'name': 'Olaines novads', 'localName': 'Olaines novads', 'type': 'Municipality', 'parentCode': None, 'flagUrl': 'https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LV/LV-068.png', - 'latLng': [56.795, 24.015]} - self.assertIsInstance(search_results_2, dict, - "Expected output object to be a dict, got {}.".format(type(search_results_2))) + 'latLng': [56.795, 24.015]}} self.assertEqual(search_results_2, expected_search_result_2, "Observed and expected output objects do not match:\n{}.".format(search_results_2)) #3.) - search_results_3 = iso.country.search(test_search_3, any=False) - expected_search_result_3 = {'alpha_2': "NA", "subdivision_code": "NA-OW", 'name': 'Ohangwena', 'localName': 'Ohangwena', 'type': 'Region', - 'parentCode': None, 'flagUrl': None, 'latLng': [-17.598, 16.818]} - self.assertIsInstance(search_results_3, dict, - "Expected output object to be a dict, got {}.".format(type(search_results_3))) + search_results_3 = self.all_iso3166_2.search(test_search_3, likeness=0.9) + expected_search_result_3 = {"DE-BE": {'name': "Berlin", "localName": "Berlin", 'type': 'Land', 'parentCode': None, + "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DE/DE-BE.svg", 'latLng': [52.52, 13.405]}, + "GB-ABC": {'name': 'Armagh City, Banbridge and Craigavon', 'localName': 'Armagh City, Banbridge and Craigavon', 'type': 'District', + 'parentCode': "GB-NIR", 'flagUrl': None, 'latLng': [54.393, -6.456]}} self.assertEqual(search_results_3, expected_search_result_3, "Observed and expected output objects do not match:\n{}.".format(search_results_3)) #4.) - search_results_4 = iso.country.search(test_search_4, any=True) #North - expected_search_result_4 = [{'name': 'North', 'localName': 'North', 'type': 'Region', 'parentCode': None, 'flagUrl': None, 'latLng': [8.581, 13.914], - 'alpha_2': 'CM', 'subdivision_code': 'CM-NO'}, {'name': 'Norte', 'localName': 'Norte', 'type': 'Province', - 'parentCode': None, 'flagUrl': None, 'latLng': [11.804, -15.18], 'alpha_2': 'GW', 'subdivision_code': 'GW-N'}] - - self.assertIsInstance(search_results_4, list, - "Expected output object to be a list, got {}.".format(type(search_results_4))) + search_results_4 = self.all_iso3166_2.search(test_search_4, likeness=70) #North + expected_search_result_4 = {"CM-NO": {'name': 'North', 'localName': 'North', 'type': 'Region', 'parentCode': None, 'flagUrl': None, 'latLng': [8.581, 13.914]}, + "GW-N": {'name': 'Norte', 'localName': 'Norte', 'type': 'Province', 'parentCode': None, 'flagUrl': None, 'latLng': [11.804, -15.18]}, + 'CM-EN': {'name': 'Far North', 'localName': 'Far North', 'type': 'Region', 'parentCode': None, 'flagUrl': None, 'latLng': [10.632, 14.659]}, + 'FJ-N': {'name': 'Northern', 'localName': 'Northern', 'type': 'Division', 'parentCode': None, 'flagUrl': None, 'latLng': [-16.627, 179.018]}, + 'GH-NP': {'name': 'Northern', 'localName': 'Northern', 'type': 'Region', 'parentCode': None, 'flagUrl': None, 'latLng': [9.367, -0.149]}, + 'PG-NPP': {'name': 'Northern', 'localName': 'Northern', 'type': 'Province', 'parentCode': None, 'flagUrl': 'https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/PG/PG-NPP.png', 'latLng': [-8.899, 148.189]}, + 'RW-03': {'name': 'Northern', 'localName': 'Northern', 'type': 'Province', 'parentCode': None, 'flagUrl': None, 'latLng': [-1.656, 29.882]}, + 'SD-NO': {'name': 'Northern', 'localName': 'Northern', 'type': 'State', 'parentCode': None, 'flagUrl': 'https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/SD/SD-NO.png', 'latLng': [18.445, 30.159]}, + 'SL-N': {'name': 'Northern', 'localName': 'Northern', 'type': 'Province', 'parentCode': None, 'flagUrl': None, 'latLng': [9.182, -11.525]}, + 'UG-N': {'name': 'Northern', 'localName': 'Northern', 'type': 'Geographical region', 'parentCode': None, 'flagUrl': None, 'latLng': [2.878, 32.718]}, + 'ZM-05': {'name': 'Northern', 'localName': 'Northern', 'type': 'Province', 'parentCode': None, 'flagUrl': 'https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ZM/ZM-05.png', 'latLng': [-9.767, 30.896]}} self.assertEqual(search_results_4, expected_search_result_4, "Observed and expected output objects do not match:\n{}.".format(search_results_4)) #5.) - search_results_5 = iso.country.search(test_search_5, any=True) #Eastern - expected_search_result_5 = [{'name': 'Eastern', 'localName': 'Eastern', 'type': 'Province', 'parentCode': None, - 'flagUrl': 'https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ZM/ZM-03.png', 'latLng': [-13.806, 31.993], - 'alpha_2': 'ZM', 'subdivision_code': 'ZM-03'}, {'name': 'Eastern', 'localName': 'Eastern', 'type': 'Province', 'parentCode': None, - 'flagUrl': 'https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ZM/ZM-03.png', 'latLng': [-13.806, 31.993], - 'alpha_2': 'ZM', 'subdivision_code': 'ZM-03'}, {'name': 'Eastern', 'localName': 'Eastern', 'type': 'Province', 'parentCode': None, - 'flagUrl': 'https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ZM/ZM-03.png', 'latLng': [-13.806, 31.993], - 'alpha_2': 'ZM', 'subdivision_code': 'ZM-03'}] - - self.assertIsInstance(search_results_5, list, - "Expected output object to be a list, got {}.".format(type(search_results_5))) + search_results_5 = self.all_iso3166_2.search(test_search_5, likeness=0.7) #Eastern + expected_search_result_5 = {"FJ-E": {'name': 'Eastern', 'localName': 'Eastern', 'type': 'Division', 'parentCode': None, "flagUrl": None, "latLng": [-17.689, 178.807]}, + "GH-EP": {'name': 'Eastern', 'localName': 'Eastern', 'type': 'Region', 'parentCode': None, "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/GH/GH-EP.svg", "latLng": [6.578, -0.45]}, + "RW-02": {'name': 'Eastern', 'localName': 'Eastern', 'type': 'Province', 'parentCode': None, "flagUrl": None, "latLng": [-1.782, 30.436]}, + "SL-E": {'name': 'Eastern', 'localName': 'Eastern', 'type': 'Province', 'parentCode': None, "flagUrl": None, "latLng": [7.811, -11.162]}, + "UG-E": {'name': 'Eastern', 'localName': 'Eastern', 'type': 'Geographical region', 'parentCode': None, "flagUrl": None, "latLng": [1.269, 33.438]}, + "ZM-03": {'name': 'Eastern', 'localName': 'Eastern', 'type': 'Province', 'parentCode': None, "flagUrl": "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ZM/ZM-03.png", "latLng": [-13.806, 31.993]}} self.assertEqual(search_results_5, expected_search_result_5, "Observed and expected output objects do not match:\n{}.".format(search_results_5)) #6.) - search_results_6 = iso.country.search(test_search_6) + search_results_6 = self.all_iso3166_2.search(test_search_6) self.assertEqual(search_results_6, {}, "Expected output to be an empty dict, got {}.".format(search_results_6)) #7.) - search_results_7 = iso.country.search(test_search_7) + search_results_7 = self.all_iso3166_2.search(test_search_7) self.assertEqual(search_results_7, {}, "Expected output to be an empty dict, got {}.".format(search_results_7)) #8.) + search_results_8 = self.all_iso3166_2.search(test_search_8) + self.assertEqual(search_results_8, {}, "Expected output to be an empty dict, got {}.".format(search_results_8)) +#9.) with (self.assertRaises(TypeError)): - search_results_7 = iso.country.search(test_search_8) + self.all_iso3166_2.search(test_search_9) + self.all_iso3166_2.search(test_search_10) def tearDown(self): - """ Delete any test json folders. """ + """ Delete any test json folders and objects . """ shutil.rmtree(self.test_output_dir) #remove the temp dir created to store duplicate of iso3166-2 object before any changes were made using the add_subdivision() function, #a successful pass of all the above test cases mean there are no errors on the current object and the archive folder can be deleted if (os.path.isdir("archive-iso3166-2")): shutil.rmtree(self.test_output_dir) + + #delete object holding all ISO 3166-2 data + del self.all_iso3166_2 if __name__ == '__main__': #run all unit tests diff --git a/tests/test_iso3166_2_api.py b/tests/test_iso3166_2_api.py index 1709ede..f56d4f4 100644 --- a/tests/test_iso3166_2_api.py +++ b/tests/test_iso3166_2_api.py @@ -1,4 +1,5 @@ import iso3166 +from iso3166_2 import * import requests import getpass import os @@ -7,7 +8,6 @@ import unittest unittest.TestLoader.sortTestMethodsUsing = None -# @unittest.skip("") class ISO3166_2_API_Tests(unittest.TestCase): """ Test suite for testing ISO 3166-2 api created to accompany the iso3166-2 Python software package. @@ -16,12 +16,20 @@ class ISO3166_2_API_Tests(unittest.TestCase): ========== test_homepage_endpoint: testing main endpoint that returns the homepage and API documentation. - test_alpha2_endpoint: - testing correct objects are returned from /alpha2 API endpoint using a variety of inputs. - test_name_endpoint: - testing correct objects are returned from /name API endpoint using a variety of inputs. + test_alpha_endpoint: + testing correct data and attributes are returned from the /alpha API endpoint using a variety of alpha-2, + alpha-3 or numeric code inputs. + test_subdivision_endpoint: + testing correct data and attributes are returned from the /subdivision API endpoint using a variety of + subdivision code inputs. + test_subdivision_name_endpoint: + testing correct data and attributes are returned from the /name API endpoint using a variety of subdivision + name inputs. + test_country_name_endpoint: + testing correct data and attributes are returned from the /country_name API endpoint using a variety of + country name inputs. test_all_endpoint: - testing correct objects are returned from /name API endpoint, which returns all the available + testing correct data and attributes are returned from the /all API endpoint, which returns all the available ISO 3166-2 data. """ def setUp(self): @@ -32,10 +40,12 @@ def setUp(self): 'https://github.com/amckenna41/iso3166-2', getpass.getuser())} #url endpoints for API - self.api_base_url = "https://iso3166-2-api.vercel.app/api/" - # self.api_base_url = "https://iso3166-2-api-amckenna41.vercel.app/api/" - self.alpha2_base_url = self.api_base_url + "alpha2/" - self.name_base_url = self.api_base_url + "name/" + # self.api_base_url = "https://iso3166-2-api.vercel.app/api/" + self.api_base_url = "https://iso3166-2-api-amckenna41.vercel.app/api/" + self.alpha_base_url = self.api_base_url + "alpha/" + self.subdivision_base_url = self.api_base_url + "subdivision/" + self.subdivision_name_base_url = self.api_base_url + "name/" + self.country_name_base_url = self.api_base_url + "country_name/" self.all_base_url = self.api_base_url + "all" #list of keys that should be in subdivisions key of output object @@ -43,9 +53,12 @@ def setUp(self): #base url for subdivision flag icons self.flag_base_url = "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/" + + #create instance of ISO 3166-2 class + self.iso3166_2_data = ISO3166_2() def test_homepage_endpoint(self): - """ Testing contents of main "/api" endpoint that returns the homepage and API documentation. """ + """ Testing contents of main /api endpoint that returns the homepage and API documentation. """ test_request_main = requests.get(self.api_base_url, headers=self.user_agent_header) soup = BeautifulSoup(test_request_main.content, 'html.parser') #1.) @@ -53,28 +66,29 @@ def test_homepage_endpoint(self): last_updated = soup.find(id='last-updated').text.split(': ')[1] author = soup.find(id='author').text.split(': ')[1] - self.assertEqual(version, "1.3.0", "Expected API version to be 1.3.0, got {}.".format(version)) - self.assertEqual(last_updated, "December 2023", "Expected last updated data to be December 2023, got {}.".format(last_updated)) + self.assertEqual(version, "1.5.0", "Expected API version to be 1.5.0, got {}.".format(version)) + self.assertEqual(last_updated, "March 2024", "Expected last updated data to be March 2024, got {}.".format(last_updated)) self.assertEqual(author, "AJ", "Expected author to be AJ, got {}.".format(author)) #2.) section_list_menu = soup.find(id='section-list-menu').find_all('li') - correct_section_menu = ["About", "Attributes", "Endpoints", "All", "Alpha-2 Code", "Name", "Credits", "Contributing"] + correct_section_menu = ["About", "Attributes", "Endpoints", "All", "Country alpha code", "Country name", "Subdivision code", "Subdivision name", "Credits", "Contributing"] for li in section_list_menu: self.assertIn(li.text.strip(), correct_section_menu, "Expected list element {} to be in list.".format(li)) - def test_alpha2_endpoint(self): - """ Testing alpha-2 endpoint, return all ISO 3166 subdivision data from input alpha-2 code/codes. """ + def test_alpha_endpoint(self): + """ Testing /alpha endpoint, return all ISO 3166 subdivision data from input alpha-2, alpha-3 or numeric code/codes. """ test_alpha2_au = "AU" #Australia - test_alpha2_cy = "CY" #Cyprus - test_alpha2_lu = "LU" #Luxembourg - test_alpha2_pa_rw = "PA, RW" #Panama, Rwanda - test_alpha2_error_1 = "ABCDE" - test_alpha2_error_2 = "12345" + test_alpha3_lux = "LUX" #Luxembourg + test_alpha3_pa_rw = "PAN, RWA" #Panama, Rwanda + test_numeric_740_752 = "740, 752" #Suriname, Sweden + test_alpha2_alpha3_numeric_ir_kgz_446 = "IR, KGZ, 446" #Iran, Kyrgyzstan, Macao + test_alpha_error_1 = "ABCDE" + test_alpha_error_2 = "12345" + test_alpha_error_3 = "" #1.) - test_request_au = requests.get(self.alpha2_base_url + test_alpha2_au, headers=self.user_agent_header).json() #Australia + test_request_au = requests.get(self.alpha_base_url + test_alpha2_au, headers=self.user_agent_header).json() #Australia self.assertIsInstance(test_request_au, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_au))) - self.assertEqual(len(test_request_au), 1, "Expected output object of API to be of length 1, got {}.".format(len(test_request_au))) self.assertEqual(list(test_request_au.keys()), ["AU"], "Expected output object of API to contain only the AU key, got {}.".format(list(test_request_au.keys()))) self.assertEqual(list(test_request_au["AU"].keys()), ["AU-ACT", "AU-NSW", "AU-NT", "AU-QLD", "AU-SA", "AU-TAS", "AU-VIC", "AU-WA"], "Expected list of subdivision codes doesn't match output.") for subd in test_request_au["AU"]: @@ -120,111 +134,63 @@ def test_alpha2_endpoint(self): self.assertEqual(test_request_au["AU"]["AU-QLD"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/AU/AU-QLD.svg", "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/AU/AU-QLD.svg, got {}.".format(test_request_au["AU"]["AU-QLD"]["flagUrl"])) #2.) - test_request_cy = requests.get(self.alpha2_base_url + test_alpha2_cy, headers=self.user_agent_header).json() #Cyprus - - self.assertIsInstance(test_request_cy, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_cy))) - self.assertEqual(len(test_request_cy), 1, "Expected output object of API to be of length 1, got {}.".format(len(test_request_cy))) - self.assertEqual(list(test_request_cy.keys()), ["CY"], "Expected output object of API to contain only the CY key, got {}.".format(list(test_request_cy.keys()))) - self.assertEqual(list(test_request_cy["CY"].keys()), ["CY-01", "CY-02", "CY-03", "CY-04", "CY-05", "CY-06"], "Expected list of subdivision codes doesn't match output.") - for subd in test_request_cy["CY"]: - self.assertIsNot(test_request_cy["CY"][subd]["name"], None, - "Expected subdivision name to not be None, got {}.".format(test_request_cy["CY"][subd]["name"])) - if not (test_request_cy["CY"][subd]["parentCode"] is None): - self.assertIn(test_request_cy["CY"][subd]["parentCode"], list(test_request_cy["CY"][subd].keys()), - "Parent code {} not found in list of subdivision codes.".format(test_request_cy["CY"][subd]["parentCode"])) - if not (test_request_cy["CY"][subd]["flagUrl"] is None): - self.assertEqual(os.path.splitext(test_request_cy["CY"][subd]["flagUrl"])[0], self.flag_base_url + "CY/" + subd, - "Expected flag url to be {}, got {}.".format(self.flag_base_url + "CY/" + subd, os.path.splitext(test_request_cy["CY"][subd]["flagUrl"])[0])) - self.assertEqual(requests.get(test_request_cy["CY"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, - "Flag URL invalid: {}.".format(test_request_cy["CY"][subd]["flagUrl"])) - for key in list(test_request_cy["CY"][subd].keys()): - self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) - - #CY-01 - Lefkosia - self.assertEqual(test_request_cy["CY"]["CY-01"]["name"], "Lefkosia", - "Expected subdivsion name to be Lefkosia, got {}.".format(test_request_cy["CY"]["CY-01"]["name"])) - self.assertEqual(test_request_cy["CY"]["CY-01"]["localName"], "Lefkosia", - "Expected subdivsion local name to be Lefkosia, got {}.".format(test_request_cy["CY"]["CY-01"]["localName"])) - self.assertEqual(test_request_cy["CY"]["CY-01"]["parentCode"], None, - "Expected subdivision parent code to be None, got {}.".format(test_request_cy["CY"]["CY-01"]["parentCode"])) - self.assertEqual(test_request_cy["CY"]["CY-01"]["type"], "District", - "Expected subdivision type to be District, got {}.".format(test_request_cy["CY"]["CY-01"]["type"])) - self.assertEqual(test_request_cy["CY"]["CY-01"]["latLng"], [35.186, 33.382], - "Expected subdivision latLng to be [35.186, 33.382], got {}.".format(test_request_cy["CY"]["CY-01"]["latLng"])) - self.assertEqual(test_request_cy["CY"]["CY-01"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/CY/CY-01.svg", - "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/CY/CY-01.svg, got {}.".format(test_request_cy["CY"]["CY-01"]["flagUrl"])) - #CY-02 - Lemesos - self.assertEqual(test_request_cy["CY"]["CY-02"]["name"], "Lemesos", - "Expected subdivsion name to be Lemesos, got {}.".format(test_request_cy["CY"]["CY-02"]["name"])) - self.assertEqual(test_request_cy["CY"]["CY-02"]["localName"], "Lemesos", - "Expected subdivsion local name to be Lemesos, got {}.".format(test_request_cy["CY"]["CY-02"]["localName"])) - self.assertEqual(test_request_cy["CY"]["CY-02"]["parentCode"], None, - "Expected subdivision parent code to be None, got {}.".format(test_request_cy["CY"]["CY-02"]["parentCode"])) - self.assertEqual(test_request_cy["CY"]["CY-02"]["type"], "District", - "Expected subdivision type to be District, got {}.".format(test_request_cy["CY"]["CY-02"]["type"])) - self.assertEqual(test_request_cy["CY"]["CY-02"]["latLng"], [34.679, 33.041], - "Expected subdivision latLng to be [34.679, 33.041], got {}.".format(test_request_cy["CY"]["CY-02"]["latLng"])) - self.assertEqual(test_request_cy["CY"]["CY-02"]["flagUrl"], None, - "Expected subdivision flag url to be None, got {}.".format(test_request_cy["CY"]["CY-02"]["flagUrl"])) -#3.) - test_request_lu = requests.get(self.alpha2_base_url + test_alpha2_lu, headers=self.user_agent_header).json() #Luxembourg + test_request_lux = requests.get(self.alpha_base_url + test_alpha3_lux, headers=self.user_agent_header).json() #Luxembourg - self.assertIsInstance(test_request_lu, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_lu))) - self.assertEqual(len(test_request_lu), 1, "Expected output object of API to be of length 1, got {}.".format(len(test_request_lu))) - self.assertEqual(list(test_request_lu.keys()), ["LU"], "Expected output object of API to contain only the LU key, got {}.".format(list(test_request_lu.keys()))) - self.assertEqual(list(test_request_lu["LU"].keys()), ["LU-CA", "LU-CL", "LU-DI", "LU-EC", "LU-ES", "LU-GR", "LU-LU", "LU-ME", "LU-RD", "LU-RM", "LU-VD", "LU-WI"], + self.assertIsInstance(test_request_lux, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_lux))) + self.assertEqual(list(test_request_lux.keys()), ["LU"], "Expected output object of API to contain only the LU key, got {}.".format(list(test_request_lux.keys()))) + self.assertEqual(list(test_request_lux["LU"].keys()), ["LU-CA", "LU-CL", "LU-DI", "LU-EC", "LU-ES", "LU-GR", "LU-LU", "LU-ME", "LU-RD", "LU-RM", "LU-VD", "LU-WI"], "Expected list of subdivision codes doesn't match output.") - for subd in test_request_lu["LU"]: - self.assertIsNot(test_request_lu["LU"][subd]["name"], None, - "Expected subdivision name to not be None, got {}.".format(test_request_lu["LU"][subd]["name"])) - self.assertEqual(test_request_lu["LU"][subd]["name"], test_request_lu["LU"][subd]["localName"], + for subd in test_request_lux["LU"]: + self.assertIsNot(test_request_lux["LU"][subd]["name"], None, + "Expected subdivision name to not be None, got {}.".format(test_request_lux["LU"][subd]["name"])) + self.assertEqual(test_request_lux["LU"][subd]["name"], test_request_lux["LU"][subd]["localName"], "Expected subdivision's name and local name to be the same.") - if not (test_request_lu["LU"][subd]["parentCode"] is None): - self.assertIn(test_request_lu["LU"][subd]["parentCode"], list(test_request_lu["LU"][subd].keys()), - "Parent code {} not found in list of subdivision codes.".format(test_request_lu["LU"][subd]["parentCode"])) - if not (test_request_lu["LU"][subd]["flagUrl"] is None): - self.assertEqual(os.path.splitext(test_request_lu["LU"][subd]["flagUrl"])[0], self.flag_base_url + "LU/" + subd, - "Expected flag url to be {}, got {}.".format(self.flag_base_url + "LU/" + subd, os.path.splitext(test_request_lu["LU"][subd]["flagUrl"])[0])) - self.assertEqual(requests.get(test_request_lu["LU"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, - "Flag URL invalid: {}.".format(test_request_lu["LU"][subd]["flagUrl"])) - for key in list(test_request_lu["LU"][subd].keys()): + if not (test_request_lux["LU"][subd]["parentCode"] is None): + self.assertIn(test_request_lux["LU"][subd]["parentCode"], list(test_request_lux["LU"][subd].keys()), + "Parent code {} not found in list of subdivision codes.".format(test_request_lux["LU"][subd]["parentCode"])) + if not (test_request_lux["LU"][subd]["flagUrl"] is None): + self.assertEqual(os.path.splitext(test_request_lux["LU"][subd]["flagUrl"])[0], self.flag_base_url + "LU/" + subd, + "Expected flag url to be {}, got {}.".format(self.flag_base_url + "LUX/" + subd, os.path.splitext(test_request_lux["LU"][subd]["flagUrl"])[0])) + self.assertEqual(requests.get(test_request_lux["LU"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, + "Flag URL invalid: {}.".format(test_request_lux["LU"][subd]["flagUrl"])) + for key in list(test_request_lux["LU"][subd].keys()): self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) #LU-CA - Capellen - self.assertEqual(test_request_lu["LU"]["LU-CA"]["name"], "Capellen", - "Expected subdivsion name to be Capellen, got {}.".format(test_request_lu["LU"]["LU-CA"]["name"])) - self.assertEqual(test_request_lu["LU"]["LU-CA"]["localName"], "Capellen", - "Expected subdivsion local name to be Capellen, got {}.".format(test_request_lu["LU"]["LU-CA"]["localName"])) - self.assertEqual(test_request_lu["LU"]["LU-CA"]["parentCode"], None, - "Expected subdivision parent code to be None, got {}.".format(test_request_lu["LU"]["LU-CA"]["parentCode"])) - self.assertEqual(test_request_lu["LU"]["LU-CA"]["type"], "Canton", - "Expected subdivision type to be Canton, got {}.".format(test_request_lu["LU"]["LU-CA"]["type"])) - self.assertEqual(test_request_lu["LU"]["LU-CA"]["latLng"], [49.646, 5.991], - "Expected subdivision latLng to be [49.646, 5.991], got {}.".format(test_request_lu["LU"]["LU-CA"]["latLng"])) - self.assertEqual(test_request_lu["LU"]["LU-CA"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LU/LU-CA.svg", - "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LU/LU-CA.svg, got {}.".format(test_request_lu["LU"]["LU-CA"]["flagUrl"])) + self.assertEqual(test_request_lux["LU"]["LU-CA"]["name"], "Capellen", + "Expected subdivsion name to be Capellen, got {}.".format(test_request_lux["LU"]["LU-CA"]["name"])) + self.assertEqual(test_request_lux["LU"]["LU-CA"]["localName"], "Capellen", + "Expected subdivsion local name to be Capellen, got {}.".format(test_request_lux["LU"]["LU-CA"]["localName"])) + self.assertEqual(test_request_lux["LU"]["LU-CA"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_lux["LU"]["LU-CA"]["parentCode"])) + self.assertEqual(test_request_lux["LU"]["LU-CA"]["type"], "Canton", + "Expected subdivision type to be Canton, got {}.".format(test_request_lux["LU"]["LU-CA"]["type"])) + self.assertEqual(test_request_lux["LU"]["LU-CA"]["latLng"], [49.646, 5.991], + "Expected subdivision latLng to be [49.646, 5.991], got {}.".format(test_request_lux["LU"]["LU-CA"]["latLng"])) + self.assertEqual(test_request_lux["LU"]["LU-CA"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LU/LU-CA.svg", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LU/LU-CA.svg, got {}.".format(test_request_lux["LU"]["LU-CA"]["flagUrl"])) #LU-LU - Luxembourg - self.assertEqual(test_request_lu["LU"]["LU-LU"]["name"], "Luxembourg", - "Expected subdivsion name to be Luxembourg, got {}.".format(test_request_lu["LU"]["LU-LU"]["name"])) - self.assertEqual(test_request_lu["LU"]["LU-LU"]["localName"], "Luxembourg", - "Expected subdivsion local name to be Luxembourg, got {}.".format(test_request_lu["LU"]["LU-LU"]["localName"])) - self.assertEqual(test_request_lu["LU"]["LU-LU"]["parentCode"], None, - "Expected subdivision parent code to be None, got {}.".format(test_request_lu["LU"]["LU-LU"]["parentCode"])) - self.assertEqual(test_request_lu["LU"]["LU-LU"]["type"], "Canton", - "Expected subdivision type to be Canton, got {}.".format(test_request_lu["LU"]["LU-LU"]["type"])) - self.assertEqual(test_request_lu["LU"]["LU-LU"]["latLng"], [49.815, 6.13], - "Expected subdivision latLng to be [49.815, 6.13], got {}.".format(test_request_lu["LU"]["LU-LU"]["latLng"])) - self.assertEqual(test_request_lu["LU"]["LU-LU"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LU/LU-LU.png", - "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LU/LU-LU.png, got {}.".format(test_request_lu["LU"]["LU-LU"]["flagUrl"])) -#4.) - test_request_pa_rw = requests.get(self.alpha2_base_url + test_alpha2_pa_rw, headers=self.user_agent_header).json() #Panama and Rwanda + self.assertEqual(test_request_lux["LU"]["LU-LU"]["name"], "Luxembourg", + "Expected subdivsion name to be Luxembourg, got {}.".format(test_request_lux["LU"]["LU-LU"]["name"])) + self.assertEqual(test_request_lux["LU"]["LU-LU"]["localName"], "Luxembourg", + "Expected subdivsion local name to be Luxembourg, got {}.".format(test_request_lux["LU"]["LU-LU"]["localName"])) + self.assertEqual(test_request_lux["LU"]["LU-LU"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_lux["LU"]["LU-LU"]["parentCode"])) + self.assertEqual(test_request_lux["LU"]["LU-LU"]["type"], "Canton", + "Expected subdivision type to be Canton, got {}.".format(test_request_lux["LU"]["LU-LU"]["type"])) + self.assertEqual(test_request_lux["LU"]["LU-LU"]["latLng"], [49.815, 6.13], + "Expected subdivision latLng to be [49.815, 6.13], got {}.".format(test_request_lux["LU"]["LU-LU"]["latLng"])) + self.assertEqual(test_request_lux["LU"]["LU-LU"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LU/LU-LU.png", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/LU/LU-LU.png, got {}.".format(test_request_lux["LU"]["LU-LU"]["flagUrl"])) +#3.) + test_request_pa_rw = requests.get(self.alpha_base_url + test_alpha3_pa_rw, headers=self.user_agent_header).json() #Panama and Rwanda self.assertIsInstance(test_request_pa_rw, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_pa_rw))) - self.assertEqual(len(test_request_pa_rw), 2, "Expected output object of API to be of length 2, got {}.".format(len(test_request_pa_rw))) self.assertEqual(list(test_request_pa_rw.keys()), ["PA", "RW"], "Expected output object of API to contain only the PA and RW keys, got {}.".format(list(test_request_pa_rw.keys()))) self.assertEqual(list(test_request_pa_rw["PA"].keys()), ["PA-1", "PA-10", "PA-2", "PA-3", "PA-4", "PA-5", "PA-6", "PA-7", "PA-8", "PA-9", "PA-EM", "PA-KY", "PA-NB", "PA-NT"], "Expected list of subdivision codes doesn't match output.") - self.assertEqual(list(test_request_pa_rw["RW"].keys()), ["RW-01", "RW-02", "RW-03", "RW-04", "RW-05"], "Expected list of subdivision codes doesn't match output.") + self.assertEqual(list(test_request_pa_rw["RW"].keys()), ["RW-01", "RW-02", "RW-03", "RW-04", "RW-05"], + "Expected list of subdivision codes doesn't match output.") for subd in test_request_pa_rw["PA"]: self.assertIsNot(test_request_pa_rw["PA"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_request_pa_rw["PA"][subd]["name"])) @@ -282,56 +248,460 @@ def test_alpha2_endpoint(self): "Expected subdivision latLng to be [-1.656, 29.882], got {}.".format(test_request_pa_rw["RW"]["RW-03"]["latLng"])) self.assertEqual(test_request_pa_rw["RW"]["RW-03"]["flagUrl"], None, "Expected subdivision flag url to be None, got {}.".format(test_request_pa_rw["RW"]["RW-03"]["flagUrl"])) -#5.) - test_request_error1 = requests.get(self.alpha2_base_url + test_alpha2_error_1, headers=self.user_agent_header).json() #ABCDE +#4.) + test_request_740_752 = requests.get(self.alpha_base_url + test_numeric_740_752, headers=self.user_agent_header).json() #740 - Suriname, 752 - Sweden + + self.assertIsInstance(test_request_740_752, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_740_752))) + self.assertEqual(list(test_request_740_752.keys()), ["SE", "SR"], "Expected output object of API to contain SR and SE keys, got {}.".format(list(test_request_740_752.keys()))) + self.assertEqual(list(test_request_740_752["SE"].keys()), ['SE-AB', 'SE-AC', 'SE-BD', 'SE-C', 'SE-D', 'SE-E', 'SE-F', 'SE-G', 'SE-H', 'SE-I', 'SE-K', \ + 'SE-M', 'SE-N', 'SE-O', 'SE-S', 'SE-T', 'SE-U', 'SE-W', 'SE-X', 'SE-Y', 'SE-Z'], + "Expected list of subdivision codes doesn't match output.") + self.assertEqual(list(test_request_740_752["SR"].keys()), ["SR-BR", "SR-CM", "SR-CR", "SR-MA", "SR-NI", "SR-PM", "SR-PR", "SR-SA", "SR-SI", "SR-WA"], + "Expected list of subdivision codes doesn't match output.") + for subd in test_request_740_752["SR"]: + self.assertIsNot(test_request_740_752["SR"][subd]["name"], None, + "Expected subdivision name to not be None, got {}.".format(test_request_740_752["SR"][subd]["name"])) + self.assertEqual(test_request_740_752["SR"][subd]["name"], test_request_740_752["SR"][subd]["localName"], + "Expected subdivision's name and local name to be the same.") + if not (test_request_740_752["SR"][subd]["parentCode"] is None): + self.assertIn(test_request_740_752["SR"][subd]["parentCode"], list(test_request_740_752["SR"][subd].keys()), + "Parent code {} not found in list of subdivision codes.".format(test_request_740_752["SR"][subd]["parentCode"])) + if not (test_request_740_752["SR"][subd]["flagUrl"] is None): + self.assertEqual(os.path.splitext(test_request_740_752["SR"][subd]["flagUrl"])[0], self.flag_base_url + "SR/" + subd, + "Expected flag url to be {}, got {}.".format(self.flag_base_url + "SR/" + subd, os.path.splitext(test_request_740_752["SR"][subd]["flagUrl"])[0])) + self.assertEqual(requests.get(test_request_740_752["SR"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, + "Flag URL invalid: {}.".format(test_request_740_752["SR"][subd]["flagUrl"])) + for key in list(test_request_740_752["SR"][subd].keys()): + self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) + for subd in test_request_740_752["SE"]: + self.assertIsNot(test_request_740_752["SE"][subd]["name"], None, + "Expected subdivision name to not be None, got {}.".format(test_request_740_752["SE"][subd]["name"])) + self.assertEqual(test_request_740_752["SE"][subd]["name"], test_request_740_752["SE"][subd]["localName"], + "Expected subdivision's name and local name to be the same.") + if not (test_request_740_752["SE"][subd]["parentCode"] is None): + self.assertIn(test_request_740_752["SE"][subd]["parentCode"], list(test_request_740_752["SE"][subd].keys()), + "Parent code {} not found in list of subdivision codes.".format(test_request_740_752["SE"][subd]["parentCode"])) + if not (test_request_740_752["SE"][subd]["flagUrl"] is None): + self.assertEqual(os.path.splitext(test_request_740_752["SE"][subd]["flagUrl"])[0], self.flag_base_url + "SE/" + subd, + "Expected flag url to be {}, got {}.".format(self.flag_base_url + "SE/" + subd, os.path.splitext(test_request_740_752["SE"][subd]["flagUrl"])[0])) + self.assertEqual(requests.get(test_request_740_752["SE"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, + "Flag URL invalid: {}.".format(test_request_740_752["SE"][subd]["flagUrl"])) + for key in list(test_request_740_752["SE"][subd].keys()): + self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) + + #SR-SI - Sipaliwini + self.assertEqual(test_request_740_752["SR"]["SR-SI"]["name"], "Sipaliwini", + "Expected subdivsion name to be Sipaliwini, got {}.".format(test_request_740_752["SR"]["SR-SI"]["name"])) + self.assertEqual(test_request_740_752["SR"]["SR-SI"]["localName"], "Sipaliwini", + "Expected subdivsion local name to be Sipaliwini, got {}.".format(test_request_740_752["SR"]["SR-SI"]["localName"])) + self.assertEqual(test_request_740_752["SR"]["SR-SI"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_740_752["SR"]["SR-SI"]["parentCode"])) + self.assertEqual(test_request_740_752["SR"]["SR-SI"]["type"], "District", + "Expected subdivision type to be District, got {}.".format(test_request_740_752["SR"]["SR-SI"]["type"])) + self.assertEqual(test_request_740_752["SR"]["SR-SI"]["latLng"], [2.033, -56.134], + "Expected subdivision latLng to be [2.033, -56.134], got {}.".format(test_request_740_752["SR"]["SR-SI"]["latLng"])) + self.assertEqual(test_request_740_752["SR"]["SR-SI"]["flagUrl"], None, + "Expected subdivision flag url to be None, got {}.".format(test_request_740_752["SR"]["SR-SI"]["flagUrl"])) + #SE-I - Gotlands + self.assertEqual(test_request_740_752["SE"]["SE-I"]["name"], "Gotlands län [SE-09]", + "Expected subdivsion name to be Gotlands län [SE-09], got {}.".format(test_request_740_752["SE"]["SE-I"]["name"])) + self.assertEqual(test_request_740_752["SE"]["SE-I"]["localName"], "Gotlands län [SE-09]", + "Expected subdivsion local name to be Gotlands län [SE-09], got {}.".format(test_request_740_752["SE"]["SE-I"]["localName"])) + self.assertEqual(test_request_740_752["SE"]["SE-I"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_740_752["SE"]["SE-I"]["parentCode"])) + self.assertEqual(test_request_740_752["SE"]["SE-I"]["type"], "County", + "Expected subdivision type to be County, got {}.".format(test_request_740_752["SE"]["SE-I"]["type"])) + self.assertEqual(test_request_740_752["SE"]["SE-I"]["latLng"], [57.531, 18.69], + "Expected subdivision latLng to be [57.531, 18.69], got {}.".format(test_request_740_752["SE"]["SE-I"]["latLng"])) + self.assertEqual(test_request_740_752["SE"]["SE-I"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/SE/SE-I.svg", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/SE/SE-I.svg, got {}.".format(test_request_740_752["SE"]["SE-I"]["flagUrl"])) +#5.) + test_request_ir_kgz_446 = requests.get(self.alpha_base_url + test_alpha2_alpha3_numeric_ir_kgz_446, headers=self.user_agent_header).json() #Iran, Kyrgyzstan, Macao + + self.assertIsInstance(test_request_ir_kgz_446, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_ir_kgz_446))) + self.assertEqual(list(test_request_ir_kgz_446.keys()), ["IR", "KG", "MO"], "Expected output object of API to contain IT, KG and MO keys, got {}.".format(list(test_request_ir_kgz_446.keys()))) + self.assertEqual(list(test_request_ir_kgz_446["IR"].keys()), ['IR-00', 'IR-01', 'IR-02', 'IR-03', 'IR-04', 'IR-05', 'IR-06', 'IR-07', 'IR-08', 'IR-09', 'IR-10', 'IR-11', 'IR-12', 'IR-13', 'IR-14', \ + 'IR-15', 'IR-16', 'IR-17', 'IR-18', 'IR-19', 'IR-20', 'IR-21', 'IR-22', 'IR-23', 'IR-24', 'IR-25', 'IR-26', 'IR-27', 'IR-28', 'IR-29', 'IR-30'], + "Expected list of subdivision codes doesn't match output.") + self.assertEqual(list(test_request_ir_kgz_446["KG"].keys()), ['KG-B', 'KG-C', 'KG-GB', 'KG-GO', 'KG-J', 'KG-N', 'KG-O', 'KG-T', 'KG-Y'], + "Expected list of subdivision codes doesn't match output.") + self.assertEqual(list(test_request_ir_kgz_446["MO"].keys()), [], "Expected no suddivision codes in output object.") + + for subd in test_request_ir_kgz_446["IR"]: + self.assertIsNot(test_request_ir_kgz_446["IR"][subd]["name"], None, + "Expected subdivision name to not be None, got {}.".format(test_request_ir_kgz_446["IR"][subd]["name"])) + self.assertEqual(test_request_ir_kgz_446["IR"][subd]["name"], test_request_ir_kgz_446["IR"][subd]["localName"], + "Expected subdivision's name and local name to be the same.") + if not (test_request_ir_kgz_446["IR"][subd]["parentCode"] is None): + self.assertIn(test_request_ir_kgz_446["IR"][subd]["parentCode"], list(test_request_ir_kgz_446["IR"][subd].keys()), + "Parent code {} not found in list of subdivision codes.".format(test_request_ir_kgz_446["IR"][subd]["parentCode"])) + if not (test_request_ir_kgz_446["IR"][subd]["flagUrl"] is None): + self.assertEqual(os.path.splitext(test_request_ir_kgz_446["IR"][subd]["flagUrl"])[0], self.flag_base_url + "IR/" + subd, + "Expected flag url to be {}, got {}.".format(self.flag_base_url + "IR/" + subd, os.path.splitext(test_request_ir_kgz_446["IR"][subd]["flagUrl"])[0])) + self.assertEqual(requests.get(test_request_ir_kgz_446["IR"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, + "Flag URL invalid: {}.".format(test_request_ir_kgz_446["IR"][subd]["flagUrl"])) + for key in list(test_request_ir_kgz_446["IR"][subd].keys()): + self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) + for subd in test_request_ir_kgz_446["KG"]: + self.assertIsNot(test_request_ir_kgz_446["KG"][subd]["name"], None, + "Expected subdivision name to not be None, got {}.".format(test_request_ir_kgz_446["KG"][subd]["name"])) + self.assertEqual(test_request_ir_kgz_446["KG"][subd]["name"], test_request_ir_kgz_446["KG"][subd]["localName"], + "Expected subdivision's name and local name to be the same.") + if not (test_request_ir_kgz_446["KG"][subd]["parentCode"] is None): + self.assertIn(test_request_ir_kgz_446["KG"][subd]["parentCode"], list(test_request_ir_kgz_446["KG"][subd].keys()), + "Parent code {} not found in list of subdivision codes.".format(test_request_ir_kgz_446["KG"][subd]["parentCode"])) + if not (test_request_ir_kgz_446["KG"][subd]["flagUrl"] is None): + self.assertEqual(os.path.splitext(test_request_ir_kgz_446["KG"][subd]["flagUrl"])[0], self.flag_base_url + "KG/" + subd, + "Expected flag url to be {}, got {}.".format(self.flag_base_url + "KG/" + subd, os.path.splitext(test_request_ir_kgz_446["KG"][subd]["flagUrl"])[0])) + self.assertEqual(requests.get(test_request_ir_kgz_446["KG"][subd]["flagUrl"], headers=self.user_agent_header).status_code, 200, + "Flag URL invalid: {}.".format(test_request_ir_kgz_446["KG"][subd]["flagUrl"])) + for key in list(test_request_ir_kgz_446["KG"][subd].keys()): + self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) +#6.) + test_request_error1 = requests.get(self.alpha_base_url + test_alpha_error_1, headers=self.user_agent_header).json() #ABCDE self.assertIsInstance(test_request_error1, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_error1))) self.assertEqual(len(test_request_error1), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_error1))) - self.assertEqual(test_request_error1["message"], 'Invalid 2 letter alpha-2 code input: ' + test_alpha2_error_1 + ".", + self.assertEqual(test_request_error1["message"], "Invalid ISO 3166-1 alpha country code input, cannot convert into corresponding alpha-2 code: {}.".format(test_alpha_error_1), "Error message does not match expected:\n{}".format(test_request_error1["message"])) - self.assertEqual(test_request_error1["path"], self.alpha2_base_url + test_alpha2_error_1, + self.assertEqual(test_request_error1["path"], self.alpha_base_url + test_alpha_error_1, "Error path does not match expected:\n{}.".format(test_request_error1["path"])) self.assertEqual(test_request_error1["status"], 400, "Error status does not match expected:\n{}.".format(test_request_error1["status"])) -#6.) - test_request_error2 = requests.get(self.alpha2_base_url + test_alpha2_error_2, headers=self.user_agent_header).json() #12345 +#7.) + test_request_error2 = requests.get(self.alpha_base_url + test_alpha_error_2, headers=self.user_agent_header).json() #12345 self.assertIsInstance(test_request_error2, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_error2))) self.assertEqual(len(test_request_error2), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_error2))) - self.assertEqual(test_request_error2["message"], 'Invalid 2 letter alpha-2 code input: ' + test_alpha2_error_2 + ".", + self.assertEqual(test_request_error2["message"], "Invalid ISO 3166-1 numeric country code input, cannot convert into corresponding alpha-2 code: {}.".format(test_alpha_error_2), "Error message does not match expected:\n{}".format(test_request_error2["message"])) - self.assertEqual(test_request_error2["path"], self.alpha2_base_url + test_alpha2_error_2, + self.assertEqual(test_request_error2["path"], self.alpha_base_url + test_alpha_error_2, "Error path does not match expected:\n{}.".format(test_request_error2["path"])) self.assertEqual(test_request_error2["status"], 400, "Error status does not match expected:\n{}.".format(test_request_error2["status"])) +#8.) + test_request_error3 = requests.get(self.alpha_base_url + test_alpha_error_3, headers=self.user_agent_header).json() #"" + + self.assertIsInstance(test_request_error3, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_error3))) + self.assertEqual(len(test_request_error3), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_error3))) + self.assertEqual(test_request_error3["message"], "The alpha input parameter cannot be empty.", + "Error message does not match expected:\n{}".format(test_request_error3["message"])) + self.assertEqual(test_request_error3["path"], self.alpha_base_url + test_alpha_error_3, + "Error path does not match expected:\n{}.".format(test_request_error3["path"])) + self.assertEqual(test_request_error3["status"], 400, + "Error status does not match expected:\n{}.".format(test_request_error3["status"])) + + def test_subdivision_endpoint(self): + """ Testing /subdivision endpoint, return all ISO 3166 subdivision data from input subdivision code/codes. """ + test_subd_jm_05 = "JM-05" + test_subd_pa_03 = "PA-3" + test_subd_ss_ew = "SS-EW" + test_subd_tv_nkf_tj_du = "TV-NKF, TJ-DU" + test_subd_gb_xyz = "GB-XYZ" + test_subd_xx_yy = "XX-YY" +#1.) + test_request_jm_05 = requests.get(self.subdivision_base_url + test_subd_jm_05, headers=self.user_agent_header).json() #JM-05 + self.assertIsInstance(test_request_jm_05, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_jm_05))) + self.assertEqual(list(test_request_jm_05.keys()), ["JM-05"], "Expected output object of API to contain only the JM-05 key, got {}.".format(list(test_request_jm_05.keys()))) + + #JM-05 - Saint Mary + self.assertEqual(test_request_jm_05["JM-05"]["name"], "Saint Mary", + "Expected subdivsion name to be Saint Mary, got {}.".format(test_request_jm_05["JM-05"]["name"])) + self.assertEqual(test_request_jm_05["JM-05"]["localName"], "Saint Mary", + "Expected subdivsion local name to be Saint Mary, got {}.".format(test_request_jm_05["JM-05"]["localName"])) + self.assertEqual(test_request_jm_05["JM-05"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_jm_05["JM-05"]["parentCode"])) + self.assertEqual(test_request_jm_05["JM-05"]["type"], "Parish", + "Expected subdivision type to be Parish, got {}.".format(test_request_jm_05["JM-05"]["type"])) + self.assertEqual(test_request_jm_05["JM-05"]["latLng"], [18.309, -76.964], + "Expected subdivision latLng to be [18.309, -76.964], got {}.".format(test_request_jm_05["JM-05"]["latLng"])) + self.assertEqual(test_request_jm_05["JM-05"]["flagUrl"], None, + "Expected subdivision flag url to be None, got {}.".format(test_request_jm_05["JM-05"]["flagUrl"])) +#2) + test_request_pa_03 = requests.get(self.subdivision_base_url + test_subd_pa_03, headers=self.user_agent_header).json() #PA-3 + self.assertIsInstance(test_request_pa_03, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_pa_03))) + self.assertEqual(list(test_request_pa_03.keys()), ["PA-3"], "Expected output object of API to contain only the PA-3 key, got {}.".format(list(test_request_pa_03.keys()))) + + #PA-3 - Colón + self.assertEqual(test_request_pa_03["PA-3"]["name"], "Colón", + "Expected subdivsion name to be Colón, got {}.".format(test_request_pa_03["PA-3"]["name"])) + self.assertEqual(test_request_pa_03["PA-3"]["localName"], "Colón", + "Expected subdivsion local name to be Colón, got {}.".format(test_request_pa_03["PA-3"]["localName"])) + self.assertEqual(test_request_pa_03["PA-3"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_pa_03["PA-3"]["parentCode"])) + self.assertEqual(test_request_pa_03["PA-3"]["type"], "Province", + "Expected subdivision type to be Province, got {}.".format(test_request_pa_03["PA-3"]["type"])) + self.assertEqual(test_request_pa_03["PA-3"]["latLng"], [9.359, -79.9], + "Expected subdivision latLng to be [9.359, -79.9], got {}.".format(test_request_pa_03["PA-3"]["latLng"])) + self.assertEqual(test_request_pa_03["PA-3"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/PA/PA-3.svg", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/PA/PA-3.svg, got {}.".format(test_request_pa_03["PA-3"]["flagUrl"])) +#3.) + test_request_ss_ew = requests.get(self.subdivision_base_url + test_subd_ss_ew, headers=self.user_agent_header).json() #SS-EW + self.assertIsInstance(test_request_ss_ew, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_ss_ew))) + self.assertEqual(list(test_request_ss_ew.keys()), ["SS-EW"], "Expected output object of API to contain only the SS-EW key, got {}.".format(list(test_request_ss_ew.keys()))) + + #SS-EW - Western Equatoria + self.assertEqual(test_request_ss_ew["SS-EW"]["name"], "Western Equatoria", + "Expected subdivsion name to be Western Equatoria, got {}.".format(test_request_ss_ew["SS-EW"]["name"])) + self.assertEqual(test_request_ss_ew["SS-EW"]["localName"], "Western Equatoria", + "Expected subdivsion local name to be Western Equatoria, got {}.".format(test_request_ss_ew["SS-EW"]["localName"])) + self.assertEqual(test_request_ss_ew["SS-EW"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_ss_ew["SS-EW"]["parentCode"])) + self.assertEqual(test_request_ss_ew["SS-EW"]["type"], "State", + "Expected subdivision type to be State, got {}.".format(test_request_ss_ew["SS-EW"]["type"])) + self.assertEqual(test_request_ss_ew["SS-EW"]["latLng"], [5.347, 28.299], + "Expected subdivision latLng to be [5.347, 28.299], got {}.".format(test_request_ss_ew["SS-EW"]["latLng"])) + self.assertEqual(test_request_ss_ew["SS-EW"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/SS/SS-EW.png", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/SS/SS-EW.png, got {}.".format(test_request_ss_ew["SS-EW"]["flagUrl"])) +#4.) + test_request_tv_nkf_tj_du = requests.get(self.subdivision_base_url + test_subd_tv_nkf_tj_du, headers=self.user_agent_header).json() #TV-NKF, TJ-DU + self.assertIsInstance(test_request_tv_nkf_tj_du, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_tv_nkf_tj_du))) + self.assertEqual(list(test_request_tv_nkf_tj_du.keys()), ["TJ-DU", "TV-NKF"], "Expected output object of API to contain only the TJ-DU and TV-NKF keys, got {}.".format(list(test_request_tv_nkf_tj_du.keys()))) + + #TV-NKF - Western Equatoria + self.assertEqual(test_request_tv_nkf_tj_du["TV-NKF"]["name"], "Nukufetau", + "Expected subdivsion name to be Nukufetau, got {}.".format(test_request_tv_nkf_tj_du["TV-NKF"]["name"])) + self.assertEqual(test_request_tv_nkf_tj_du["TV-NKF"]["localName"], "Nukufetau", + "Expected subdivsion local name to be Nukufetau, got {}.".format(test_request_tv_nkf_tj_du["TV-NKF"]["localName"])) + self.assertEqual(test_request_tv_nkf_tj_du["TV-NKF"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_tv_nkf_tj_du["TV-NKF"]["parentCode"])) + self.assertEqual(test_request_tv_nkf_tj_du["TV-NKF"]["type"], "Island council", + "Expected subdivision type to be Island Council, got {}.".format(test_request_tv_nkf_tj_du["TV-NKF"]["type"])) + self.assertEqual(test_request_tv_nkf_tj_du["TV-NKF"]["latLng"], [-8, 178.5], + "Expected subdivision latLng to be [-8, 178.5], got {}.".format(test_request_tv_nkf_tj_du["TV-NKF"]["latLng"])) + self.assertEqual(test_request_tv_nkf_tj_du["TV-NKF"]["flagUrl"], None, + "Expected subdivision flag url to be None, got {}.".format(test_request_tv_nkf_tj_du["TV-NKF"]["flagUrl"])) + #TJ-DU - Dushanbe + self.assertEqual(test_request_tv_nkf_tj_du["TJ-DU"]["name"], "Dushanbe", + "Expected subdivsion name to be Dushanbe, got {}.".format(test_request_tv_nkf_tj_du["TJ-DU"]["name"])) + self.assertEqual(test_request_tv_nkf_tj_du["TJ-DU"]["localName"], "Dushanbe", + "Expected subdivsion local name to be Dushanbe, got {}.".format(test_request_tv_nkf_tj_du["TJ-DU"]["localName"])) + self.assertEqual(test_request_tv_nkf_tj_du["TJ-DU"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_tv_nkf_tj_du["TJ-DU"]["parentCode"])) + self.assertEqual(test_request_tv_nkf_tj_du["TJ-DU"]["type"], "Capital territory", + "Expected subdivision type to be Capital territory, got {}.".format(test_request_tv_nkf_tj_du["TJ-DU"]["type"])) + self.assertEqual(test_request_tv_nkf_tj_du["TJ-DU"]["latLng"], [38.56, 68.787], + "Expected subdivision latLng to be [38.56, 68.787], got {}.".format(test_request_tv_nkf_tj_du["TJ-DU"]["latLng"])) + self.assertEqual(test_request_tv_nkf_tj_du["TJ-DU"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/TJ/TJ-DU.png", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/TJ/TJ-DU.png, got {}.".format(test_request_tv_nkf_tj_du["TJ-DU"]["flagUrl"])) +#5.) + test_request_gb_xyz = requests.get(self.subdivision_base_url + test_subd_gb_xyz, headers=self.user_agent_header).json() #GB-XYZ + self.assertIsInstance(test_request_gb_xyz, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_gb_xyz))) + self.assertEqual(len(test_request_gb_xyz), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_gb_xyz))) + self.assertEqual(test_request_gb_xyz["message"], "Subdivision code {} not found in list of available subdivisions for GB.".format(test_subd_gb_xyz), + "Error message does not match expected:\n{}".format(test_request_gb_xyz["message"])) + self.assertEqual(test_request_gb_xyz["path"], self.subdivision_base_url + test_subd_gb_xyz, + "Error path does not match expected:\n{}".format(test_request_gb_xyz["path"])) + self.assertEqual(test_request_gb_xyz["status"], 400, + "Error status does not match expected:\n{}".format(test_request_gb_xyz["status"])) +#6.) + test_request_xx_yy = requests.get(self.subdivision_base_url + test_subd_xx_yy, headers=self.user_agent_header).json() #XX-YY + + self.assertIsInstance(test_request_xx_yy, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_xx_yy))) + self.assertEqual(len(test_request_xx_yy), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_xx_yy))) + self.assertEqual(test_request_xx_yy["message"], "Subdivision code {} not found in list of available subdivisions for XX.".format(test_subd_xx_yy), + "Error message does not match expected:\n{}".format(test_request_xx_yy["message"])) + self.assertEqual(test_request_xx_yy["path"], self.subdivision_base_url + test_subd_xx_yy, + "Error path does not match expected:\n{}".format(test_request_xx_yy["path"])) + self.assertEqual(test_request_xx_yy["status"], 400, + "Error status does not match expected:\n{}".format(test_request_xx_yy["status"])) + + def test_subdivision_name_endpoint(self): + """ Testing /name endpoint, return all ISO 3166 subdivision data from input subdivision name/names. """ + test_subdivision_name_azua = "Azua" #DO-02 + test_subdivision_name_cakaudrove = "Cakaudrove" #FJ-03 + test_subdivision_name_gelderland_overijssel = "Gelderland, Overijssel" #NL-GE, NL-OV + test_subdivision_name_ciudad = "Ciudad" + test_subdivision_madrid_armaghcity = "Madrid, Comunidad de, Armagh City, Banbridge and Craigavon" #ES-MD, GB-ABC + test_subdivision_name_error1 = "blahblahblah" + test_subdivision_name_error2 = "1234" + test_subdivision_name_error3 = "" +#1.) + test_request_azua = requests.get(self.subdivision_name_base_url + test_subdivision_name_azua, headers=self.user_agent_header).json() #DO-02 - Azua + + self.assertIsInstance(test_request_azua, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_azua))) + self.assertEqual(list(test_request_azua.keys()), ["DO-02"], "Expected output object of API to contain only the DO-02 key, got {}.".format(list(test_request_azua.keys()))) + + #DO-02 - Azua + self.assertEqual(test_request_azua["DO-02"]["name"], "Azua", + "Expected subdivsion name to be Azua, got {}.".format(test_request_azua["DO-02"]["name"])) + self.assertEqual(test_request_azua["DO-02"]["localName"], "Azua", + "Expected subdivsion local name to be Azua, got {}.".format(test_request_azua["DO-02"]["localName"])) + self.assertEqual(test_request_azua["DO-02"]["parentCode"], "DO-41", + "Expected subdivision parent code to be DO-41, got {}.".format(test_request_azua["DO-02"]["parentCode"])) + self.assertEqual(test_request_azua["DO-02"]["type"], "Province", + "Expected subdivision type to be Province, got {}.".format(test_request_azua["DO-02"]["type"])) + self.assertEqual(test_request_azua["DO-02"]["latLng"], [18.453, -70.735], + "Expected subdivision latLng to be [18.453, -70.735], got {}.".format(test_request_azua["DO-02"]["latLng"])) + self.assertEqual(test_request_azua["DO-02"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DO/DO-02.png", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/DO/DO-02.png, got {}.".format(test_request_azua["DO-02"]["flagUrl"])) +#2.) + test_request_cakaudrove = requests.get(self.subdivision_name_base_url + test_subdivision_name_cakaudrove, headers=self.user_agent_header).json() #FJ-03 - Cakaudrove + + self.assertIsInstance(test_request_cakaudrove, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_cakaudrove))) + self.assertEqual(list(test_request_cakaudrove.keys()), ["FJ-03"], "Expected output object of API to contain only the FJ-03 key, got {}.".format(list(test_request_cakaudrove.keys()))) + + #FJ-03 - Cakaudrove + self.assertEqual(test_request_cakaudrove["FJ-03"]["name"], "Cakaudrove", + "Expected subdivsion name to be Cakaudrove, got {}.".format(test_request_cakaudrove["FJ-03"]["name"])) + self.assertEqual(test_request_cakaudrove["FJ-03"]["localName"], "Cakaudrove", + "Expected subdivsion local name to be Cakaudrove, got {}.".format(test_request_cakaudrove["FJ-03"]["localName"])) + self.assertEqual(test_request_cakaudrove["FJ-03"]["parentCode"], "FJ-N", + "Expected subdivision parent code to be FJ-N, got {}.".format(test_request_cakaudrove["FJ-03"]["parentCode"])) + self.assertEqual(test_request_cakaudrove["FJ-03"]["type"], "Province", + "Expected subdivision type to be Province, got {}.".format(test_request_cakaudrove["FJ-03"]["type"])) + self.assertEqual(test_request_cakaudrove["FJ-03"]["latLng"], [-16.581, 179.436], + "Expected subdivision latLng to be [-16.581, 179.436], got {}.".format(test_request_cakaudrove["FJ-03"]["latLng"])) + self.assertEqual(test_request_cakaudrove["FJ-03"]["flagUrl"], None, + "Expected subdivision flag url to be None, got {}.".format(test_request_cakaudrove["FJ-03"]["flagUrl"])) +#3.) + test_request_gelderland_overijssel = requests.get(self.subdivision_name_base_url + test_subdivision_name_gelderland_overijssel, headers=self.user_agent_header).json() #NL-GE - Gelderland, NL-OV - Overijssel + + self.assertIsInstance(test_request_gelderland_overijssel, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_gelderland_overijssel))) + self.assertEqual(list(test_request_gelderland_overijssel.keys()), ["NL-GE", "NL-OV"], "Expected output object of API to contain only the NL-GE and NL-OV keys, got {}.".format(list(test_request_gelderland_overijssel.keys()))) + + #NL-GE - Gelderland + self.assertEqual(test_request_gelderland_overijssel["NL-GE"]["name"], "Gelderland", + "Expected subdivsion name to be Gelderland, got {}.".format(test_request_gelderland_overijssel["NL-GE"]["name"])) + self.assertEqual(test_request_gelderland_overijssel["NL-GE"]["localName"], "Gelderland", + "Expected subdivsion local name to be Gelderland, got {}.".format(test_request_gelderland_overijssel["NL-GE"]["localName"])) + self.assertEqual(test_request_gelderland_overijssel["NL-GE"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_gelderland_overijssel["NL-GE"]["parentCode"])) + self.assertEqual(test_request_gelderland_overijssel["NL-GE"]["type"], "Province", + "Expected subdivision type to be Province, got {}.".format(test_request_gelderland_overijssel["NL-GE"]["type"])) + self.assertEqual(test_request_gelderland_overijssel["NL-GE"]["latLng"], [52.045, 5.872], + "Expected subdivision latLng to be [52.045, 5.872], got {}.".format(test_request_gelderland_overijssel["NL-GE"]["latLng"])) + self.assertEqual(test_request_gelderland_overijssel["NL-GE"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/NL/NL-GE.svg", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/NL/NL-GE.svg, got {}.".format(test_request_gelderland_overijssel["NL-GE"]["flagUrl"])) + #NL-OV - Overijssel + self.assertEqual(test_request_gelderland_overijssel["NL-OV"]["name"], "Overijssel", + "Expected subdivsion name to be Overijssel, got {}.".format(test_request_gelderland_overijssel["NL-OV"]["name"])) + self.assertEqual(test_request_gelderland_overijssel["NL-OV"]["localName"], "Overijssel", + "Expected subdivsion local name to be Overijssel, got {}.".format(test_request_gelderland_overijssel["NL-OV"]["localName"])) + self.assertEqual(test_request_gelderland_overijssel["NL-OV"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_gelderland_overijssel["NL-OV"]["parentCode"])) + self.assertEqual(test_request_gelderland_overijssel["NL-OV"]["type"], "Province", + "Expected subdivision type to be Province, got {}.".format(test_request_gelderland_overijssel["NL-OV"]["type"])) + self.assertEqual(test_request_gelderland_overijssel["NL-OV"]["latLng"], [52.439, 6.502], + "Expected subdivision latLng to be [52.439, 6.502], got {}.".format(test_request_gelderland_overijssel["NL-OV"]["latLng"])) + self.assertEqual(test_request_gelderland_overijssel["NL-OV"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/NL/NL-OV.svg", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/NL/NL-OV.svg, got {}.".format(test_request_gelderland_overijssel["NL-OV"]["flagUrl"])) +#4.) + test_request_ciudad = requests.get(self.subdivision_name_base_url + test_subdivision_name_ciudad, headers=self.user_agent_header, params={"likeness": "50"}).json() #Ciudad - likeness score of 50 + + self.assertIsInstance(test_request_ciudad, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_ciudad))) + self.assertEqual(list(test_request_ciudad.keys()), ["AO-CAB", "AZ-CUL", "ES-CR", "MX-CMX", "ZW-MI"], "Expected output object of API to contain only the AO-CAB, AZ-CUL, ES-CR, MX-CMX and ZW-MI keys, got {}.".format(list(test_request_ciudad.keys()))) + + #ES-CR - Ciudad Real + self.assertEqual(test_request_ciudad["ES-CR"]["name"], "Ciudad Real", + "Expected subdivsion name to be Ciudad Real, got {}.".format(test_request_ciudad["ES-CR"]["name"])) + self.assertEqual(test_request_ciudad["ES-CR"]["localName"], "Ciudad Real", + "Expected subdivsion local name to be Ciudad Real, got {}.".format(test_request_ciudad["ES-CR"]["localName"])) + self.assertEqual(test_request_ciudad["ES-CR"]["parentCode"], "ES-CM", + "Expected subdivision parent code to be ES-CM, got {}.".format(test_request_ciudad["ES-CR"]["parentCode"])) + self.assertEqual(test_request_ciudad["ES-CR"]["type"], "Province", + "Expected subdivision type to be Province, got {}.".format(test_request_ciudad["ES-CR"]["type"])) + self.assertEqual(test_request_ciudad["ES-CR"]["latLng"], [38.985, -3.927], + "Expected subdivision latLng to be [38.985, -3.927], got {}.".format(test_request_ciudad["ES-CR"]["latLng"])) + self.assertEqual(test_request_ciudad["ES-CR"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-CR.svg", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-CR.svg, got {}.".format(test_request_ciudad["ES-CR"]["flagUrl"])) + #MX-CMX - Ciudad de Mexico + self.assertEqual(test_request_ciudad["MX-CMX"]["name"], "Ciudad de México", + "Expected subdivsion name to be Ciudad de México, got {}.".format(test_request_ciudad["MX-CMX"]["name"])) + self.assertEqual(test_request_ciudad["MX-CMX"]["localName"], "Ciudad de México", + "Expected subdivsion local name to be Ciudad de México, got {}.".format(test_request_ciudad["MX-CMX"]["localName"])) + self.assertEqual(test_request_ciudad["MX-CMX"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_ciudad["MX-CMX"]["parentCode"])) + self.assertEqual(test_request_ciudad["MX-CMX"]["type"], "Federal entity", + "Expected subdivision type to be Federal entity, got {}.".format(test_request_ciudad["MX-CMX"]["type"])) + self.assertEqual(test_request_ciudad["MX-CMX"]["latLng"], [19.433, -99.133], + "Expected subdivision latLng to be [19.433, -99.133], got {}.".format(test_request_ciudad["MX-CMX"]["latLng"])) + self.assertEqual(test_request_ciudad["MX-CMX"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/MX/MX-CMX.svg", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/MX/MX-CMX.svg, got {}.".format(test_request_ciudad["MX-CMX"]["flagUrl"])) +#5.) + test_request_madrid_armaghcity = requests.get(self.subdivision_name_base_url + test_subdivision_madrid_armaghcity, headers=self.user_agent_header).json() #ES-MD - Madrid, GB-ABC - Armagh City, Banbridge and Craigavon + + self.assertIsInstance(test_request_madrid_armaghcity, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_madrid_armaghcity))) + self.assertEqual(list(test_request_madrid_armaghcity.keys()), ["ES-MD", "GB-ABC"], "Expected output object of API to contain only the ES-MD and GB-ABC keys, got {}.".format(list(test_request_madrid_armaghcity.keys()))) + + #ES-MD - Madrid + self.assertEqual(test_request_madrid_armaghcity["ES-MD"]["name"], "Madrid, Comunidad de", + "Expected subdivsion name to be Madrid, Comunidad de, got {}.".format(test_request_madrid_armaghcity["ES-MD"]["name"])) + self.assertEqual(test_request_madrid_armaghcity["ES-MD"]["localName"], "Madrid, Comunidad de", + "Expected subdivsion local name to be Madrid, Comunidad de, got {}.".format(test_request_madrid_armaghcity["ES-MD"]["localName"])) + self.assertEqual(test_request_madrid_armaghcity["ES-MD"]["parentCode"], None, + "Expected subdivision parent code to be None, got {}.".format(test_request_madrid_armaghcity["ES-MD"]["parentCode"])) + self.assertEqual(test_request_madrid_armaghcity["ES-MD"]["type"], "Autonomous community", + "Expected subdivision type to be Autonomous community, got {}.".format(test_request_madrid_armaghcity["ES-MD"]["type"])) + self.assertEqual(test_request_madrid_armaghcity["ES-MD"]["latLng"], [40.417, -3.704], + "Expected subdivision latLng to be [40.417, -3.704], got {}.".format(test_request_madrid_armaghcity["ES-MD"]["latLng"])) + self.assertEqual(test_request_madrid_armaghcity["ES-MD"]["flagUrl"], "https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-MD.svg", + "Expected subdivision flag url to be https://github.com/amckenna41/iso3166-flag-icons/blob/main/iso3166-2-icons/ES/ES-MD.svg, got {}.".format(test_request_madrid_armaghcity["ES-MD"]["flagUrl"])) + #GB-ABC - Armagh City, Banbridge and Craigavon + self.assertEqual(test_request_madrid_armaghcity["GB-ABC"]["name"], "Armagh City, Banbridge and Craigavon", + "Expected subdivsion name to be Armagh City, Banbridge and Craigavon, got {}.".format(test_request_madrid_armaghcity["GB-ABC"]["name"])) + self.assertEqual(test_request_madrid_armaghcity["GB-ABC"]["localName"], "Armagh City, Banbridge and Craigavon", + "Expected subdivsion local name to be Armagh City, Banbridge and Craigavon, got {}.".format(test_request_madrid_armaghcity["GB-ABC"]["localName"])) + self.assertEqual(test_request_madrid_armaghcity["GB-ABC"]["parentCode"], "GB-NIR", + "Expected subdivision parent code to be GB-NIR, got {}.".format(test_request_madrid_armaghcity["GB-ABC"]["parentCode"])) + self.assertEqual(test_request_madrid_armaghcity["GB-ABC"]["type"], "District", + "Expected subdivision type to be District, got {}.".format(test_request_madrid_armaghcity["GB-ABC"]["type"])) + self.assertEqual(test_request_madrid_armaghcity["GB-ABC"]["latLng"], [54.393, -6.456], + "Expected subdivision latLng to be [54.393, -6.456], got {}.".format(test_request_madrid_armaghcity["GB-ABC"]["latLng"])) + self.assertEqual(test_request_madrid_armaghcity["GB-ABC"]["flagUrl"], None, + "Expected subdivision flag url to be None, got {}.".format(test_request_madrid_armaghcity["GB-ABC"]["flagUrl"])) +#6.) + test_request_subdivision_error1 = requests.get(self.subdivision_name_base_url + test_subdivision_name_error1, headers=self.user_agent_header).json() #blahblahblah + + self.assertIsInstance(test_request_subdivision_error1, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_subdivision_error1))) + self.assertEqual(len(test_request_subdivision_error1), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_subdivision_error1))) + self.assertEqual(test_request_subdivision_error1["message"], "No valid subdivision found for input name: {}. Try using the query string parameter '?likeness' and " + "reduce the likeness score to expand the search space, e.g '?likeness=0.3' will return subdivisions that have a 30% match to the input name.".format(test_subdivision_name_error1), + "Error message does not match expected:\n{}".format(test_request_subdivision_error1["message"])) + self.assertEqual(test_request_subdivision_error1["path"], self.subdivision_name_base_url + test_subdivision_name_error1, + "Error path does not match expected:\n{}".format(test_request_subdivision_error1["path"])) + self.assertEqual(test_request_subdivision_error1["status"], 400, + "Error status does not match expected:\n{}".format(test_request_subdivision_error1["status"])) +#6.) + test_request_subdivision_error2 = requests.get(self.subdivision_name_base_url + test_subdivision_name_error2, headers=self.user_agent_header).json() #1234 + + self.assertIsInstance(test_request_subdivision_error2, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_subdivision_error2))) + self.assertEqual(len(test_request_subdivision_error2), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_subdivision_error2))) + self.assertEqual(test_request_subdivision_error2["message"], "No valid subdivision found for input name: {}. Try using the query string parameter '?likeness' and " + "reduce the likeness score to expand the search space, e.g '?likeness=0.3' will return subdivisions that have a 30% match to the input name.".format(test_subdivision_name_error2), + "Error message does not match expected:\n{}".format(test_request_subdivision_error2["message"])) + self.assertEqual(test_request_subdivision_error2["path"], self.subdivision_name_base_url + test_subdivision_name_error2, + "Error path does not match expected:\n{}".format(test_request_subdivision_error2["path"])) + self.assertEqual(test_request_subdivision_error2["status"], 400, + "Error status does not match expected:\n{}".format(test_request_subdivision_error2["status"])) +#7.) + test_request_subdivision_error3 = requests.get(self.subdivision_name_base_url + test_subdivision_name_error3, headers=self.user_agent_header).json() #"" + + self.assertIsInstance(test_request_subdivision_error3, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_subdivision_error3))) + self.assertEqual(len(test_request_subdivision_error3), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_subdivision_error3))) + self.assertEqual(test_request_subdivision_error3["message"], "The subdivision name input parameter cannot be empty.", + "Error message does not match expected:\n{}".format(test_request_subdivision_error3["message"])) + self.assertEqual(test_request_subdivision_error3["path"], self.subdivision_name_base_url + test_subdivision_name_error3, + "Error path does not match expected:\n{}".format(test_request_subdivision_error3["path"])) + self.assertEqual(test_request_subdivision_error3["status"], 400, + "Error status does not match expected:\n{}".format(test_request_subdivision_error3["status"])) + def test_name_endpoint(self): - """ Testing name endpoint, return all ISO 3166 subdivision data from input alpha-2 name/names. """ - test_name_bj = "Benin" - test_name_tj = "Tajikistan" - test_name_sd = "Sudan" - test_name_ml_ni = "Mali, Nicaragua" - test_name_error1 = "ABCDEF" - test_name_error2 = "12345" - name_exceptions_converted = {"Brunei Darussalam": "Brunei", "Bolivia, Plurinational State of": "Bolivia", - "Bonaire, Sint Eustatius and Saba": "Caribbean Netherlands", "Congo, Democratic Republic of the": "DR Congo", - "Congo": "Republic of the Congo", "Côte d'Ivoire": "Ivory Coast", "Cabo Verde": "Cape Verde", "Falkland Islands (Malvinas)": - "Falkland Islands", "Micronesia, Federated States of" : "Micronesia", "United Kingdom of Great Britain and Northern Ireland": "United Kingdom", - "South Georgia and the South Sandwich Islands": "South Georgia", "Iran, Islamic Republic of": "Iran", - "Korea, Democratic People's Republic of": "North Korea", "Korea, Republic of": "South Korea", - "Lao People's Democratic Republic": "Laos", "Moldova, Republic of": "Moldova", "Saint Martin (French part)": "Saint Martin", - "Macao": "Macau", "Pitcairn": "Pitcairn Islands", "Palestine, State of": "Palestine", "Russian Federation": "Russia", "Sao Tome and Principe": "São Tomé and Príncipe", - "Sint Maarten (Dutch part)": "Sint Maarten", "Syrian Arab Republic": "Syria", "French Southern Territories": "French Southern and Antarctic Lands", - "Türkiye": "Turkey", "Taiwan, Province of China": "Taiwan", "Tanzania, United Republic of": "Tanzania", "United States of America": "United States", - "Holy See": "Vatican City", "Venezuela, Bolivarian Republic of": "Venezuela", "Virgin Islands, British": "British Virgin Islands", - "Virgin Islands, U.S.": "United States Virgin Islands", "Viet Nam": "Vietnam"} + """ Testing /country_name endpoint, return all ISO 3166 subdivision data from input country name/names. """ + test_country_name_bj = "Benin" + test_country_name_tj = "Tajikistan" + test_country_name_sd = "Sudan" + test_country_name_ml_ni = "Mali, Nicaragua" + test_country_name_error1 = "ABCDEF" + test_country_name_error2 = "12345" #1.) - test_request_bj = requests.get(self.name_base_url + test_name_bj, headers=self.user_agent_header).json() #Benin + test_request_bj = requests.get(self.country_name_base_url + test_country_name_bj, headers=self.user_agent_header).json() #Benin self.assertIsInstance(test_request_bj, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_bj))) - self.assertEqual(len(test_request_bj), 1, "Expected output object of API to be of length 1, got {}.".format(len(test_request_bj))) self.assertEqual(list(test_request_bj.keys()), ["BJ"], "Expected output object of API to contain only the BJ key, got {}.".format(list(test_request_bj.keys()))) - self.assertEqual(list(test_request_bj["BJ"].keys()), ["BJ-AK", "BJ-AL", "BJ-AQ", "BJ-BO", "BJ-CO", "BJ-DO", "BJ-KO", "BJ-LI", "BJ-MO", "BJ-OU", "BJ-PL", "BJ-ZO"], "") + self.assertEqual(list(test_request_bj["BJ"].keys()), ["BJ-AK", "BJ-AL", "BJ-AQ", "BJ-BO", "BJ-CO", "BJ-DO", "BJ-KO", "BJ-LI", "BJ-MO", "BJ-OU", "BJ-PL", "BJ-ZO"], "") + for subd in test_request_bj["BJ"]: self.assertIsNot(test_request_bj["BJ"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_request_bj["BJ"][subd]["name"])) @@ -348,12 +718,12 @@ def test_name_endpoint(self): for key in list(test_request_bj["BJ"][subd].keys()): self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) #2.) - test_request_tj = requests.get(self.name_base_url + test_name_tj, headers=self.user_agent_header).json() #Tajikistan + test_request_tj = requests.get(self.country_name_base_url + test_country_name_tj, headers=self.user_agent_header).json() #Tajikistan self.assertIsInstance(test_request_tj, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_tj))) - self.assertEqual(len(test_request_tj), 1, "Expected output object of API to be of length 1, got {}.".format(len(test_request_tj))) self.assertEqual(list(test_request_tj.keys()), ["TJ"], "Expected output object of API to contain only the TJ key, got {}.".format(list(test_request_tj.keys()))) self.assertEqual(list(test_request_tj["TJ"].keys()), ["TJ-DU", "TJ-GB", "TJ-KT", "TJ-RA", "TJ-SU"], "") + for subd in test_request_tj["TJ"]: self.assertIsNot(test_request_tj["TJ"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_request_tj["TJ"][subd]["name"])) @@ -370,14 +740,14 @@ def test_name_endpoint(self): for key in list(test_request_tj["TJ"][subd].keys()): self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) #3.) - test_request_sd = requests.get(self.name_base_url + test_name_sd, headers=self.user_agent_header).json() #Sudan + test_request_sd = requests.get(self.country_name_base_url + test_country_name_sd, headers=self.user_agent_header).json() #Sudan self.assertIsInstance(test_request_sd, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_sd))) - self.assertEqual(len(test_request_sd), 1, "Expected output object of API to be of length 1, got {}.".format(len(test_request_sd))) self.assertEqual(list(test_request_sd.keys()), ["SD"], "Expected output object of API to contain only the SD key, got {}.".format(list(test_request_sd.keys()))) self.assertEqual(list(test_request_sd["SD"].keys()), ["SD-DC", "SD-DE", "SD-DN", "SD-DS", "SD-DW", "SD-GD", "SD-GK", "SD-GZ", "SD-KA", "SD-KH", \ - "SD-KN", "SD-KS", "SD-NB", "SD-NO", "SD-NR", "SD-NW", "SD-RS", "SD-SI"], "") + "SD-KN", "SD-KS", "SD-NB", "SD-NO", "SD-NR", "SD-NW", "SD-RS", "SD-SI"], "") + for subd in test_request_sd["SD"]: self.assertIsNot(test_request_sd["SD"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_request_sd["SD"][subd]["name"])) @@ -394,12 +764,14 @@ def test_name_endpoint(self): for key in list(test_request_sd["SD"][subd].keys()): self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) #4.) - test_request_ml_ni = requests.get(self.name_base_url + test_name_ml_ni, headers=self.user_agent_header).json() #Mali and Nicaragua + test_request_ml_ni = requests.get(self.country_name_base_url + test_country_name_ml_ni, headers=self.user_agent_header).json() #Mali and Nicaragua self.assertIsInstance(test_request_ml_ni, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_ml_ni))) - self.assertEqual(len(test_request_ml_ni), 2, "Expected output object of API to be of length 2, got {}.".format(len(test_request_ml_ni))) self.assertEqual(list(test_request_ml_ni.keys()), ["ML", "NI"], "Expected output object of API to contain only the ML and NI keys, got {}.".format(list(test_request_ml_ni.keys()))) - self.assertEqual(list(test_request_ml_ni["ML"].keys()), ["ML-1", "ML-10", "ML-2", "ML-3", "ML-4", "ML-5", "ML-6", "ML-7", "ML-8", "ML-9", "ML-BKO"], "") + self.assertEqual(list(test_request_ml_ni["ML"].keys()), ["ML-1", "ML-10", "ML-2", "ML-3", "ML-4", "ML-5", "ML-6", "ML-7", "ML-8", "ML-9", "ML-BKO"], "") + self.assertEqual(list(test_request_ml_ni["NI"].keys()), + ["NI-AN", "NI-AS", "NI-BO", "NI-CA", "NI-CI", "NI-CO", "NI-ES", "NI-GR", "NI-JI", "NI-LE", "NI-MD", "NI-MN", "NI-MS", "NI-MT", "NI-NS", "NI-RI", "NI-SJ"], "") + for subd in test_request_ml_ni["ML"]: self.assertIsNot(test_request_ml_ni["ML"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_request_ml_ni["ML"][subd]["name"])) @@ -416,8 +788,6 @@ def test_name_endpoint(self): for key in list(test_request_ml_ni["ML"][subd].keys()): self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) - self.assertEqual(list(test_request_ml_ni["NI"].keys()), - ["NI-AN", "NI-AS", "NI-BO", "NI-CA", "NI-CI", "NI-CO", "NI-ES", "NI-GR", "NI-JI", "NI-LE", "NI-MD", "NI-MN", "NI-MS", "NI-MT", "NI-NS", "NI-RI", "NI-SJ"], "") for subd in test_request_ml_ni["NI"]: self.assertIsNot(test_request_ml_ni["NI"][subd]["name"], None, "Expected subdivision name to not be None, got {}.".format(test_request_ml_ni["NI"][subd]["name"])) @@ -434,38 +804,40 @@ def test_name_endpoint(self): for key in list(test_request_ml_ni["NI"][subd].keys()): self.assertIn(key, self.correct_subdivision_keys, "Attribute {} not found in list of correct attributes:\n{}.".format(key, self.correct_subdivision_keys)) #5.) - test_request_error = requests.get(self.name_base_url + test_name_error1, headers=self.user_agent_header).json() #ABCDEF + test_request_error = requests.get(self.country_name_base_url + test_country_name_error1, headers=self.user_agent_header).json() #ABCDEF self.assertIsInstance(test_request_error, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_error))) self.assertEqual(len(test_request_error), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_error))) - self.assertEqual(test_request_error["message"], 'Country name ' + test_name_error1.title() + " not found in the ISO 3166.", + self.assertEqual(test_request_error["message"], "Invalid country name input: {}.".format(test_country_name_error1.title()), "Error message does not match expected:\n{}".format(test_request_error["message"])) - self.assertEqual(test_request_error["path"], self.name_base_url + test_name_error1, + self.assertEqual(test_request_error["path"], self.country_name_base_url + test_country_name_error1, "Error path does not match expected:\n{}".format(test_request_error["path"])) self.assertEqual(test_request_error["status"], 400, "Error status does not match expected:\n{}".format(test_request_error["status"])) #6.) - test_request_error = requests.get(self.name_base_url + test_name_error2, headers=self.user_agent_header).json() #12345 + test_request_error = requests.get(self.country_name_base_url + test_country_name_error2, headers=self.user_agent_header).json() #12345 self.assertIsInstance(test_request_error, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_error))) self.assertEqual(len(test_request_error), 3, "Expected output object of API to be of length 3, got {}.".format(len(test_request_error))) - self.assertEqual(test_request_error["message"], 'Country name ' + test_name_error2.title() + " not found in the ISO 3166.", + self.assertEqual(test_request_error["message"], "Invalid country name input: {}.".format(test_country_name_error2.title()), "Error message does not match expected:\n{}".format(test_request_error["message"])) - self.assertEqual(test_request_error["path"], self.name_base_url + test_name_error2, + self.assertEqual(test_request_error["path"], self.country_name_base_url + test_country_name_error2, "Error path does not match expected:\n{}".format(test_request_error["path"])) self.assertEqual(test_request_error["status"], 400, "Error status does not match expected:\n{}".format(test_request_error["status"])) @unittest.skip("Skipping /all endpoint tests to not overload server.") def test_all_endpoint(self): - """ Test 'all' endpoint which returns all subdivision data for all ISO 3166 countries. """ + """ Test /all endpoint which returns all subdivision data for all ISO 3166 countries. """ #1.) test_request_all = requests.get(self.all_base_url, headers=self.user_agent_header).json() self.assertIsInstance(test_request_all, dict, "Expected output object of API to be type dict, got {}.".format(type(test_request_all))) self.assertEqual(len(test_request_all), 250, "Expected output object of API to be of length 250, got {}.".format(len(test_request_all))) for alpha2 in list(test_request_all.keys()): - self.assertIn(alpha2, iso3166.countries_by_alpha2, "Alpha-2 code {} not found in list of available codes.".format(alpha2)) + self.assertIn(alpha2, iso3166.countries_by_alpha2, "Alpha-2 code {} not found in list of available country codes.".format(alpha2)) + for subd in test_request_all[alpha2]: + self.assertIn(subd, self.iso3166_2_data.subdivision_codes(), "Subdivision code {} not found in list of available subdivision codes.".format(subd)) if __name__ == '__main__': #run all unit tests