diff --git a/Changes.txt b/Changes.txt index 4f81137..6debe66 100644 --- a/Changes.txt +++ b/Changes.txt @@ -1,3 +1,6 @@ +v3.4.0 Sat May 2 2026 + CLI tool extracted to separate `opencage-cli` package and repository (https://github.com/OpenCageData/opencage-cli) + v3.3.1 Sun Mar 29 2026 Validate the host domain parameter. Should be opencagedata.com subdomain or localhost Set explicit timeout for HTTP requests diff --git a/README.md b/README.md index 3d79629..e502a11 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,6 @@ You can find a [comprehensive tutorial for using this module on the OpenCage sit There are two brief video tutorials on YouTube, one [covering forward geocoding](https://www.youtube.com/watch?v=9bXu8-LPr5c), one [covering reverse geocoding](https://www.youtube.com/watch?v=u-kkE4yA-z0). -The module installs an `opencage` CLI tool for geocoding files. Check `opencage --help` or the [CLI tutorial](https://opencagedata.com/tutorials/geocode-commandline). - ## Working with AI / Agent Skill There is an [Agent Skill for working with the OpenCage Geocoding API](https://github.com/OpenCageData/opencage-skills/) which includes a reference file for developing in Python using this module. @@ -100,9 +98,6 @@ async with OpenCageGeocode(key) as geocoder: results = await geocoder.geocode_async(address) ``` -For a more complete example and links to futher tutorials on asyncronous IO see -`batch.py` in the `examples` directory. - ### Non-SSL API use If you have trouble accesing the OpenCage API with https, e.g. issues with OpenSSL @@ -125,35 +120,7 @@ If anything goes wrong, then an exception will be raised: ## Command-line batch geocoding -Use `opencage forward` or `opencage reverse` - -``` -opencage forward --help - -options: - -h, --help show this help message and exit - --api-key API_KEY Your OpenCage API key - --input FILENAME Input file name - --output FILENAME Output file name - --headers If the first row should be treated as a header row - --input-columns Comma-separated list of integers (default '1') - --add-columns Comma-separated list of output columns (default 'lat,lng,_type,_category,country_code,country,state,county,_normalized_city,postcode,road,house_number,confidence,formatted,json,status') - --workers Number of parallel geocoding requests (default 1) - --timeout Timeout in seconds (default 10) - --retries Number of retries (default 5) - --api-domain API domain (default api.opencagedata.com) - --optional-api-params - Extra parameters for each request (e.g. language=fr,no_dedupe=1) - --unordered Allow the output lines to be in different order (can be faster) - --limit Stop after this number of lines in the input - --dry-run Read the input file but no geocoding - --no-progress Display no progress bar - --quiet No progress bar and no messages - --overwrite Delete the output file first if it exists - --verbose Display debug information for each request -``` - - +The `opencage` CLI now lives in its own package. See [opencage-cli](https://github.com/OpenCageData/opencage-cli) — install with `pip install opencage-cli`. ## Copyright & License diff --git a/batch-progress.gif b/batch-progress.gif deleted file mode 100644 index 030c24c..0000000 Binary files a/batch-progress.gif and /dev/null differ diff --git a/examples/addresses.csv b/examples/addresses.csv deleted file mode 100644 index 619b94d..0000000 --- a/examples/addresses.csv +++ /dev/null @@ -1,595 +0,0 @@ -id, address -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" -1, "Via Allende 8, Cascina, Toscana, Italia" -2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia" -3, "Via Dei Salici 20, Gallarate, Lombardia, Italia" -4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia" -5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia" -6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia" -7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia" -8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia" -9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia" -10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia" -11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia" diff --git a/examples/batch.py b/examples/batch.py deleted file mode 100755 index 71502e0..0000000 --- a/examples/batch.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 - -# Example script we used between 2021 and 2023. It's now being replaced by -# the much more powerful CLI tool (see README.md file). -# -# Git version history will show how we kept adding features. Below is a -# version with less features, on purpose, for better readability. -# -# Background tutorial on async programming with Python -# https://realpython.com/async-io-python/ -# -# Requires Python 3.7 or newer. Tested with 3.8 and 3.9. -# -# Installation: -# pip3 install opencage -# - -import sys -import csv -import asyncio -from opencage.geocoder import OpenCageGeocode - -API_KEY = '' -INFILE = 'file_to_geocode.csv' -OUTFILE = 'file_geocoded.csv' -MAX_ITEMS = 100 # Set to 0 for unlimited -NUM_WORKERS = 3 # For 10 requests per second try 2-5 - -csv_writer = csv.writer(open(OUTFILE, 'w', encoding='utf8', newline='')) - -async def write_one_geocoding_result(geocoding_result, address, address_id): - if geocoding_result is not None: - geocoding_result = geocoding_result[0] - row = [ - address_id, - geocoding_result['geometry']['lat'], - geocoding_result['geometry']['lng'], - # Any of these components might be empty : - geocoding_result['components'].get('country', ''), - geocoding_result['components'].get('county', ''), - geocoding_result['components'].get('city', ''), - geocoding_result['components'].get('postcode', ''), - geocoding_result['components'].get('road', ''), - geocoding_result['components'].get('house_number', ''), - geocoding_result['confidence'], - geocoding_result['formatted'] - ] - - else: - row = [ - address_id, - 0, # not to be confused with https://en.wikipedia.org/wiki/Null_Island - 0, - '', - '', - '', - '', - '', - '', - -1, # confidence values are 1-10 (lowest to highest), use -1 for unknown - '' - ] - sys.stderr.write(f"not found, writing empty result: ${address}\n") - csv_writer.writerow(row) - - -async def geocode_one_address(address, address_id): - async with OpenCageGeocode(API_KEY) as geocoder: - geocoding_result = await geocoder.geocode_async(address) - try: - await write_one_geocoding_result(geocoding_result, address, address_id) - except Exception as e: - sys.stderr.write(e) - - - -async def run_worker(worker_name, queue): - sys.stderr.write(f"Worker ${worker_name} starts...\n") - while True: - work_item = await queue.get() - address_id = work_item['id'] - address = work_item['address'] - await geocode_one_address(address, address_id) - queue.task_done() - - - - -async def main(): - assert sys.version_info >= (3, 7), "Script requires Python 3.7+." - - ## 1. Read CSV into a Queue - ## Each work_item is an address and id. The id will be part of the output, - ## easy to add more settings. Named 'work_item' to avoid the words - ## 'address' or 'task' which are used elsewhere - ## - ## https://docs.python.org/3/library/asyncio-queue.html - ## - queue = asyncio.Queue(maxsize=MAX_ITEMS) - - csv_reader = csv.reader(open(INFILE, 'r', encoding='utf8')) - - for row in csv_reader: - work_item = {'id': row[0], 'address': row[1]} - await queue.put(work_item) - if queue.full(): - break - - sys.stderr.write(f"${queue.qsize()} work_items in queue\n") - - - ## 2. Create tasks workers. That is coroutines, each taks take work_items - ## from the queue until it's empty. Tasks run in parallel - ## - ## https://docs.python.org/3/library/asyncio-task.html#creating-tasks - ## https://docs.python.org/3/library/asyncio-task.html#coroutine - ## - sys.stderr.write(f"Creating ${NUM_WORKERS} task workers...\n") - tasks = [] - for i in range(NUM_WORKERS): - task = asyncio.create_task(run_worker(f'worker {i}', queue)) - tasks.append(task) - - - ## 3. Now workers do the geocoding - ## - sys.stderr.write("Now waiting for workers to finish processing queue...\n") - await queue.join() - - - ## 4. Cleanup - ## - for task in tasks: - task.cancel() - - sys.stderr.write("All done.\n") - - -asyncio.run(main()) diff --git a/opencage/batch.py b/opencage/batch.py deleted file mode 100644 index 9385065..0000000 --- a/opencage/batch.py +++ /dev/null @@ -1,376 +0,0 @@ -import sys -import ssl -import asyncio -import traceback -import threading -import random -import json - -from contextlib import suppress -from urllib.parse import urlencode -from tqdm import tqdm -import certifi -import backoff -from opencage.geocoder import ( - OpenCageGeocode, - OpenCageGeocodeError, - _query_for_reverse_geocoding, - floatify_latlng -) - - -class OpenCageBatchGeocoder(): - """Batch geocoder that processes CSV files using the OpenCage API. - - Reads rows from a CSV input, geocodes each address using async workers, - and writes results to a CSV output. - - Args: - options: Parsed command-line options from argparse. - """ - - def __init__(self, options): - self.options = options - self.sslcontext = ssl.create_default_context(cafile=certifi.where()) - self.user_agent_comment = 'OpenCage CLI' - self.write_counter = 1 - - def __call__(self, *args, **kwargs): - """Run the batch geocoder synchronously via asyncio.run.""" - asyncio.run(self.geocode(*args, **kwargs)) - - async def geocode(self, csv_input, csv_output): - """Process a CSV input, geocode each row, and write results. - - Args: - csv_input: CSV reader for input rows. - csv_output: CSV writer for output rows. - """ - if not self.options.dry_run: - test = await self.test_request() - if test['error']: - self.log(test['error']) - return - if test['free'] is True and self.options.workers > 1: - sys.stderr.write("Free trial account detected. Resetting number of workers to 1.\n") - self.options.workers = 1 - - if self.options.headers: - header_columns = next(csv_input, None) - if header_columns is None: - return - - queue = asyncio.Queue(maxsize=self.options.limit) - - read_warnings = await self.read_input(csv_input, queue) - - if self.options.dry_run: - if not read_warnings: - print('All good.') - return - - if self.options.headers: - csv_output.writerow(header_columns + self.options.add_columns) - - progress_bar = not (self.options.no_progress or self.options.quiet) and \ - tqdm(total=queue.qsize(), position=0, desc="Addresses geocoded", dynamic_ncols=True) - - tasks = [] - for _ in range(self.options.workers): - task = asyncio.create_task(self.worker(csv_output, queue, progress_bar)) - tasks.append(task) - - # This starts the workers and waits until all are finished - await queue.join() - - # All tasks done - for task in tasks: - task.cancel() - - if progress_bar: - progress_bar.close() - - async def test_request(self): - """Send a test geocoding request to verify the API key. - - Returns: - Dict with 'error' (None or exception) and 'free' (bool indicating - whether a free trial account is being used). - """ - try: - async with OpenCageGeocode( - self.options.api_key, - domain=self.options.api_domain, - sslcontext=self.sslcontext, - user_agent_comment=self.user_agent_comment - ) as geocoder: - result = await geocoder.geocode_async('Kendall Sq, Cambridge, MA', raw_response=True) - - free = False - with suppress(KeyError): - free = result['rate']['limit'] == 2500 - - return {'error': None, 'free': free} - except Exception as exc: - return {'error': exc} - - async def read_input(self, csv_input, queue): - """Read all rows from CSV input and add them to the work queue. - - Args: - csv_input: CSV reader for input rows. - queue: Async queue to populate with parsed input items. - - Returns: - True if any warnings were encountered while reading, False otherwise. - """ - any_warnings = False - for index, row in enumerate(csv_input): - line_number = index + 1 - - if len(row) == 0: - self.log(f"Line {line_number} - Empty line") - any_warnings = True - row = [''] - - item = await self.read_one_line(row, line_number) - if item['warnings'] is True: - any_warnings = True - await queue.put(item) - - if queue.full(): - break - - return any_warnings - - async def read_one_line(self, row, row_id): - """Parse a single CSV row into a work item for geocoding. - - Args: - row: List of column values from the CSV reader. - row_id: 1-based line number of the row in the input. - - Returns: - Dict with keys 'row_id', 'address', 'original_columns', - and 'warnings'. - """ - warnings = False - - if self.options.input_columns: - input_columns = self.options.input_columns - elif self.options.command == 'reverse': - input_columns = [1, 2] - else: - input_columns = None - - if input_columns: - address = [] - try: - for column in input_columns: - # input_columns option uses 1-based indexing - address.append(row[column - 1]) - except IndexError: - self.log(f"Line {row_id} - Missing input column {column} in {row}") - warnings = True - else: - address = row - - if self.options.command == 'reverse': - - if len(address) != 2: - self.log( - f"Line {row_id} - Expected two comma-separated values for reverse geocoding, got {address}") - else: - # _query_for_reverse_geocoding attempts to convert into numbers. We rather have it fail - # now than during the actual geocoding - try: - _query_for_reverse_geocoding(address[0], address[1]) - except BaseException: - self.log( - f"Line {row_id} - Does not look like latitude and longitude: '{address[0]}' and '{address[1]}'") - warnings = True - address = [] - - return {'row_id': row_id, 'address': ','.join(address), 'original_columns': row, 'warnings': warnings} - - async def worker(self, csv_output, queue, progress): - """Consume items from the queue and geocode each one. - - Args: - csv_output: CSV writer for output rows. - queue: Async queue of work items to process. - progress: tqdm progress bar, or False if disabled. - """ - while True: - item = await queue.get() - - try: - await self.geocode_one_address(csv_output, item['row_id'], item['address'], item['original_columns']) - - if progress: - progress.update(1) - except Exception as exc: - traceback.print_exception(exc, file=sys.stderr) - finally: - queue.task_done() - - async def geocode_one_address(self, csv_output, row_id, address, original_columns): - """Geocode a single address and write the result to the output. - - Args: - csv_output: CSV writer for output rows. - row_id: 1-based line number of the row in the input. - address: Address string (or lat,lng for reverse geocoding). - original_columns: Original CSV row columns to preserve in output. - """ - def on_backoff(details): - if not self.options.quiet: - sys.stderr.write("Backing off {wait:0.1f} seconds afters {tries} tries " - "calling function {target} with args {args} and kwargs " - "{kwargs}\n".format(**details)) - - @backoff.on_exception(backoff.expo, - asyncio.TimeoutError, - max_time=self.options.timeout, - max_tries=self.options.retries, - on_backoff=on_backoff) - async def _geocode_one_address(): - async with OpenCageGeocode( - self.options.api_key, - domain=self.options.api_domain, - sslcontext=self.sslcontext, - user_agent_comment=self.user_agent_comment - ) as geocoder: - geocoding_results = None - response = None - params = {'no_annotations': 1, 'raw_response': True, **self.options.optional_api_params} - - try: - if self.options.command == 'reverse': - if ',' in address: - lon, lat = address.split(',') - response = await geocoder.reverse_geocode_async(lon, lat, **params) - geocoding_results = floatify_latlng(response['results']) - else: - response = await geocoder.geocode_async(address, **params) - geocoding_results = floatify_latlng(response['results']) - except OpenCageGeocodeError as exc: - self.log(str(exc)) - except Exception as exc: - traceback.print_exception(exc, file=sys.stderr) - - try: - if geocoding_results is not None and len(geocoding_results): - geocoding_result = geocoding_results[0] - else: - geocoding_result = None - - if self.options.verbose: - self.log({ - 'row_id': row_id, - 'thread_id': threading.get_native_id(), - 'request': geocoder.url + '?' + urlencode(geocoder._parse_request(address, params)), - 'response': response - }) - - await self.write_one_geocoding_result( - csv_output, - row_id, - geocoding_result, - response, - original_columns - ) - except Exception as exc: - traceback.print_exception(exc, file=sys.stderr) - - await _geocode_one_address() - - async def write_one_geocoding_result( - self, - csv_output, - row_id, - geocoding_result, - raw_response, - original_columns): - """Write a single geocoding result row to the CSV output. - - Appends the requested output columns to the original CSV columns. - Rows are written in order unless the --unordered option is set. - - Args: - csv_output: CSV writer for output rows. - row_id: 1-based line number of the row in the input. - geocoding_result: First result dict from the API, or None. - raw_response: Full API response dict. - original_columns: Original CSV row columns to preserve in output. - """ - row = original_columns - - for column in self.options.add_columns: - if column == 'status': - row.append(self.deep_get_result_value(raw_response, ['status', 'message'])) - elif geocoding_result is None: - row.append('') - elif column in geocoding_result: - row.append(self.deep_get_result_value(geocoding_result, [column], '')) - elif column in geocoding_result['components']: - row.append(self.deep_get_result_value(geocoding_result, ['components', column], '')) - elif column in geocoding_result['geometry']: - row.append(self.deep_get_result_value(geocoding_result, ['geometry', column], '')) - elif column == 'FIPS': - row.append( - self.deep_get_result_value( - geocoding_result, [ - 'annotations', 'FIPS', 'county'], '')) - elif column == 'json': - row.append(json.dumps(geocoding_result, separators=(',', ':'))) # Compact JSON - else: - row.append('') - - # Enforce that row are written ordered. That means we might wait for other threads - # to finish a task and make the overall process slower. Alternative would be to - # use a second queue, or keep some results in memory. - if not self.options.unordered: - while row_id > self.write_counter: - if self.options.verbose: - self.log(f"Want to write row {row_id}, but write_counter is at {self.write_counter}") - await asyncio.sleep(random.uniform(0.01, 0.1)) - - if self.options.verbose: - self.log(f"Writing row {row_id}") - csv_output.writerow(row) - self.write_counter = self.write_counter + 1 - - def log(self, message): - """Write a message to stderr unless quiet mode is enabled. - - Args: - message: Message string to display. - """ - if not self.options.quiet: - sys.stderr.write(f"{message}\n") - - def deep_get_result_value(self, data, keys, default=None): - """Retrieve a nested value from a dict using a list of keys. - - Args: - data: Dict to traverse. - keys: List of keys to follow in sequence. - default: Value to return if any key is missing. - - Returns: - The nested value, or default if the path doesn't exist. - - Example: - >>> data = {'status': {'code': 200, 'message': 'OK'}} - >>> self.deep_get_result_value(data, ['status', 'message']) - 'OK' - >>> self.deep_get_result_value(data, ['missing', 'key'], '') - '' - """ - for key in keys: - if isinstance(data, dict): - data = data.get(key, default) - else: - return default - return data diff --git a/opencage/command_line.py b/opencage/command_line.py deleted file mode 100644 index 3ffee99..0000000 --- a/opencage/command_line.py +++ /dev/null @@ -1,234 +0,0 @@ -import argparse -import sys -import io -from pathlib import Path -import re -import csv - -from opencage.batch import OpenCageBatchGeocoder -from opencage.version import __version__ - - -def main(args=sys.argv[1:]): - """Entry point for the OpenCage CLI. - - Args: - args: Command-line arguments (defaults to sys.argv[1:]). - """ - options = parse_args(args) - - geocoder = OpenCageBatchGeocoder(options) - - with options.input as input_filename: - with (io.StringIO() if options.dry_run else open(options.output, 'x', encoding='utf-8')) as output_io: - reader = csv.reader(input_filename, strict=True, skipinitialspace=True) - writer = csv.writer(output_io) - - geocoder(csv_input=reader, csv_output=writer) - - -def parse_args(args): - """Parse and validate command-line arguments. - - Args: - args: List of command-line argument strings. - - Returns: - Parsed argparse.Namespace with all options set. - """ - if len(args) == 0: - print("To display help use 'opencage -h', 'opencage forward -h' or 'opencage reverse -h'", file=sys.stderr) - sys.exit(1) - - parser = argparse.ArgumentParser(description=f'OpenCage CLI {__version__}') - parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}') - - subparsers = parser.add_subparsers(dest='command') - subparsers.required = True - - subparser_forward = subparsers.add_parser( - 'forward', help="Forward geocode a file (input is address, add coordinates)") - subparser_reverse = subparsers.add_parser( - 'reverse', help="Reverse geocode a file (input is coordinates, add full address)") - - for subparser in [subparser_forward, subparser_reverse]: - subparser.add_argument("--api-key", required=True, type=api_key_type, help="Your OpenCage API key") - subparser.add_argument( - "--input", - required=True, - type=argparse.FileType( - 'r', - encoding='utf-8'), - help="Input file name", - metavar='FILENAME') - subparser.add_argument( - "--output", - required=True, - type=str, - help="Output file name", - metavar='FILENAME') - - add_optional_arguments(subparser) - - options = parser.parse_args(args) - - if Path(options.output).exists() and not options.dry_run: - if options.overwrite: - Path(options.output).unlink() - else: - print( - f"Error: The output file '{options.output}' already exists. You can add --overwrite to your command.", - file=sys.stderr) - sys.exit(1) - - if 0 in options.input_columns: - print("Error: A column 0 in --input-columns does not exist. The lowest possible number is 1.", file=sys.stderr) - sys.exit(1) - - return options - - -def add_optional_arguments(parser): - """Add optional arguments shared by forward and reverse subcommands. - - Args: - parser: argparse subparser to add arguments to. - - Returns: - The parser with arguments added. - """ - parser.add_argument( - "--headers", - action="store_true", - help="If the first row should be treated as a header row") - default_input_cols = '1,2' if re.match(r'.*reverse', parser.prog) else '1' - parser.add_argument( - "--input-columns", - type=comma_separated_type(int), - default=default_input_cols, - help=f"Comma-separated list of integers (default '{default_input_cols}')", - metavar='') - default_add_cols = ( - 'lat,lng,_type,_category,country_code,country,state,county,_normalized_city,' - 'postcode,road,house_number,confidence,formatted' - ) - parser.add_argument( - "--add-columns", - type=comma_separated_type(str), - default=default_add_cols, - help=f"Comma-separated list of output columns (default '{default_add_cols}')", - metavar='') - parser.add_argument("--workers", type=ranged_type(int, 1, 20), default=1, - help="Number of parallel geocoding requests (default 1)", metavar='') - parser.add_argument("--timeout", type=ranged_type(int, 1, 60), default=10, - help="Timeout in seconds (default 10)", metavar='') - parser.add_argument("--retries", type=ranged_type(int, 1, 60), default=10, - help="Number of retries (default 5)", metavar='') - parser.add_argument("--api-domain", type=str, default="api.opencagedata.com", - help="API domain (default api.opencagedata.com)", metavar='') - parser.add_argument("--optional-api-params", type=comma_separated_dict_type, default="", - help="Extra parameters for each request (e.g. language=fr,no_dedupe=1)", metavar='') - parser.add_argument( - "--limit", - type=int, - default=0, - help="Stop after this number of lines in the input", - metavar='') - parser.add_argument( - "--unordered", - action="store_true", - help="Allow the output lines to be in different order (can be faster)") - parser.add_argument("--dry-run", action="store_true", help="Read the input file but no geocoding") - parser.add_argument("--no-progress", action="store_true", help="Display no progress bar") - parser.add_argument("--quiet", action="store_true", help="No progress bar and no messages") - parser.add_argument("--overwrite", action="store_true", help="Delete the output file first if it exists") - parser.add_argument("--verbose", action="store_true", help="Display debug information for each request") - - return parser - - -def api_key_type(apikey): - """Validate an OpenCage API key format. - - Expects a 32-character lowercase hex string, optionally prefixed - with ``oc_gc_`` (e.g. ``oc_gc_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d`` - or ``1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d``). - - Args: - apikey: API key string to validate. - - Returns: - The validated API key string. - - Raises: - argparse.ArgumentTypeError: If the key doesn't match the expected format. - """ - pattern = re.compile(r"^(oc_gc_)?[0-9a-f]{32}$") - - if not pattern.match(apikey): - raise argparse.ArgumentTypeError("invalid API key") - - return apikey - - -def ranged_type(value_type, min_value, max_value): - """Create an argparse type function that enforces a value range. - - Args: - value_type: Type to convert the argument to (e.g. int, float). - min_value: Minimum allowed value (inclusive). - max_value: Maximum allowed value (inclusive). - - Returns: - A type-checking function suitable for argparse's type parameter. - """ - def range_checker(arg: str): - try: - f = value_type(arg) - except ValueError as exc: - raise argparse.ArgumentTypeError(f'must be a valid {value_type}') from exc - if f < min_value or f > max_value: - raise argparse.ArgumentTypeError(f'must be within [{min_value}, {max_value}]') - return f - - # Return function handle to checking function - return range_checker - - -def comma_separated_type(value_type): - """Create an argparse type function that parses comma-separated values. - - Args: - value_type: Type to convert each element to (e.g. int, str). - - Returns: - A type-checking function suitable for argparse's type parameter. - """ - def comma_separated(arg: str): - if not arg: - return [] - - return [value_type(x) for x in arg.split(',')] - - return comma_separated - - -def comma_separated_dict_type(arg): - """Parse a comma-separated list of key=value pairs into a dict. - - Args: - arg: String like "key1=val1,key2=val2". - - Returns: - Dict of parsed key-value pairs, or empty dict if arg is empty. - - Raises: - argparse.ArgumentTypeError: If the string is not valid key=value format. - """ - if not arg: - return {} - - try: - return dict([x.split('=') for x in arg.split(',')]) - except ValueError as exc: - raise argparse.ArgumentTypeError("must be a valid comma separated list of key=value pairs") from exc diff --git a/opencage/version.py b/opencage/version.py index 310a75d..f631007 100644 --- a/opencage/version.py +++ b/opencage/version.py @@ -1 +1 @@ -__version__ = '3.3.1' +__version__ = '3.4.0' diff --git a/pyproject.toml b/pyproject.toml index b182c35..46fdcfd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,8 +29,6 @@ classifiers = [ dependencies = [ "Requests>=2.31.0", "backoff>=2.2.1", - "tqdm>=4.66.4", - "certifi>=2024.07.04", "aiohttp>=3.10.5", ] @@ -43,9 +41,6 @@ dev = [ "pytest-cov>=4.1.0", ] -[project.scripts] -opencage = "opencage.command_line:main" - [project.urls] Repository = "https://github.com/OpenCageData/python-opencage-geocoder" Download = "https://github.com/OpenCageData/python-opencage-geocoder" diff --git a/test/cli/test_cli_args.py b/test/cli/test_cli_args.py deleted file mode 100644 index 43011f4..0000000 --- a/test/cli/test_cli_args.py +++ /dev/null @@ -1,200 +0,0 @@ -import pathlib -import pytest -from opencage.version import __version__ - -from opencage.command_line import parse_args - - -@pytest.fixture(autouse=True) -def around(): - yield - try: - pathlib.Path("test/fixtures/output.csv").unlink() - except FileNotFoundError: - pass - - -def assert_parse_args_error(args, message, capfd): - with pytest.raises(SystemExit): - parse_args(args) - - _, err = capfd.readouterr() - assert message in err - - -def test_required_arguments(capfd): - assert_parse_args_error( - [], - 'To display help use', - capfd - ) - - -def test_invalid_command(capfd): - assert_parse_args_error( - [ - "singasong" - ], - 'argument command: invalid choice', - capfd - ) - - -def test_version_number(capfd): - with pytest.raises(SystemExit): - parse_args(['--version']) - out, _ = capfd.readouterr() - - assert __version__ in out - - -def test_invalid_api_key(capfd): - assert_parse_args_error( - [ - "forward", - "--api-key", "invalid", - "--input", "test/fixtures/input.txt", - "--output", "test/fixtures/output.csv" - ], - 'invalid API key', - capfd - ) - - -def test_existing_output_file(capfd): - assert_parse_args_error( - [ - "forward", - "--api-key", "oc_gc_12345678901234567890123456789012", - "--input", "test/fixtures/input.txt", - "--output", "test/fixtures/input.txt" - ], - 'already exists', - capfd - ) - - -def test_argument_range(capfd): - assert_parse_args_error( - [ - "forward", - "--api-key", "oc_gc_12345678901234567890123456789012", - "--input", "test/fixtures/input.txt", - "--output", "test/fixtures/output.csv", - "--workers", "200" - ], - 'must be within [1, 20]', - capfd - ) - - -def test_zero_based_list(capfd): - assert_parse_args_error( - [ - "forward", - "--api-key", "oc_gc_12345678901234567890123456789012", - "--input", "test/fixtures/input.txt", - "--output", "test/fixtures/output.csv", - "--input-columns", "0,1,2" - ], - 'The lowest possible number is 1', - capfd - ) - - -def test_full_argument_list(): - args = parse_args([ - "reverse", - "--api-key", "oc_gc_12345678901234567890123456789012", - "--input", "test/fixtures/input.txt", - "--output", "test/fixtures/output.csv", - "--headers", - "--input-columns", "1,2", - "--add-columns", "city,postcode", - "--limit", "4", - "--workers", "3", - "--timeout", "2", - "--retries", "1", - "--dry-run", - "--unordered", - "--api-domain", "bulk.opencagedata.com", - "--optional-api-params", "extra=1", - "--no-progress", - "--quiet" - ]) - - assert args.command == "reverse" - assert args.api_key == "oc_gc_12345678901234567890123456789012" - assert args.input.name == "test/fixtures/input.txt" - assert args.output == "test/fixtures/output.csv" - assert args.headers is True - assert args.input_columns == [1, 2] - assert args.add_columns == ["city", "postcode"] - assert args.limit == 4 - assert args.workers == 3 - assert args.timeout == 2 - assert args.retries == 1 - assert args.dry_run is True - assert args.unordered is True - assert args.api_domain == "bulk.opencagedata.com" - assert args.optional_api_params == {"extra": "1"} - assert args.no_progress is True - assert args.quiet is True - - -def test_defaults(): - args = parse_args([ - "forward", - "--api-key", "12345678901234567890123456789012", - "--input", "test/fixtures/input.txt", - "--output", "test/fixtures/output.csv" - ]) - - assert args.command == "forward" - assert args.limit == 0 - assert args.headers is False - assert args.input_columns == [1] - assert args.add_columns == [ - "lat", - "lng", - "_type", - "_category", - "country_code", - "country", - "state", - "county", - "_normalized_city", - "postcode", - "road", - "house_number", - "confidence", - "formatted"] - assert args.workers == 1 - assert args.timeout == 10 - assert args.retries == 10 - assert args.dry_run is False - assert args.unordered is False - assert args.api_domain == "api.opencagedata.com" - assert args.optional_api_params == {} - assert args.no_progress is False - assert args.quiet is False - - -def test_reverse_input_columns(): - args = parse_args([ - "reverse", - "--api-key", "12345678901234567890123456789012", - "--input", "test/fixtures/input.txt", - "--output", "test/fixtures/output.csv" - ]) - assert args.input_columns == [1, 2] - - args = parse_args([ - "reverse", - "--api-key", "12345678901234567890123456789012", - "--input", "test/fixtures/input.txt", - "--output", "test/fixtures/output.csv", - "--input-columns", '2,1' - ]) - - assert args.input_columns == [2, 1] diff --git a/test/cli/test_cli_run.py b/test/cli/test_cli_run.py deleted file mode 100644 index 89fbb27..0000000 --- a/test/cli/test_cli_run.py +++ /dev/null @@ -1,193 +0,0 @@ -import pathlib -import os -import pytest - -from opencage.command_line import main - -# NOTE: Testing keys https://opencagedata.com/api#testingkeys -TEST_APIKEY_200 = '6d0e711d72d74daeb2b0bfd2a5cdfdba' # always returns same address -TEST_APIKEY_401 = '11111111111111111111111111111111' # invalid key - - -@pytest.fixture(autouse=True) -def around(): - yield - try: - pathlib.Path("test/fixtures/cli/output.csv").unlink() - except FileNotFoundError: - pass - - -def assert_output(path, length, lines): - assert pathlib.Path(path).exists() - - with open(path, "r", encoding="utf-8") as f: - actual = f.readlines() - # print(actual, file=sys.stderr) - assert len(actual) == length - - for i, expected in enumerate(lines): - assert actual[i].strip() == expected - - -def test_forward(): - main([ - "forward", - "--api-key", TEST_APIKEY_200, - "--input", "test/fixtures/cli/forward.csv", - "--output", "test/fixtures/cli/output.csv", - "--input-columns", "2,3,4", - "--add-columns", "country_code,country,postcode,city" - ]) - - assert_output( - path="test/fixtures/cli/output.csv", - length=3, - lines=['Rathausmarkt 1,Hamburg,20095,Germany,de,Germany,48153,Münster'] - ) - - -def test_reverse(): - main([ - "reverse", - "--api-key", TEST_APIKEY_200, - "--input", "test/fixtures/cli/reverse.csv", - "--output", "test/fixtures/cli/output.csv", - "--add-columns", "country_code,country,postcode" - ]) - - assert_output( - path="test/fixtures/cli/output.csv", - length=1, - lines=['51.9526622,7.6324709,de,Germany,48153'] - ) - - -def test_headers(): - main([ - "forward", - "--api-key", TEST_APIKEY_200, - "--input", "test/fixtures/cli/forward_with_headers.csv", - "--output", "test/fixtures/cli/output.csv", - "--input-columns", "1,2,3,4", - "--headers", - "--add-columns", "lat,lng,postcode" - ]) - - assert_output( - path="test/fixtures/cli/output.csv", - length=4, - lines=[ - 'street and number,town,postcode,country,lat,lng,postcode', - 'Rathausmarkt 1,Hamburg,20095,Germany,51.9526622,7.6324709,48153' - ] - ) - - -def test_input_errors(capfd): - main([ - "reverse", - "--api-key", TEST_APIKEY_200, - "--input", "test/fixtures/cli/reverse_with_errors.csv", - "--output", "test/fixtures/cli/output.csv", - "--add-columns", "country_code,postcode", - "--no-progress" - ]) - - _, err = capfd.readouterr() - # assert err == '' - assert err.count("\n") == 7 - assert "Line 1 - Missing input column 2 in ['50.101010']" in err - assert "Line 1 - Expected two comma-separated values for reverse geocoding, got ['50.101010']" in err - assert "Line 3 - Empty line" in err - assert "Line 3 - Missing input column 2 in ['']" in err - assert "Line 3 - Expected two comma-separated values for reverse geocoding, got ['']" in err - assert "Line 4 - Does not look like latitude and longitude: 'a' and 'b'" in err - - assert_output( - path="test/fixtures/cli/output.csv", - length=4, - lines=[ - '50.101010,,', - '-100,60.1,,', - ',,', - 'a,b,,' - ] - ) - - -def test_empty_result(): - # 'NOWHERE-INTERESTING' is guaranteed to return no result - # https://opencagedata.com/api#testingkeys - main([ - "forward", - "--api-key", TEST_APIKEY_200, - "--input", "test/fixtures/cli/forward_noresult.csv", - "--output", "test/fixtures/cli/output.csv", - "--input-columns", "2", - "--headers", - "--verbose", - "--add-columns", "lat,lng,postcode" - ]) - - assert_output( - path="test/fixtures/cli/output.csv", - length=2, - lines=[ - 'id,full_address,lat,lng,postcode', - '123,NOWHERE-INTERESTING,,,' - ] - ) - - -def test_invalid_api_key(capfd): - main([ - "forward", - "--api-key", TEST_APIKEY_401, - "--input", "test/fixtures/cli/forward_with_headers.csv", - "--output", "test/fixtures/cli/output.csv" - ]) - - _, err = capfd.readouterr() - assert 'Your API key is not authorized' in err - - -def test_dryrun(capfd): - main([ - "forward", - "--api-key", TEST_APIKEY_200, - "--input", "test/fixtures/cli/forward_with_headers.csv", - "--output", "test/fixtures/cli/output.csv", - "--dry-run" - ]) - - assert not os.path.isfile("test/fixtures/cli/output.csv") - - out, _ = capfd.readouterr() - assert out.count("\n") == 1 - assert "All good." in out - - -def test_invalid_domain(capfd): - main([ - "forward", - "--api-key", TEST_APIKEY_200, - "--input", "test/fixtures/cli/forward.csv", - "--output", "test/fixtures/cli/output.csv", - "--api-domain", "invalid73585348.opencagedata.com" - ]) - - _, err = capfd.readouterr() - assert 'Cannot connect to host' in err - - # with dry-run no request will be made - main([ - "forward", - "--api-key", TEST_APIKEY_200, - "--input", "test/fixtures/cli/forward.csv", - "--output", "test/fixtures/cli/output.csv", - "--api-domain", "invalid73585348.opencagedata.com", - "--dry-run" - ]) - _, err = capfd.readouterr() - assert err == '' diff --git a/test/fixtures/cli/forward.csv b/test/fixtures/cli/forward.csv deleted file mode 100644 index 64283bd..0000000 --- a/test/fixtures/cli/forward.csv +++ /dev/null @@ -1,3 +0,0 @@ -"Rathausmarkt 1",Hamburg,20095,Germany -"10 Downing Street",London,"SW1A 2AA","United Kingdom" -"C/ de Mallorca 401",Barcelona,08013,Spain \ No newline at end of file diff --git a/test/fixtures/cli/forward_noresult.csv b/test/fixtures/cli/forward_noresult.csv deleted file mode 100644 index 1d4dceb..0000000 --- a/test/fixtures/cli/forward_noresult.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,full_address -123,NOWHERE-INTERESTING \ No newline at end of file diff --git a/test/fixtures/cli/forward_with_headers.csv b/test/fixtures/cli/forward_with_headers.csv deleted file mode 100644 index feaa784..0000000 --- a/test/fixtures/cli/forward_with_headers.csv +++ /dev/null @@ -1,4 +0,0 @@ -street and number,town,postcode,country -"Rathausmarkt 1",Hamburg,20095,Germany -"10 Downing Street",London,"SW1A 2AA","United Kingdom" -"C/ de Mallorca 401",Barcelona,08013,Spain \ No newline at end of file diff --git a/test/fixtures/cli/output.csv2 b/test/fixtures/cli/output.csv2 deleted file mode 100644 index 30942f9..0000000 --- a/test/fixtures/cli/output.csv2 +++ /dev/null @@ -1,2 +0,0 @@ -id,full_address,lat,lng,postcode -123,NOWHERE-INTERESTING,51.9526622,7.6324709,48153 diff --git a/test/fixtures/cli/reverse.csv b/test/fixtures/cli/reverse.csv deleted file mode 100644 index 95ab1fd..0000000 --- a/test/fixtures/cli/reverse.csv +++ /dev/null @@ -1 +0,0 @@ -51.9526622,7.6324709 \ No newline at end of file diff --git a/test/fixtures/cli/reverse_with_errors.csv b/test/fixtures/cli/reverse_with_errors.csv deleted file mode 100644 index e85bebd..0000000 --- a/test/fixtures/cli/reverse_with_errors.csv +++ /dev/null @@ -1,4 +0,0 @@ -50.101010 --100,60.1 - -a,b \ No newline at end of file diff --git a/test/fixtures/input.txt b/test/fixtures/input.txt deleted file mode 100644 index e69de29..0000000 diff --git a/test/test_batch.py b/test/test_batch.py deleted file mode 100644 index 3139dd5..0000000 --- a/test/test_batch.py +++ /dev/null @@ -1,25 +0,0 @@ -from opencage.batch import OpenCageBatchGeocoder - -batch = OpenCageBatchGeocoder({}) - - -def test_deep_get_result_value(): - result = { - 'annotations': { - 'FIPS': { - 'state': 'CA' - } - }, - 'components': { - 'street': 'Main Road' - } - } - - assert batch.deep_get_result_value(result, ['hello', 'world']) is None - - assert batch.deep_get_result_value(result, ['components', 'street']) == 'Main Road' - assert batch.deep_get_result_value(result, ['components', 'city']) is None - assert batch.deep_get_result_value(result, ['components', 'city'], '') == '' - - assert batch.deep_get_result_value([], ['hello', 'world']) is None - assert batch.deep_get_result_value(None, ['hello', 'world']) is None