├── .coveragerc ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.md ├── deploy └── deploy_to_pypi.sh ├── dev-requirements.txt ├── logos ├── 128x128.png ├── 180x180.png ├── 32x32.png ├── LOGOS-LICENSE └── fullres.png ├── pyowm ├── __init__.py ├── __version__.py ├── agroapi10 │ ├── __init__.py │ ├── agro_manager.py │ ├── enums.py │ ├── imagery.py │ ├── polygon.py │ ├── search.py │ ├── soil.py │ └── uris.py ├── airpollutionapi30 │ ├── __init__.py │ ├── airpollution_client.py │ ├── airpollution_manager.py │ ├── airstatus.py │ ├── coindex.py │ ├── no2index.py │ ├── ozone.py │ ├── so2index.py │ └── uris.py ├── alertapi30 │ ├── __init__.py │ ├── alert.py │ ├── alert_manager.py │ ├── condition.py │ ├── enums.py │ ├── trigger.py │ └── uris.py ├── commons │ ├── __init__.py │ ├── cityidregistry.py │ ├── cityids │ │ ├── __init__.py │ │ └── cities.db.bz2 │ ├── databoxes.py │ ├── enums.py │ ├── exceptions.py │ ├── http_client.py │ ├── image.py │ ├── tile.py │ └── uris.py ├── config.py ├── constants.py ├── geocodingapi10 │ ├── __init__.py │ └── geocoding_manager.py ├── owm.py ├── stationsapi30 │ ├── __init__.py │ ├── buffer.py │ ├── measurement.py │ ├── persistence_backend.py │ ├── station.py │ ├── stations_manager.py │ └── uris.py ├── tiles │ ├── __init__.py │ ├── enums.py │ ├── tile_manager.py │ └── uris.py ├── utils │ ├── __init__.py │ ├── config.py │ ├── decorators.py │ ├── formatting.py │ ├── geo.py │ ├── measurables.py │ ├── strings.py │ ├── timestamps.py │ └── weather.py ├── uvindexapi30 │ ├── __init__.py │ ├── uris.py │ ├── uv_client.py │ ├── uvindex.py │ └── uvindex_manager.py └── weatherapi30 │ ├── __init__.py │ ├── forecast.py │ ├── forecaster.py │ ├── historian.py │ ├── location.py │ ├── national_weather_alert.py │ ├── observation.py │ ├── one_call.py │ ├── stationhistory.py │ ├── uris.py │ ├── weather.py │ ├── weather_manager.py │ └── weathercoderegistry.py ├── readthedocs.yml ├── requirements.txt ├── scripts ├── fill_entity_template.py ├── generate_city_id_files.py ├── generate_new_entity.sh ├── generate_pypi_dist.sh ├── generate_sphinx_docs.sh ├── new_entity.template.txt ├── publish_to_pypi.sh └── update-pipfilelock.sh ├── setup.py ├── sphinx ├── Makefile ├── conf.py ├── contributing.md ├── favicon.ico ├── index.rst ├── logo.png ├── make.bat ├── pyowm.agroapi10.rst ├── pyowm.airpollutionapi30.rst ├── pyowm.alertapi30.rst ├── pyowm.commons.rst ├── pyowm.geocodingapi10.rst ├── pyowm.rst ├── pyowm.stationsapi30.rst ├── pyowm.tiles.rst ├── pyowm.utils.rst ├── pyowm.uvindexapi30.rst ├── pyowm.weatherapi25.rst └── v3 │ ├── agro-api-usage-examples.md │ ├── air-pollution-api-usage-examples.md │ ├── alerts-api-usage-examples.md │ ├── city-id-registry-examples.md │ ├── code-recipes.md │ ├── exceptions.md │ ├── faq.md │ ├── geocoding-api-usage-examples.md │ ├── global-pyowm-usage-examples.md │ ├── maintenance-streams-timelines.md │ ├── map-tiles-client-usage-examples.md │ ├── migration-guide-pyowm-v2-to-v3.md │ ├── pyowm-configuration-description.md │ ├── stations-api-usage-examples.md │ ├── utilities-usage-examples.md │ ├── uv-api-usage-examples.md │ └── weather-api-usage-examples.md ├── tests ├── __init__.py ├── integration │ ├── __init__.py │ ├── agroapi10 │ │ ├── __init__.py │ │ ├── test_integration_polygons_api_subset.py │ │ ├── test_integration_satellite_imagery_download.py │ │ ├── test_integration_satellite_imagery_search.py │ │ ├── test_integration_satellite_imagery_stats.py │ │ └── test_integration_soil_data.py │ ├── airpollutionapi30 │ │ ├── __init__.py │ │ └── test_integration_pollutionapi30.py │ ├── alertapi30 │ │ ├── __init__.py │ │ └── test_integration_alertapi30.py │ ├── commons │ │ ├── 256x256.png │ │ ├── __init__.py │ │ ├── test_cityidregistry.py │ │ ├── test_http_client.py │ │ └── test_image.py │ ├── geocodingapi10 │ │ ├── __init__.py │ │ └── test_integration_geocodingapi10.py │ ├── stationsapi30 │ │ ├── __init__.py │ │ ├── measurements.json │ │ ├── test_integration_stationsapi30.py │ │ └── test_persistence_backends_read_fs.py │ ├── tiles │ │ ├── __init__.py │ │ └── test_integration_tile_manager.py │ ├── tox.ini │ ├── utils │ │ ├── __init__.py │ │ ├── non_json │ │ ├── test_config.json │ │ ├── test_default_config.py │ │ └── test_integration_config.py │ ├── uvindexapi30 │ │ ├── __init__.py │ │ └── test_integration_uvindexapi30.py │ └── weatherapi25 │ │ ├── __init__.py │ │ └── test_integration.py ├── local_installation_test.sh ├── proxy │ ├── __init__.py │ ├── proxy_http.json │ ├── proxy_socks.json │ ├── selfsigned.crt │ ├── test_integration_proxy.py │ └── tox.ini ├── pypi_installation_test.sh ├── run_integration_tests.sh ├── run_proxy_tests.sh ├── run_unit_tests.sh ├── test_readme.py └── unit │ ├── __init__.py │ ├── agroapi10 │ ├── __init__.py │ ├── test_agro_manager.py │ ├── test_enums.py │ ├── test_imagery.py │ ├── test_polygon.py │ ├── test_search.py │ └── test_soil.py │ ├── airpollutionapi30 │ ├── __init__.py │ ├── test_airpollution_client.py │ ├── test_airpollution_manager.py │ ├── test_airstatus.py │ ├── test_coindex.py │ ├── test_no2index.py │ ├── test_ozone.py │ └── test_so2index.py │ ├── alertapi30 │ ├── __init__.py │ ├── test_alert.py │ ├── test_alert_manager.py │ ├── test_condition.py │ ├── test_enums.py │ ├── test_parsers.py │ └── test_trigger.py │ ├── commons │ ├── __init__.py │ ├── test_databoxes.py │ ├── test_enums.py │ ├── test_http_client.py │ └── test_tile.py │ ├── geocodingapi10 │ ├── __init__.py │ └── geocoding_manager.py │ ├── stationsapi30 │ ├── __init__.py │ ├── test_buffer.py │ ├── test_measurement.py │ ├── test_persistence_backend.py │ ├── test_station.py │ └── test_stations_manager.py │ ├── test_owm.py │ ├── tiles │ ├── __init__.py │ └── test_tile_manager.py │ ├── utils │ ├── __init__.py │ ├── test_config.py │ ├── test_decorators.py │ ├── test_formatting.py │ ├── test_geo.py │ ├── test_measurables.py │ ├── test_strings.py │ ├── test_timestamps.py │ └── test_weather.py │ ├── uvindexapi30 │ ├── __init__.py │ ├── test_uv_client.py │ ├── test_uvindex.py │ └── test_uvindex_manager.py │ └── weatherapi30 │ ├── __init__.py │ ├── json_test_responses.py │ ├── test_forecast.py │ ├── test_forecaster.py │ ├── test_historian.py │ ├── test_location.py │ ├── test_national_weather_alert.py │ ├── test_observation.py │ ├── test_one_call.py │ ├── test_stationhistory.py │ ├── test_weather.py │ ├── test_weather_manager.py │ └── test_weathercoderegistry.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = pyowm 4 | 5 | [report] 6 | exclude_lines = 7 | # Have to re-enable the standard pragma 8 | pragma: no cover 9 | 10 | # Don't complain about missing debug-only code: 11 | def __repr__ 12 | def __str__ 13 | 14 | # Don't complain if tests don't hit defensive assertion code: 15 | raise AssertionError 16 | raise NotImplementedError 17 | 18 | # Don't complain if non-runnable code isn't run: 19 | if 0: 20 | if False: 21 | if __name__ == __main__: 22 | 23 | # Exclude dump methods 24 | def to_JSON 25 | def to_XML 26 | def _to_DOM 27 | 28 | ignore_errors = True -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | build/* 4 | bin/* 5 | dist/* 6 | pyowm.egg-info/* 7 | sphinx/_build/* 8 | .settings/* 9 | .project 10 | .pydevproject 11 | .idea/* 12 | .tox/* 13 | .coverage 14 | tests/integration/.tox/* 15 | tests/proxy/.tox/* 16 | .cache 17 | .eggs/ 18 | htmlcov/* 19 | venv 20 | 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wiki"] 2 | path = wiki 3 | url = https://github.com/csparpa/pyowm.wiki.git 4 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: sphinx/conf.py 11 | 12 | # Optionally build your docs in additional formats such as PDF 13 | formats: 14 | - htmlzip 15 | - pdf 16 | 17 | python: 18 | version: 3.7 19 | install: 20 | - requirements: dev-requirements.txt 21 | - requirements: requirements.txt 22 | - method: pip 23 | path: . 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | python: 4 | - "3.7" 5 | - "3.8" 6 | - "3.9-dev" 7 | script: tox 8 | after_success: 9 | - coveralls 10 | 11 | notifications: 12 | email: 13 | on_failure: change 14 | env: 15 | global: 16 | - TEST_PYPI_URL=https://test.pypi.org/legacy/ 17 | - PYPI_URL=https://upload.pypi.org/legacy/ 18 | install: 19 | - pip install --upgrade pip && pip install -r requirements.txt && pip install --upgrade -r dev-requirements.txt && pip install . 20 | cache: pip 21 | 22 | stages: 23 | - test 24 | - "Local Installation Test" 25 | - "Coverage" 26 | - "Build docs" 27 | - "Deploy to PyPI" 28 | - "PyPI Installation Test" 29 | 30 | jobs: 31 | include: 32 | - &local_installation_test 33 | stage: "Local Installation Test" 34 | python: "3.7" 35 | script: bash tests/local_installation_test.sh 36 | - <<: *local_installation_test 37 | python: "3.8" 38 | - <<: *local_installation_test 39 | python: "3.9-dev" 40 | 41 | - &coverage 42 | stage: "Coverage" 43 | python: "3.7" 44 | script: coverage run --rcfile=.coveragerc setup.py test -s tests.unit 45 | - <<: *coverage 46 | python: "3.8" 47 | - <<: *coverage 48 | python: "3.9-dev" 49 | 50 | - stage: "Build docs" 51 | script: cd sphinx && make clean && make html 52 | 53 | - &deploy_to_pypi 54 | stage: "Deploy to PyPI" 55 | python: "3.7" 56 | script: bash deploy/deploy_to_pypi.sh 57 | - <<: *deploy_to_pypi 58 | python: "3.8" 59 | - <<: *deploy_to_pypi 60 | python: "3.9-dev" 61 | 62 | - &pypi_installation_test 63 | stage: "PyPI Installation Test" 64 | python: "3.7" 65 | if: branch = master 66 | script: bash tests/pypi_installation_test.sh 67 | - <<: *pypi_installation_test 68 | python: "3.8" 69 | - <<: *pypi_installation_test 70 | python: "3.9-dev" 71 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | PyOWM adheres to the [Contributor Covenant Code of Conduct v1.4](http://contributor-covenant.org), 2 | which is reported below. 3 | 4 | 5 | # Contributor Covenant Code of Conduct 6 | 7 | ## Our Pledge 8 | 9 | In the interest of fostering an open and welcoming environment, we as 10 | contributors and maintainers pledge to making participation in our project and 11 | our community a harassment-free experience for everyone, regardless of age, body 12 | size, disability, ethnicity, gender identity and expression, level of experience, 13 | nationality, personal appearance, race, religion, or sexual identity and 14 | orientation. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to creating a positive environment 19 | include: 20 | 21 | * Using welcoming and inclusive language 22 | * Being respectful of differing viewpoints and experiences 23 | * Gracefully accepting constructive criticism 24 | * Focusing on what is best for the community 25 | * Showing empathy towards other community members 26 | 27 | Examples of unacceptable behavior by participants include: 28 | 29 | * The use of sexualized language or imagery and unwelcome sexual attention or 30 | advances 31 | * Trolling, insulting/derogatory comments, and personal or political attacks 32 | * Public or private harassment 33 | * Publishing others' private information, such as a physical or electronic 34 | address, without explicit permission 35 | * Other conduct which could reasonably be considered inappropriate in a 36 | professional setting 37 | 38 | ## Our Responsibilities 39 | 40 | Project maintainers are responsible for clarifying the standards of acceptable 41 | behavior and are expected to take appropriate and fair corrective action in 42 | response to any instances of unacceptable behavior. 43 | 44 | Project maintainers have the right and responsibility to remove, edit, or 45 | reject comments, commits, code, wiki edits, issues, and other contributions 46 | that are not aligned to this Code of Conduct, or to ban temporarily or 47 | permanently any contributor for other behaviors that they deem inappropriate, 48 | threatening, offensive, or harmful. 49 | 50 | ## Scope 51 | 52 | This Code of Conduct applies both within project spaces and in public spaces 53 | when an individual is representing the project or its community. Examples of 54 | representing a project or community include using an official project e-mail 55 | address, posting via an official social media account, or acting as an appointed 56 | representative at an online or offline event. Representation of a project may be 57 | further defined and clarified by project maintainers. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported by contacting the project team at `pyowmlib@gmail.com`. All 63 | complaints will be reviewed and investigated and will result in a response that 64 | is deemed necessary and appropriate to the circumstances. The project team is 65 | obligated to maintain confidentiality with regard to the reporter of an incident. 66 | Further details of specific enforcement policies may be posted separately. 67 | 68 | Project maintainers who do not follow or enforce the Code of Conduct in good 69 | faith may face temporary or permanent repercussions as determined by other 70 | members of the project's leadership. 71 | 72 | ## Attribution 73 | 74 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 75 | available at [http://contributor-covenant.org/version/1/4][version] 76 | 77 | [homepage]: http://contributor-covenant.org 78 | [version]: http://contributor-covenant.org/version/1/4/ 79 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Hail the contributors! 2 | Eternal thanks to you, as _YOU_ make this project live and thrive! 3 | 4 | *Contributing is easy, baby!* 5 | 6 | You will find all of the details to get started [on the official documentation website](https://pyowm.readthedocs.io/) 7 | 8 | ## Quick guide on code contributions 9 | If you're eager to start coding on PyOWM, please follow these steps: 10 | 11 | 1. Fork the PyOWM repository 12 | 2. On your fork, work on the **development branch** (_not the master branch!!!_) or on a **ad-hoc topic branch**. Don't forget to insert your name in the `CONTRIBUTORS.md` file! 13 | 3. TEST YOUR CODE please! [Here's the gig](https://github.com/csparpa/pyowm/wiki/Notes-on-testing) 14 | 4. Submit a [pull request](https://help.github.com/articles/about-pull-requests/) 15 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Thanks to the contributors 2 | ========================== 3 | 4 | Contributors will be shown in alphabetical order 5 | 6 | Code 7 | ---- 8 | * [ahertz](https://github.com/ahertz) 9 | * [alechewitt](https://github.com/alechewitt) 10 | * [camponez](https://github.com/camponez) 11 | * [Darumin](https://github.com/Darumin) 12 | * [davidpirogov](https://github.com/davidpirogov) 13 | * [dev-iks](https://github.com/dev-iks) 14 | * [dphildebrandt](https://github.com/dphildebrandt) 15 | * [dstmar](https://github.com/dstmar) 16 | * [edenhaus](https://github.com/edenhaus) 17 | * [eumiro](https://github.com/eumiro) 18 | * [ggstuart](https://github.com/ggstuart) 19 | * [irahorecka](https://github.com/irahorecka) 20 | * [JakeThomson](https://github.com/jakethomson) 21 | * [jwmelvin](https://github.com/jwmelvin) 22 | * [lardconcepts](https://github.com/lardconcepts) 23 | * [liato](https://github.com/liato) 24 | * [LukasBoersma](https://github.com/LukasBoersma) 25 | * [MatthiasLohr](https://github.com/MatthiasLohr) 26 | * [Misiu](https://github.com/Misiu) 27 | * [Noid](https://github.com/n0id) 28 | * [titilambert](https://github.com/titilambert) 29 | * [Tobiaqs](https://github.com/Tobiaqs) 30 | * [txemi](https://github.com/txemi) 31 | * [Wesley-Vos](https://github.com/Wesley-Vos) 32 | 33 | Docs 34 | ---- 35 | * [Crozzers](https://github.com/Crozzers) 36 | * [EJEP](https://github.com/EJEP) 37 | * [Franzqat](https://github.com/franzqat) 38 | * [Harmon758](https://github.com/Harmon758) 39 | * [joe-meyer](https://github.com/joe-meyer) 40 | 41 | Testing 42 | ------- 43 | * [Ankur](https://github.com/Ankuraxz) 44 | * [Samuel Yap](https://github.com/samuelyap) 45 | * [Patrick Casbon](https://github.com/patcas) 46 | * [Tamas Magyar](https://github.com/tamasmagyar) 47 | 48 | Packaging and Distribution 49 | -------------------------- 50 | * [Crozzers](https://github.com/Crozzers) 51 | * [Diapente](https://github.com/Diapente) 52 | * [onkelbeh](https://github.com/onkelbeh) 53 | * [Simone-Zabberoni](https://github.com/Simone-Zabberoni) 54 | 55 | Wiki 56 | ---- 57 | * [lardconcepts](https://github.com/lardconcepts) 58 | * [richarddunks](https://github.com/richarddunks) 59 | * [solumos](https://github.com/solumos) 60 | 61 | Logo 62 | ---- 63 | * [marlinmm](https://github.com/marlinmm) 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Claudio Sparpaglione 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include pyowm/requirements.txt 2 | include pyowm/commons/cityids/*.bz2 -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.python.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [dev-packages] 7 | Babel = ">=2.9.1" 8 | coverage = "*" 9 | coveralls = "*" 10 | Jinja2 = "*" 11 | pip = ">=18.0" 12 | pproxy = ">=2.2.0,<3" 13 | pytest = "*" 14 | recommonmark = "*" 15 | Sphinx = "*" 16 | sphinx-readable-theme = "*" 17 | tox = "*" 18 | tox-travis = "*" 19 | virtualenv = "*" 20 | twine = "*" 21 | 22 | 23 | [packages] 24 | requests = "<3,>=2.20.0" 25 | geojson = "<3,>=2.3.0" 26 | PySocks = "<2,>=1.7.1" 27 | "requests[socks]" = "*" -------------------------------------------------------------------------------- /deploy/deploy_to_pypi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Rules: 4 | # - deploy to Test PyPI upon every push on the develop branch 5 | # - only deploy to the real PyPI upon merged pull requests on the master branch 6 | # - gracefully fail if integrated branch is neither develop nor master 7 | # - under any circumstance, only deply if the corresponding release does not yet exist (otherwise, gracefully fail) 8 | 9 | if [ $TRAVIS_BRANCH = "develop" ] && [[ $TRAVIS_EVENT_TYPE == "push" ]]; then 10 | echo "*** Will build the DEVELOP branch and publish to Test PyPI at $TEST_PYPI_URL" 11 | 12 | # Get env variables 13 | export INDEX_URL="$TEST_PYPI_URL" 14 | export PYPI_USERNAME="$TEST_PYPI_USERNAME" 15 | export PYPI_PASSWORD="$TEST_PYPI_PASSWORD" 16 | 17 | # Get commit SHA and patch development release number 18 | TS="$(date "+%Y%m%d%H0000")" 19 | echo "*** Development release number is: post$TS" 20 | sed -i -e "s/constants.PYOWM_VERSION/constants.PYOWM_VERSION+\".post${TS}\"/g" setup.py 21 | 22 | elif [ $TRAVIS_BRANCH = "master" ] && [[ $TRAVIS_EVENT_TYPE == "pull_request" ]]; then 23 | echo "*** Will build the MASTER branch and publish to PyPI at $PYPI_URL" 24 | 25 | # Get env variables 26 | export INDEX_URL="$PYPI_URL" 27 | export PYPI_USERNAME="$PYPI_USERNAME" 28 | export PYPI_PASSWORD="$PYPI_PASSWORD" 29 | 30 | REL_VERSION="$(cd .. && python3.5 -c "from pyowm.constants import PYOWM_VERSION; from pyowm.utils.stringutils import version_tuple_to_str; print(version_tuple_to_str(PYOWM_VERSION))")" 31 | 32 | echo "*** Checking if target release already exists on the repository..." 33 | wget "https://pypi.org/pypi/pyowm/${REL_VERSION}/json" 34 | OUTCOME="$(echo $?)" 35 | if [ $OUTCOME = "0" ]; then 36 | echo "*** OK: release is brand new" 37 | else 38 | echo "*** WARNING: release is already on the repository!" 39 | echo "*** SKIPPING deployment" 40 | exit 0 41 | fi 42 | 43 | else 44 | echo "*** Will not build branch $TRAVIS_BRANCH as this is neither DEVELOP nor MASTER" 45 | echo "*** SKIPPING deployment" 46 | exit 0 47 | fi 48 | 49 | echo '*** Generating source distribution...' 50 | python setup.py sdist 51 | 52 | echo '*** Generating egg distribution...' 53 | python setup.py bdist_egg 54 | 55 | echo '*** Generating wheel distribution...' 56 | python setup.py bdist_wheel 57 | 58 | echo '*** Uploading all artifacts to PyPi...' 59 | twine upload --repository-url "$INDEX_URL" \ 60 | --username "$PYPI_USERNAME" \ 61 | --password "$PYPI_PASSWORD" \ 62 | --skip-existing \ 63 | dist/* 64 | if [ "$?" -ne 0 ]; then 65 | exit 7 66 | fi 67 | 68 | echo '*** Done' -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | coveralls 3 | Jinja2 4 | pip>=18.0 5 | pproxy>=2.2.0,<3 6 | pytest 7 | recommonmark 8 | Sphinx 9 | sphinx-readable-theme<2 10 | tox 11 | tox-travis 12 | virtualenv 13 | twine 14 | urllib3>=1.26.5 15 | -------------------------------------------------------------------------------- /logos/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/logos/128x128.png -------------------------------------------------------------------------------- /logos/180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/logos/180x180.png -------------------------------------------------------------------------------- /logos/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/logos/32x32.png -------------------------------------------------------------------------------- /logos/LOGOS-LICENSE: -------------------------------------------------------------------------------- 1 | All PyOWM logos are distributed under a Creative Commons 2 | Attribution-NoDerivatives 4.0 International license 3 | (https://creativecommons.org/licenses/by-nd/4.0/legalcode) -------------------------------------------------------------------------------- /logos/fullres.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/logos/fullres.png -------------------------------------------------------------------------------- /pyowm/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.owm import OWM 5 | -------------------------------------------------------------------------------- /pyowm/__version__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm import constants 5 | from pyowm.utils.strings import version_tuple_to_str 6 | 7 | __title__ = 'pyowm' 8 | __description__ = 'A Python wrapper around OpenWeatherMap web APIs' 9 | __url__ = 'https://github.com/csparpa/pyowm' 10 | __version__ = version_tuple_to_str(constants.PYOWM_VERSION) 11 | __author__ = 'Claudio Sparpaglione' 12 | __author_email__ = 'csparpa@gmail.com' 13 | __license__ = 'MIT' 14 | -------------------------------------------------------------------------------- /pyowm/agroapi10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/agroapi10/__init__.py -------------------------------------------------------------------------------- /pyowm/agroapi10/enums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.commons.databoxes import Satellite 5 | 6 | 7 | class PresetEnum: 8 | """ 9 | Allowed presets for satellite images on Agro API 1.0 10 | 11 | """ 12 | TRUE_COLOR = 'truecolor' 13 | FALSE_COLOR = 'falsecolor' 14 | NDVI = 'ndvi' 15 | EVI = 'evi' 16 | 17 | @classmethod 18 | def items(cls): 19 | """ 20 | All values for this enum 21 | :return: list of str 22 | 23 | """ 24 | return [ 25 | cls.TRUE_COLOR, 26 | cls.FALSE_COLOR, 27 | cls.NDVI, 28 | cls.EVI 29 | ] 30 | 31 | def __repr__(self): 32 | return '<%s.%s>' % (__name__, self.__class__.__name__) 33 | 34 | 35 | class SatelliteEnum: 36 | """ 37 | Allowed presets for satellite names on Agro API 1.0 38 | 39 | """ 40 | LANDSAT_8 = Satellite('Landsat 8', 'l8') 41 | SENTINEL_2 = Satellite('Sentinel-2', 's2') 42 | 43 | @classmethod 44 | def items(cls): 45 | """ 46 | All values for this enum 47 | :return: list of str 48 | 49 | """ 50 | return [ 51 | cls.LANDSAT_8, 52 | cls.SENTINEL_2 53 | ] 54 | 55 | def __repr__(self): 56 | return '<%s.%s>' % (__name__, self.__class__.__name__) 57 | 58 | 59 | class PaletteEnum: 60 | """ 61 | Allowed color palettes for satellite images on Agro API 1.0 62 | 63 | """ 64 | GREEN = '1' # default Agro API 1.0 palette 65 | BLACK_AND_WHITE = '2' 66 | CONTRAST_SHIFTED = '3' 67 | CONTRAST_CONTINUOUS = '4' 68 | 69 | @classmethod 70 | def items(cls): 71 | """ 72 | All values for this enum 73 | :return: list of str 74 | 75 | """ 76 | return [ 77 | cls.GREEN, 78 | cls.BLACK_AND_WHITE, 79 | cls.CONTRAST_SHIFTED, 80 | cls.CONTRAST_CONTINUOUS 81 | ] 82 | 83 | def __repr__(self): 84 | return '<%s.%s>' % (__name__, self.__class__.__name__) -------------------------------------------------------------------------------- /pyowm/agroapi10/polygon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.utils.geo import GeometryBuilder 5 | from pyowm.utils.geo import Point as GeoPoint 6 | from pyowm.utils.geo import Polygon as GeoPolygon 7 | 8 | 9 | class Polygon: 10 | 11 | """ 12 | A Polygon feature, foundational element for all Agro API operations 13 | 14 | :param id: the polygon's ID 15 | :type id: str 16 | :param name: the polygon's name 17 | :type namr: str 18 | :param geopolygon: the `pyowm.utils.geo.Polygon` instance that represents this polygon 19 | :type geopolygon: `pyowm.utils.geo.Polygon` 20 | :param center: the `pyowm.utils.geo.Point` instance that represents the central point of the polygon 21 | :type center: `pyowm.utils.geo.Point` 22 | :param area: the area of the polygon in hectares 23 | :type area: float or int 24 | :param user_id: the ID of the user owning this polygon 25 | :type user_id: str 26 | :returns: a `Polygon` instance 27 | :raises: `AssertionError` when either id is `None` or geopolygon, center or area have wrong type 28 | """ 29 | 30 | def __init__(self, id, name=None, geopolygon=None, center=None, area=None, user_id=None): 31 | 32 | assert id is not None, 'Polygon ID cannot be None' 33 | if geopolygon is not None: 34 | assert isinstance(geopolygon, GeoPolygon), 'Polygon must be a valid geopolygon type' 35 | if center is not None: 36 | assert isinstance(center, GeoPoint), 'Polygon center must be a valid geopoint type' 37 | if area is not None: 38 | assert isinstance(area, (float, int)), 'Area must be a numeric type' 39 | assert area >= 0, 'Area must not be negative' 40 | self.id = id 41 | self.name = name 42 | self.geopolygon = geopolygon 43 | self.center = center 44 | self.area = area 45 | self.user_id = user_id 46 | 47 | @property 48 | def area_km(self): 49 | if self.area: 50 | return self.area * 0.01 51 | return None 52 | 53 | @classmethod 54 | def from_dict(cls, the_dict): 55 | assert isinstance(the_dict, dict) 56 | the_id = the_dict.get('id', None) 57 | geojson = the_dict.get('geo_json', {}).get('geometry', None) 58 | name = the_dict.get('name', None) 59 | center = the_dict.get('center', None) 60 | area = the_dict.get('area', None) 61 | user_id =the_dict.get('user_id', None) 62 | geopolygon = GeometryBuilder.build(geojson) 63 | try: 64 | center = GeoPoint(center[0], center[1]) 65 | except: 66 | raise ValueError('Wrong format for polygon center coordinates') 67 | return Polygon(the_id, name, geopolygon, center, area, user_id) 68 | 69 | def __repr__(self): 70 | return "<%s.%s - id=%s, name=%s, area=%s>" % (__name__, self.__class__.__name__, self.id, self.name, str(self.area)) 71 | -------------------------------------------------------------------------------- /pyowm/agroapi10/uris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ROOT_AGRO_API = 'agromonitoring.com/agro/1.0' 5 | ROOT_DOWNLOAD_PNG_API = 'agromonitoring.com/image/1.0' 6 | ROOT_DOWNLOAD_GEOTIFF_API = 'agromonitoring.com/data/1.0' 7 | 8 | # Polygons API subset 9 | POLYGONS_URI = 'polygons' 10 | NAMED_POLYGON_URI = 'polygons/%s' 11 | 12 | # Soil API subset 13 | SOIL_URI = 'soil' 14 | 15 | # Satellite Imagery Search API subset 16 | SATELLITE_IMAGERY_SEARCH_URI = 'image/search' 17 | -------------------------------------------------------------------------------- /pyowm/airpollutionapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/airpollutionapi30/__init__.py -------------------------------------------------------------------------------- /pyowm/airpollutionapi30/uris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # deprecated API endpoints 5 | ROOT_POLLUTION_API_URL = 'openweathermap.org/pollution/v1' 6 | CO_INDEX_URL = 'co' 7 | OZONE_URL = 'o3' 8 | NO2_INDEX_URL = 'no2' 9 | SO2_INDEX_URL = 'so2' 10 | 11 | 12 | # current API endpoint 13 | NEW_ROOT_POLLUTION_API_URL = 'openweathermap.org/data/2.5' 14 | AIR_POLLUTION_URL = 'air_pollution' 15 | AIR_POLLUTION_FORECAST_URL = 'air_pollution/forecast' 16 | AIR_POLLUTION_HISTORY_URL = 'air_pollution/history' 17 | -------------------------------------------------------------------------------- /pyowm/alertapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/alertapi30/__init__.py -------------------------------------------------------------------------------- /pyowm/alertapi30/condition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.commons import exceptions 5 | 6 | 7 | class Condition: 8 | """ 9 | Object representing a condition to be checked on a specific weather parameter. A condition is given when comparing 10 | the weather parameter against a numerical value with respect to an operator. 11 | Allowed weather params and operators are specified by the `pyowm.utils.alertapi30.WeatherParametersEnum` and 12 | `pyowm.utils.alertapi30.OperatorsEnum` enumerator classes. 13 | :param weather_param: the weather variable to be checked (eg. TEMPERATURE, CLOUDS, ...) 14 | :type weather_param: str 15 | :param operator: the comparison operator to be applied to the weather variable (eg. GREATER_THAN, EQUAL, ...) 16 | :type operator: str 17 | :param amount: comparison value 18 | :type amount: int or float 19 | :param id: optional unique ID for this Condition instance 20 | :type id: str 21 | :returns: a *Condition* instance 22 | :raises: *AssertionError* when either the weather param has wrong type or the operator has wrong type or the 23 | amount has wrong type 24 | 25 | """ 26 | def __init__(self, weather_param, operator, amount, id=None): 27 | assert weather_param is not None 28 | assert isinstance(weather_param, str), "Value must be a string" 29 | self.weather_param = weather_param 30 | 31 | assert operator is not None 32 | assert isinstance(operator, str), "Value must be a string" 33 | self.operator = operator 34 | 35 | assert amount is not None 36 | assert isinstance(amount, (int, float)) 37 | self.amount = amount 38 | self.id = id 39 | 40 | @classmethod 41 | def from_dict(cls, the_dict): 42 | if the_dict is None: 43 | raise exceptions.ParseAPIResponseError('Data is None') 44 | try: 45 | weather_param = the_dict['name'] 46 | operator = the_dict['expression'] 47 | amount = the_dict['amount'] 48 | the_id = the_dict.get('_id', None) 49 | return Condition(weather_param, operator, amount, id=the_id) 50 | except KeyError as e: 51 | raise exceptions.ParseAPIResponseError('Impossible to parse data: %s' % e) 52 | 53 | def to_dict(self): 54 | return { 55 | 'id': self.id, 56 | 'weather_param': self.weather_param, 57 | 'operator': self.operator, 58 | 'amount': self.amount} 59 | 60 | def __repr__(self): 61 | return '<%s.%s - when %s %s %s>' % (__name__, self.__class__.__name__, self.weather_param, 62 | self.operator, str(self.amount)) 63 | 64 | -------------------------------------------------------------------------------- /pyowm/alertapi30/enums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.alertapi30.alert import AlertChannel 5 | 6 | 7 | class WeatherParametersEnum: 8 | """ 9 | Allowed weather parameters for condition checking 10 | 11 | """ 12 | TEMPERATURE = 'temp' # Kelvin 13 | PRESSURE = 'pressure' 14 | HUMIDITY = 'humidity' 15 | WIND_SPEED = 'wind_speed' 16 | WIND_DIRECTION = 'wind_direction' 17 | CLOUDS = 'clouds' # Coverage % 18 | 19 | @classmethod 20 | def items(cls): 21 | """ 22 | All values for this enum 23 | :return: list of str 24 | 25 | """ 26 | return [ 27 | cls.TEMPERATURE, 28 | cls.PRESSURE, 29 | cls.HUMIDITY, 30 | cls.WIND_SPEED, 31 | cls.WIND_DIRECTION, 32 | cls.CLOUDS 33 | ] 34 | 35 | 36 | class OperatorsEnum: 37 | """ 38 | Allowed comparison operators for condition checking upon weather parameters 39 | 40 | """ 41 | GREATER_THAN = '$gt' 42 | GREATER_THAN_EQUAL = '$gte' 43 | LESS_THAN = '$lt' 44 | LESS_THAN_EQUAL = '$lte' 45 | EQUAL = '$eq' 46 | NOT_EQUAL = '$ne' 47 | 48 | @classmethod 49 | def items(cls): 50 | """ 51 | All values for this enum 52 | :return: list of str 53 | 54 | """ 55 | return [ 56 | cls.GREATER_THAN, 57 | cls.GREATER_THAN_EQUAL, 58 | cls.LESS_THAN, 59 | cls.LESS_THAN_EQUAL, 60 | cls.EQUAL, 61 | cls.NOT_EQUAL 62 | ] 63 | 64 | 65 | class AlertChannelsEnum: 66 | """ 67 | Allowed alert channels 68 | 69 | """ 70 | OWM_API_POLLING = AlertChannel('OWM API POLLING') 71 | 72 | @classmethod 73 | def items(cls): 74 | """ 75 | All values for this enum 76 | :return: list of str 77 | 78 | """ 79 | return [ 80 | cls.OWM_API_POLLING 81 | ] 82 | -------------------------------------------------------------------------------- /pyowm/alertapi30/uris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ROOT_ALERT_API_URL = 'openweathermap.org/data/3.0' 5 | 6 | # Triggers 7 | TRIGGERS_URI = 'triggers' 8 | NAMED_TRIGGER_URI = 'triggers/%s' 9 | 10 | # Alerts 11 | ALERTS_URI = 'triggers/%s/history' 12 | NAMED_ALERT_URI = 'triggers/%s/history/%s' 13 | -------------------------------------------------------------------------------- /pyowm/commons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/commons/__init__.py -------------------------------------------------------------------------------- /pyowm/commons/cityids/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pyowm/commons/cityids/cities.db.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/commons/cityids/cities.db.bz2 -------------------------------------------------------------------------------- /pyowm/commons/databoxes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class SubscriptionType: 6 | """ 7 | Databox class representing a type of subscription to OpenWeatherMap web APIs 8 | 9 | :param name: the name of the subscription 10 | :type name: str 11 | :param subdomain: the root API subdomain associated to the subscription 12 | :type subdomain: str 13 | :param is_paid: tells if the subscription plan is paid 14 | :type is_paid: bool 15 | """ 16 | def __init__(self, name, subdomain, is_paid): 17 | 18 | self.name = name 19 | self.subdomain = subdomain 20 | self.is_paid = is_paid 21 | 22 | def __repr__(self): 23 | return "<%s.%s - name=%s subdomain=%s paid=%s>" % ( 24 | __name__, self.__class__.__name__, self.name, self.subdomain, self.is_paid) 25 | 26 | 27 | class ImageType: 28 | """ 29 | Databox class representing an image type 30 | 31 | :param name: the image type name 32 | :type name: str 33 | :param mime_type: the image type MIME type 34 | :type mime_type: str 35 | """ 36 | def __init__(self, name, mime_type): 37 | 38 | self.name = name 39 | self.mime_type = mime_type 40 | 41 | def __repr__(self): 42 | return "<%s.%s - name=%s mime=%s>" % ( 43 | __name__, self.__class__.__name__, self.name, self.mime_type) 44 | 45 | 46 | class Satellite: 47 | """ 48 | Databox class representing a satellite 49 | 50 | :param name: the satellite 51 | :type name: str 52 | :param symbol: the short name of the satellite 53 | :type symbol: str 54 | """ 55 | def __init__(self, name, symbol): 56 | 57 | self.name = name 58 | self.symbol = symbol 59 | 60 | def __repr__(self): 61 | return "<%s.%s - name=%s symbol=%s>" % ( 62 | __name__, self.__class__.__name__, self.name, self.symbol) 63 | 64 | -------------------------------------------------------------------------------- /pyowm/commons/enums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.commons.databoxes import ImageType, SubscriptionType 5 | 6 | 7 | class SubscriptionTypeEnum: 8 | """ 9 | Allowed OpenWeatherMap subscription types 10 | 11 | """ 12 | FREE = SubscriptionType('free', 'api', False) 13 | STARTUP = SubscriptionType('startup', 'api', True) 14 | DEVELOPER = SubscriptionType('developer', 'api', True) 15 | PROFESSIONAL = SubscriptionType('professional', 'api', True) 16 | ENTERPRISE = SubscriptionType('enterprise', 'api', True) 17 | 18 | @classmethod 19 | def lookup_by_name(cls, name): 20 | for i in SubscriptionTypeEnum.items(): 21 | if i.name == name: 22 | return i 23 | raise ValueError('Subscription type not allowed') 24 | 25 | @classmethod 26 | def items(cls): 27 | """ 28 | All values for this enum 29 | :return: list of `pyowm.commons.enums.SubscriptionType` 30 | 31 | """ 32 | return [ 33 | cls.FREE, 34 | cls.STARTUP, 35 | cls.DEVELOPER, 36 | cls.PROFESSIONAL, 37 | cls.ENTERPRISE 38 | ] 39 | 40 | def __repr__(self): 41 | return "<%s.%s>" % (__name__, self.__class__.__name__) 42 | 43 | 44 | class ImageTypeEnum: 45 | """ 46 | Allowed image types on OWM APIs 47 | 48 | """ 49 | PNG = ImageType('PNG', 'image/png') 50 | GEOTIFF = ImageType('GEOTIFF', 'image/tiff') 51 | 52 | @classmethod 53 | def lookup_by_mime_type(cls, mime_type): 54 | for i in ImageTypeEnum.items(): 55 | if i.mime_type == mime_type: 56 | return i 57 | return None 58 | 59 | @classmethod 60 | def lookup_by_name(cls, name): 61 | for i in ImageTypeEnum.items(): 62 | if i.name == name: 63 | return i 64 | return None 65 | 66 | @classmethod 67 | def items(cls): 68 | """ 69 | All values for this enum 70 | :return: list of `pyowm.commons.enums.ImageType` 71 | 72 | """ 73 | return [ 74 | cls.PNG, 75 | cls.GEOTIFF 76 | ] 77 | 78 | def __repr__(self): 79 | return "<%s.%s>" % (__name__, self.__class__.__name__) 80 | -------------------------------------------------------------------------------- /pyowm/commons/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class PyOWMError(Exception): 6 | """Generic base class for PyOWM exceptions""" 7 | pass 8 | 9 | 10 | class ConfigurationError(PyOWMError): 11 | """Generic base class for configuration related errors""" 12 | pass 13 | 14 | 15 | class ConfigurationNotFoundError(ConfigurationError): 16 | """Raised when configuration source file is not available""" 17 | pass 18 | 19 | 20 | class ConfigurationParseError(ConfigurationError): 21 | """Raised on failures in parsing configuration data""" 22 | pass 23 | 24 | 25 | class APIRequestError(PyOWMError): 26 | """ 27 | Error class that represents network/infrastructural failures when invoking OWM Weather API, in 28 | example due to network errors. 29 | """ 30 | pass 31 | 32 | 33 | class BadGatewayError(APIRequestError): 34 | """ 35 | Error class that represents 502 errors - i.e when upstream backend 36 | cannot communicate with API gateways. 37 | """ 38 | pass 39 | 40 | 41 | class TimeoutError(APIRequestError): 42 | """ 43 | Error class that represents response timeout conditions 44 | """ 45 | pass 46 | 47 | 48 | class InvalidSSLCertificateError(APIRequestError): 49 | """ 50 | Error class that represents failure in verifying the SSL certificate provided 51 | by the OWM API 52 | """ 53 | pass 54 | 55 | 56 | class APIResponseError(PyOWMError): 57 | """ 58 | Generic base class for exceptions representing HTTP error status codes in OWM Weather API 59 | responses 60 | """ 61 | pass 62 | 63 | 64 | class NotFoundError(APIResponseError): 65 | """ 66 | Error class that represents the situation when an entity is not found. 67 | """ 68 | pass 69 | 70 | 71 | class UnauthorizedError(APIResponseError): 72 | """ 73 | Error class that represents the situation when an entity cannot be retrieved 74 | due to user subscription insufficient capabilities. 75 | """ 76 | pass 77 | 78 | 79 | class ParseAPIResponseError(PyOWMError): 80 | """ 81 | Error class that represents failures when parsing payload data in HTTP 82 | responses sent by the OWM Weather API. 83 | """ 84 | pass 85 | -------------------------------------------------------------------------------- /pyowm/commons/image.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.commons.databoxes import ImageType 5 | from pyowm.commons.enums import ImageTypeEnum 6 | 7 | 8 | class Image: 9 | 10 | """ 11 | Wrapper class for a generic image 12 | 13 | :param data: raw image data 14 | :type data: bytes 15 | :param image_type: the type of the image, if known 16 | :type image_type: `pyowm.commons.databoxes.ImageType` or `None` 17 | """ 18 | 19 | def __init__(self, data, image_type=None): 20 | self.data = data 21 | if image_type is not None: 22 | assert isinstance(image_type, ImageType) 23 | self.image_type = image_type 24 | 25 | def persist(self, path_to_file): 26 | """ 27 | Saves the image to disk on a file 28 | 29 | :param path_to_file: path to the target file 30 | :type path_to_file: str 31 | :return: `None` 32 | """ 33 | with open(path_to_file, 'wb') as f: 34 | f.write(self.data) 35 | 36 | @classmethod 37 | def load(cls, path_to_file): 38 | """ 39 | Loads the image data from a file on disk and tries to guess the image MIME type 40 | 41 | :param path_to_file: path to the source file 42 | :type path_to_file: str 43 | :return: a `pyowm.image.Image` instance 44 | """ 45 | import mimetypes 46 | mimetypes.init() 47 | mime = mimetypes.guess_type('file://%s' % path_to_file)[0] 48 | img_type = ImageTypeEnum.lookup_by_mime_type(mime) 49 | with open(path_to_file, 'rb') as f: 50 | data = f.read() 51 | return Image(data, image_type=img_type) 52 | 53 | def __repr__(self): 54 | return "<%s.%s - type=%s>" % (__name__, self.__class__.__name__, str(self.image_type)) 55 | -------------------------------------------------------------------------------- /pyowm/commons/tile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import math 5 | from pyowm.commons.image import Image 6 | from pyowm.utils.geo import Polygon 7 | 8 | 9 | class Tile: 10 | 11 | """ 12 | Wrapper class for an image tile 13 | :param x: horizontal tile number in OWM tile reference system 14 | :type x: int 15 | :param y: vertical tile number in OWM tile reference system 16 | :type y: int 17 | :param zoom: zoom level for the tile 18 | :type zoom: int 19 | :param map_layer: the name of the OWM map layer this tile belongs to 20 | :type map_layer: str 21 | :param image: raw image data 22 | :type image: `pyowm.commons.image.Image instance` 23 | """ 24 | 25 | def __init__(self, x, y, zoom, map_layer, image): 26 | assert x >= 0, 'X tile coordinate cannot be negative' 27 | self.x = x 28 | assert y >= 0, 'Y tile coordinate cannot be negative' 29 | self.y = y 30 | assert zoom >= 0, 'Tile zoom level cannot be negative' 31 | self.zoom = zoom 32 | self.map_layer = map_layer 33 | assert isinstance(image, Image), 'The provided image is in invalid format' 34 | self.image = image 35 | 36 | def persist(self, path_to_file): 37 | """ 38 | Saves the tile to disk on a file 39 | 40 | :param path_to_file: path to the target file 41 | :type path_to_file: str 42 | :return: `None` 43 | """ 44 | self.image.persist(path_to_file) 45 | 46 | def bounding_polygon(self): 47 | """ 48 | Returns the bounding box polygon for this tile 49 | 50 | :return: `pywom.utils.geo.Polygon` instance 51 | """ 52 | lon_left, lat_bottom, lon_right, lat_top = Tile.tile_coords_to_bbox(self.x, self.y, self.zoom) 53 | print(lon_left, lat_bottom, lon_right, lat_top) 54 | return Polygon([[[lon_left, lat_top], 55 | [lon_right, lat_top], 56 | [lon_right, lat_bottom], 57 | [lon_left, lat_bottom], 58 | [lon_left, lat_top]]]) 59 | 60 | @classmethod 61 | def tile_coords_for_point(cls, geopoint, zoom): 62 | """ 63 | Returns the coordinates of the tile containing the specified geopoint at the specified zoom level 64 | 65 | :param geopoint: the input geopoint instance 66 | :type geopoint: `pywom.utils.geo.Point` 67 | :param zoom: zoom level 68 | :type zoom: int 69 | :return: a tuple (x, y) containing the tile-coordinates 70 | """ 71 | return Tile.geoocoords_to_tile_coords(geopoint.lon, geopoint.lat, zoom) 72 | 73 | @classmethod 74 | def geoocoords_to_tile_coords(cls, lon, lat, zoom): 75 | """ 76 | Calculates the tile numbers corresponding to the specified geocoordinates at the specified zoom level 77 | Coordinates shall be provided in degrees and using the Mercator Projection (http://en.wikipedia.org/wiki/Mercator_projection) 78 | 79 | :param lon: longitude 80 | :type lon: int or float 81 | :param lat: latitude 82 | :type lat: int or float 83 | :param zoom: zoom level 84 | :type zoom: int 85 | :return: a tuple (x, y) containing the tile-coordinates 86 | """ 87 | n = 2.0 ** zoom 88 | x = int((lon + 180.0) / 360.0 * n) 89 | y = int((1.0 - math.log(math.tan(math.radians(lat)) + (1 / math.cos(math.radians(lat)))) / math.pi) / 2.0 * n) 90 | return x, y 91 | 92 | @classmethod 93 | def tile_coords_to_bbox(cls, x, y, zoom): 94 | """ 95 | Calculates the lon/lat estrema of the bounding box corresponding to specific tile coordinates. Output coordinates 96 | are in degrees and in the Mercator Projection (http://en.wikipedia.org/wiki/Mercator_projection) 97 | 98 | :param x: the x tile coordinates 99 | :param y: the y tile coordinates 100 | :param zoom: the zoom level 101 | :return: tuple with (lon_left, lat_bottom, lon_right, lat_top) 102 | """ 103 | def tile_to_geocoords(x, y, zoom): 104 | n = 2. ** zoom 105 | lon = x / n * 360. - 180. 106 | lat = math.degrees(math.atan(math.sinh(math.pi * (1 - 2 * y / n)))) 107 | return lat, lon 108 | north_west_corner = tile_to_geocoords(x, y, zoom) 109 | south_east_corner = tile_to_geocoords(x+1, y+1, zoom) 110 | return north_west_corner[1], south_east_corner[0], south_east_corner[1], north_west_corner[0] 111 | 112 | def __repr__(self): 113 | return "<%s.%s - x=%s, y=%s, zoom=%s, map_layer=%s>" % ( 114 | __name__, self.__class__.__name__, self.x, self.y, self.zoom, self.map_layer) 115 | -------------------------------------------------------------------------------- /pyowm/commons/uris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ROOT_GEOCODING_API_URL = 'openweathermap.org/geo/1.0' 5 | DIRECT_GEOCODING_URI = 'direct' 6 | REVERSE_GEOCODING_URI = 'reverse' 7 | 8 | -------------------------------------------------------------------------------- /pyowm/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.commons.enums import SubscriptionTypeEnum 5 | 6 | DEFAULT_CONFIG = { 7 | 'subscription_type': SubscriptionTypeEnum.FREE, 8 | 'language': 'en', 9 | 'connection': { 10 | 'use_ssl': True, 11 | 'verify_ssl_certs': True, 12 | 'use_proxy': False, 13 | 'timeout_secs': 5, 14 | 'max_retries': None 15 | }, 16 | 'proxies': { 17 | 'http': 'http://user:pass@host:port', 18 | 'https': 'socks5://user:pass@host:port' 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pyowm/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | PYOWM_VERSION = (3, 3, 0) 5 | AGRO_API_VERSION = (1, 0, 0) 6 | AIRPOLLUTION_API_VERSION = (3, 0, 0) 7 | ALERT_API_VERSION = (3, 0, 0) 8 | GEOCODING_API_VERSION = (1, 0, 0) 9 | STATIONS_API_VERSION = (3, 0, 0) 10 | UVINDEX_API_VERSION = (3, 0, 0) 11 | WEATHER_API_VERSION = (2, 5, 0) 12 | LANGUAGES = [ 13 | "af", 14 | "al", 15 | "ar", 16 | "az", 17 | "bg", 18 | "ca", 19 | "cz", 20 | "da", 21 | "de", 22 | "el", 23 | "en", 24 | "es", 25 | "eu", 26 | "fa", 27 | "fi", 28 | "fr", 29 | "gl", 30 | "he", 31 | "hi", 32 | "hr", 33 | "hu", 34 | "id", 35 | "it", 36 | "ja", 37 | "kr", 38 | "la", 39 | "lt", 40 | "mk", 41 | "nl", 42 | "no", 43 | "pl", 44 | "pt", 45 | "pt_br", 46 | "ro", 47 | "ru", 48 | "se", 49 | "sk", 50 | "sl", 51 | "sp", 52 | "sr", 53 | "sv", 54 | "th", 55 | "tr", 56 | "ua", 57 | "uk", 58 | "vi", 59 | "zh_cn", 60 | "zh_tw", 61 | "zu", 62 | ] -------------------------------------------------------------------------------- /pyowm/geocodingapi10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/geocodingapi10/__init__.py -------------------------------------------------------------------------------- /pyowm/geocodingapi10/geocoding_manager.py: -------------------------------------------------------------------------------- 1 | from pyowm.commons.http_client import HttpClient 2 | from pyowm.commons.uris import ROOT_GEOCODING_API_URL, DIRECT_GEOCODING_URI, REVERSE_GEOCODING_URI 3 | from pyowm.constants import GEOCODING_API_VERSION 4 | from pyowm.utils import geo 5 | from pyowm.weatherapi30.location import Location 6 | 7 | 8 | class GeocodingManager: 9 | 10 | """ 11 | A manager objects that provides a full interface to OWM Geocoding API. 12 | 13 | :param API_key: the OWM API key 14 | :type API_key: str 15 | :param config: the configuration dictionary 16 | :type config: dict 17 | :returns: an *GeocodingManager* instance 18 | :raises: *AssertionError* when no API Key is provided 19 | 20 | """ 21 | 22 | def __init__(self, API_key, config): 23 | assert API_key is not None, 'You must provide a valid API Key' 24 | self.API_key = API_key 25 | assert isinstance(config, dict) 26 | self.http_client = HttpClient(API_key, config, ROOT_GEOCODING_API_URL) 27 | 28 | def geocoding_api_version(self): 29 | return GEOCODING_API_VERSION 30 | 31 | def geocode(self, toponym, country=None, state_code=None, limit=None): 32 | """ 33 | Invokes the direct geocoding API endpoint 34 | 35 | :param toponym: the name of the location 36 | :type toponym: `str` 37 | :param country: the 2-chars ISO symbol of the country 38 | :type country: `str` or `None` 39 | :param state_code: the 2-chars ISO symbol of state (only useful in case the country is US) 40 | :type state_code: `str` or `None` 41 | :param limit: the max number of results to be returned in case of multiple matchings (no limits by default) 42 | :type limit: `int` or `None` 43 | :returns: a list of *Location* instances 44 | :raises: *AssertionError*, *ValueError*, *APIRequestError* 45 | 46 | """ 47 | assert toponym, 'Toponym must be specified' 48 | if country is not None and len(country) != 2: 49 | raise ValueError("Country must be a 2-char string") 50 | if state_code is not None and len(state_code) != 2: 51 | raise ValueError("State Code must be a 2-char string") 52 | if limit is not None: 53 | assert isinstance(limit, int) 54 | assert limit > 0 55 | 56 | query = toponym 57 | if state_code is not None: 58 | query += ',' + state_code 59 | if country is not None: 60 | query += ',' + country 61 | 62 | params = {'q': query} 63 | 64 | if limit is not None: 65 | params['limit'] = limit 66 | 67 | _, json_data = self.http_client.get_json(DIRECT_GEOCODING_URI, params=params) 68 | return [Location.from_dict(item) for item in json_data] 69 | 70 | def reverse_geocode(self, lat, lon, limit=None): 71 | geo.assert_is_lon(lon) 72 | geo.assert_is_lat(lat) 73 | if limit is not None: 74 | assert isinstance(limit, int) 75 | assert limit > 0 76 | 77 | params = {'lat': lat, 'lon': lon} 78 | if limit is not None: 79 | params['limit'] = limit 80 | 81 | _, json_data = self.http_client.get_json(REVERSE_GEOCODING_URI, params=params) 82 | return [Location.from_dict(item) for item in json_data] 83 | 84 | def __repr__(self): 85 | return '<%s.%s>' % (__name__, self.__class__.__name__) -------------------------------------------------------------------------------- /pyowm/stationsapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/stationsapi30/__init__.py -------------------------------------------------------------------------------- /pyowm/stationsapi30/buffer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import copy 5 | import json 6 | from pyowm.stationsapi30.measurement import Measurement 7 | from pyowm.utils import timestamps, formatting 8 | 9 | 10 | class Buffer: 11 | 12 | station_id = None 13 | created_at = None 14 | measurements = None 15 | 16 | def __init__(self, station_id): 17 | assert station_id is not None 18 | self.station_id = station_id 19 | self.created_at = timestamps.now(timeformat='unix') 20 | self.measurements = [] 21 | 22 | def creation_time(self, timeformat='unix'): 23 | """Returns the UTC time of creation of this aggregated measurement 24 | 25 | :param timeformat: the format for the time value. May be: 26 | '*unix*' (default) for UNIX time, '*iso*' for ISO8601-formatted 27 | string in the format ``YYYY-MM-DD HH:MM:SS+00`` or `date` for 28 | a ``datetime.datetime`` object 29 | :type timeformat: str 30 | :returns: an int or a str or a ``datetime.datetime`` object or None 31 | :raises: ValueError 32 | 33 | """ 34 | if self.created_at is None: 35 | return None 36 | return formatting.timeformat(self.created_at, timeformat) 37 | 38 | def append(self, measurement): 39 | """ 40 | Appends the specified ``Measurement`` object to the buffer 41 | :param measurement: a ``measurement.Measurement`` instance 42 | 43 | """ 44 | assert isinstance(measurement, Measurement) 45 | assert measurement.station_id == self.station_id 46 | self.measurements.append(measurement) 47 | 48 | def append_from_dict(self, the_dict): 49 | """ 50 | Creates a ``measurement.Measurement`` object from the supplied dict 51 | and then appends it to the buffer 52 | :param the_dict: dict 53 | 54 | """ 55 | m = Measurement.from_dict(the_dict) 56 | self.append(m) 57 | 58 | def append_from_json(self, json_string): 59 | """ 60 | Creates a ``measurement.Measurement`` object from the supplied JSON string 61 | and then appends it to the buffer 62 | :param json_string: the JSON formatted string 63 | 64 | """ 65 | a_dict = json.loads(json_string) 66 | self.append_from_dict(a_dict) 67 | 68 | def empty(self): 69 | """ 70 | Drops all measurements of this buffer instance 71 | 72 | """ 73 | self.measurements = [] 74 | 75 | def sort_chronologically(self): 76 | """ 77 | Sorts the measurements of this buffer in chronological order 78 | 79 | """ 80 | self.measurements.sort(key=lambda m: m.timestamp) 81 | 82 | def sort_reverse_chronologically(self): 83 | """ 84 | Sorts the measurements of this buffer in reverse chronological order 85 | 86 | """ 87 | self.measurements.sort(key=lambda m: m.timestamp, reverse=True) 88 | 89 | def __len__(self): 90 | return len(self.measurements) 91 | 92 | def __iter__(self): 93 | return iter(self.measurements) 94 | 95 | def __add__(self, other): 96 | assert all(i.station_id == self.station_id for i in other) 97 | result = copy.deepcopy(self) 98 | for m in other.measurements: 99 | result.append(m) 100 | return result 101 | 102 | def __contains__(self, measurement): 103 | return measurement in self.measurements 104 | 105 | def __repr__(self): 106 | return '<%s.%s - station_id=%s, n_samples=%s>' \ 107 | % (__name__, self.__class__.__name__, 108 | self.station_id, len(self)) 109 | -------------------------------------------------------------------------------- /pyowm/stationsapi30/persistence_backend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import json 6 | from abc import ABCMeta, abstractmethod 7 | from pyowm.stationsapi30.buffer import Buffer 8 | 9 | 10 | class PersistenceBackend: # pragma: no cover 11 | 12 | """ 13 | A global abstract class representing an I/O manager for buffer objects containing 14 | raw measurements. 15 | """ 16 | 17 | __metaclass__ = ABCMeta 18 | 19 | @abstractmethod 20 | def load_to_buffer(self): 21 | """ 22 | Reads meteostation measurement data into a *pyowm.stationsapi30.buffer.Buffer* 23 | object. 24 | 25 | :returns: a *pyowm.stationsapi30.buffer.Buffer* instance 26 | 27 | """ 28 | pass 29 | 30 | @abstractmethod 31 | def persist_buffer(self, buffer): 32 | """ 33 | Saves data contained into a *pyowm.stationsapi30.buffer.Buffer* object 34 | in a durable form. 35 | 36 | :param buffer: the Buffer object to be persisted 37 | :type buffer: *pyowm.stationsapi30.buffer.Buffer* instance 38 | 39 | """ 40 | pass 41 | 42 | def __repr__(self): 43 | return '<%s.%s>' % (__name__, self.__class__.__name__) 44 | 45 | 46 | class JSONPersistenceBackend(PersistenceBackend): 47 | 48 | """ 49 | A `PersistenceBackend` loading/saving data to a JSON file. Data will be 50 | saved as a JSON list, each element being representing data of a 51 | *pyowm.stationsapi30.measurement.Measurement* object. 52 | 53 | :param json_file_path: path to the JSON file 54 | :type json_file_path: str 55 | :param station_id: unique OWM-provided ID of the station whose data is read/saved 56 | :type station_id: str 57 | """ 58 | 59 | _file_path = None 60 | _station_id = None 61 | 62 | def __init__(self, json_file_path, station_id): 63 | assert json_file_path is not None 64 | self._station_id = station_id 65 | assert os.path.isfile(json_file_path) 66 | self._file_path = json_file_path 67 | 68 | def load_to_buffer(self): 69 | if self._station_id is None: 70 | raise ValueError('No station ID specified') 71 | result = Buffer(self._station_id) 72 | with open(self._file_path, 'r') as f: 73 | list_of_dicts = json.load(f) 74 | for _dict in list_of_dicts: 75 | result.append_from_dict(_dict) 76 | return result 77 | 78 | def persist_buffer(self, buffer): 79 | with open(self._file_path, 'w') as f: 80 | data = [msmt.to_JSON() for msmt in buffer] 81 | f.write('[%s]' % ','.join(data)) 82 | -------------------------------------------------------------------------------- /pyowm/stationsapi30/uris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ROOT_STATIONS_API_URL = 'openweathermap.org/data/3.0' 5 | 6 | # Stations 7 | STATIONS_URI = 'stations' 8 | NAMED_STATION_URI = 'stations/%s' 9 | 10 | # Measurements 11 | MEASUREMENTS_URI = 'measurements' 12 | -------------------------------------------------------------------------------- /pyowm/tiles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/tiles/__init__.py -------------------------------------------------------------------------------- /pyowm/tiles/enums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class MapLayerEnum: 6 | """ 7 | Allowed map layer values for tiles retrieval 8 | 9 | """ 10 | PRECIPITATION = 'precipitation_new' 11 | WIND = 'wind_new' 12 | TEMPERATURE = 'temp_new' 13 | PRESSURE = 'pressure_new' 14 | -------------------------------------------------------------------------------- /pyowm/tiles/tile_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.commons.enums import ImageTypeEnum 5 | from pyowm.commons.http_client import HttpClient 6 | from pyowm.commons.image import Image 7 | from pyowm.commons.tile import Tile 8 | from pyowm.tiles.uris import ROOT_TILE_URL, NAMED_MAP_LAYER_URL 9 | 10 | 11 | class TileManager: 12 | 13 | """ 14 | A manager objects that reads OWM map layers tile images . 15 | 16 | :param API_key: the OWM Weather API key 17 | :type API_key: str 18 | :param map_layer: the layer for which you want tiles fetched. Allowed map layers are specified by 19 | the `pyowm.tiles.enum.MapLayerEnum` enumerator class. 20 | :type map_layer: str 21 | :param config: the configuration dictionary 22 | :type config: dict 23 | :returns: a *TileManager* instance 24 | :raises: *AssertionError* when no API Key or no map layer is provided, or map layer name is not a string 25 | 26 | """ 27 | 28 | def __init__(self, API_key, map_layer, config): 29 | assert API_key is not None, 'You must provide a valid API Key' 30 | self.API_key = API_key 31 | assert map_layer is not None, 'You must provide a valid map layer name' 32 | assert isinstance(map_layer, str), 'Map layer name must be a string' 33 | self.map_layer = map_layer 34 | assert isinstance(config, dict) 35 | self.http_client = HttpClient(API_key, config, ROOT_TILE_URL, admits_subdomains=False) 36 | 37 | def get_tile(self, x, y, zoom): 38 | """ 39 | Retrieves the tile having the specified coordinates and zoom level 40 | 41 | :param x: horizontal tile number in OWM tile reference system 42 | :type x: int 43 | :param y: vertical tile number in OWM tile reference system 44 | :type y: int 45 | :param zoom: zoom level for the tile 46 | :type zoom: int 47 | :returns: a `pyowm.tiles.Tile` instance 48 | 49 | """ 50 | status, data = self.http_client.get_png( 51 | NAMED_MAP_LAYER_URL % self.map_layer + '/%s/%s/%s.png' % (zoom, x, y), 52 | params={'appid': self.API_key}) 53 | img = Image(data, ImageTypeEnum.PNG) 54 | return Tile(x, y, zoom, self.map_layer, img) 55 | 56 | def __repr__(self): 57 | return "<%s.%s - layer_name=%s>" % (__name__, self.__class__.__name__, self.map_layer) 58 | -------------------------------------------------------------------------------- /pyowm/tiles/uris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ROOT_TILE_URL = 'tile.openweathermap.org/map' 5 | 6 | NAMED_MAP_LAYER_URL = '%s' 7 | -------------------------------------------------------------------------------- /pyowm/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/utils/__init__.py -------------------------------------------------------------------------------- /pyowm/utils/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import json 6 | 7 | from pyowm.commons import exceptions 8 | from pyowm.config import DEFAULT_CONFIG 9 | from pyowm.commons.enums import SubscriptionTypeEnum 10 | 11 | 12 | def get_config_from(path_to_file): 13 | """ 14 | Loads configuration data from the supplied file and returns it. 15 | 16 | :param path_to_file: path to the configuration file 17 | :type path_to_file: str 18 | :returns: the configuration `dict` 19 | :raises: `ConfigurationNotFoundError` when the supplied filepath is not a regular file; `ConfigurationParseError` 20 | when the supplied file cannot be parsed 21 | """ 22 | assert path_to_file is not None 23 | if not os.path.isfile(path_to_file): 24 | raise exceptions.ConfigurationNotFoundError( 25 | 'Configuration file not found: {}'.format(path_to_file)) 26 | with open(path_to_file, 'r') as cf: 27 | try: 28 | config_data = json.load(cf) 29 | config_data['subscription_type'] = SubscriptionTypeEnum.lookup_by_name(config_data['subscription_type']) 30 | return config_data 31 | except Exception: 32 | raise exceptions.ConfigurationParseError() 33 | 34 | 35 | def get_default_config(): 36 | """ 37 | Returns the default PyOWM configuration. 38 | 39 | :returns: the configuration `dict` 40 | """ 41 | return DEFAULT_CONFIG 42 | 43 | 44 | def get_default_config_for_subscription_type(name): 45 | """ 46 | Returns the PyOWM configuration for a specific OWM API Plan subscription type 47 | 48 | :param name: name of the subscription type 49 | :type name: str 50 | :returns: the configuration `dict` 51 | """ 52 | assert isinstance(name, str) 53 | config = get_default_config() 54 | config['subscription_type'] = SubscriptionTypeEnum.lookup_by_name(name) 55 | return config 56 | 57 | 58 | def get_default_config_for_proxy(http_url, https_url): 59 | """ 60 | Returns the PyOWM configuration to be used behind a proxy server 61 | 62 | :param http_url: URL connection string for HTTP protocol 63 | :type http_url: str 64 | :param https_url: URL connection string for HTTPS protocol 65 | :type https_url: str 66 | :returns: the configuration `dict` 67 | """ 68 | assert isinstance(http_url, str) 69 | assert isinstance(https_url, str) 70 | config = get_default_config() 71 | config['connection']['use_proxy'] = True 72 | config['proxies']['http'] = http_url 73 | config['proxies']['https'] = https_url 74 | return config 75 | -------------------------------------------------------------------------------- /pyowm/utils/decorators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from functools import wraps 5 | import warnings 6 | 7 | 8 | def deprecated(will_be=None, on_version=None, name=None): 9 | """ 10 | Function decorator that warns about deprecation upon function invocation. 11 | :param will_be: str representing the target action on the deprecated function 12 | :param on_version: tuple representing a SW version 13 | :param name: name of the entity to be deprecated (useful when decorating 14 | __init__ methods so you can specify the deprecated class name) 15 | :return: callable 16 | """ 17 | def outer_function(function): 18 | if name is None: 19 | _name = function.__name__ 20 | else: 21 | _name = name 22 | warning_msg = '"%s" is deprecated.' % _name 23 | if will_be is not None and on_version is not None: 24 | warning_msg += " It will be %s on version %s" % ( 25 | will_be, 26 | '.'.join(map(str, on_version))) 27 | 28 | @wraps(function) 29 | def inner_function(*args, **kwargs): 30 | warnings.warn(warning_msg, 31 | category=DeprecationWarning, 32 | stacklevel=2) 33 | return function(*args, **kwargs) 34 | 35 | return inner_function 36 | 37 | return outer_function 38 | 39 | -------------------------------------------------------------------------------- /pyowm/utils/strings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import importlib 5 | 6 | 7 | def obfuscate_API_key(API_key): 8 | """ 9 | Return a mostly obfuscated version of the API Key 10 | 11 | :param API_key: input string 12 | :return: str 13 | """ 14 | if API_key is not None: 15 | return (len(API_key)-8)*'*'+API_key[-8:] 16 | 17 | 18 | def version_tuple_to_str(version_tuple, separator='.'): 19 | """ 20 | Turns something like (X, Y, Z) into "X.Y.Z" 21 | :param version_tuple: the tuple identifying a software Semantic version 22 | :type version_tuple: tuple 23 | :param separator: the character to be used as separator 24 | :type separator: str, defaults to '.' 25 | :return: str 26 | """ 27 | str_version_tuple = [str(v) for v in version_tuple] 28 | return separator.join(str_version_tuple) 29 | 30 | 31 | def class_from_dotted_path(dotted_path): 32 | """ 33 | Loads a Python class from the supplied Python dot-separated class path. 34 | The class must be visible according to the PYTHONPATH variable contents. 35 | Eg: "package.subpackage.module.MyClass" --> MyClass 36 | 37 | :param dotted_path: the dot-separated path of the class 38 | :type dotted_path: str 39 | :return: a `type` object 40 | """ 41 | assert isinstance(dotted_path, str), 'A string must be provided' 42 | tokens = dotted_path.split('.') 43 | modpath, class_name = '.'.join(tokens[:-1]), tokens[-1] 44 | return getattr(importlib.import_module(modpath), class_name) 45 | -------------------------------------------------------------------------------- /pyowm/uvindexapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/uvindexapi30/__init__.py -------------------------------------------------------------------------------- /pyowm/uvindexapi30/uris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ROOT_UV_API_URL = 'openweathermap.org/data/2.5' 5 | UV_INDEX_URL = 'uvi' 6 | UV_INDEX_FORECAST_URL = 'uvi/forecast' 7 | UV_INDEX_HISTORY_URL = 'uvi/history' 8 | -------------------------------------------------------------------------------- /pyowm/uvindexapi30/uv_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.uvindexapi30.uris import UV_INDEX_URL, UV_INDEX_FORECAST_URL, \ 5 | UV_INDEX_HISTORY_URL 6 | 7 | 8 | class UltraVioletHttpClient: 9 | 10 | """ 11 | An HTTP client class for the OWM UV web API, which is a subset of the 12 | overall OWM API. 13 | 14 | :param API_key: a Unicode object representing the OWM UV web API key 15 | :type API_key: Unicode 16 | :param httpclient: an *httpclient.HttpClient* instance that will be used to \ 17 | send requests to the OWM Air Pollution web API. 18 | :type httpclient: an *httpclient.HttpClient* instance 19 | 20 | """ 21 | 22 | def __init__(self, API_key, httpclient): 23 | self._API_key = API_key 24 | self._client = httpclient 25 | 26 | def _trim_to(self, date_object, interval): 27 | if interval == 'minute': 28 | return date_object.strftime('%Y-%m-%dT%H:%MZ') 29 | elif interval == 'hour': 30 | return date_object.strftime('%Y-%m-%dT%HZ') 31 | elif interval == 'day': 32 | return date_object.strftime('%Y-%m-%dZ') 33 | elif interval == 'month': 34 | return date_object.strftime('%Y-%mZ') 35 | elif interval == 'year': 36 | return date_object.strftime('%YZ') 37 | else: 38 | raise ValueError("The interval provided for UVIndex search " 39 | "window is invalid") 40 | 41 | def get_uvi(self, params_dict): 42 | """ 43 | Invokes the UV Index endpoint 44 | 45 | :param params_dict: dict of parameters 46 | :returns: a string containing raw JSON data 47 | :raises: *ValueError*, *APIRequestError* 48 | 49 | """ 50 | lat = str(params_dict['lat']) 51 | lon = str(params_dict['lon']) 52 | params = dict(lat=lat, lon=lon) 53 | 54 | # build request URL 55 | _, json_data = self._client.get_json(UV_INDEX_URL, params=params) 56 | return json_data 57 | 58 | def get_uvi_forecast(self, params_dict): 59 | """ 60 | Invokes the UV Index Forecast endpoint 61 | 62 | :param params_dict: dict of parameters 63 | :returns: a string containing raw JSON data 64 | :raises: *ValueError*, *APIRequestError* 65 | 66 | """ 67 | lat = str(params_dict['lat']) 68 | lon = str(params_dict['lon']) 69 | params = dict(lat=lat, lon=lon) 70 | 71 | # build request URL 72 | _, json_data = self._client.get_json(UV_INDEX_FORECAST_URL, params=params) 73 | return json_data 74 | 75 | def get_uvi_history(self, params_dict): 76 | """ 77 | Invokes the UV Index History endpoint 78 | 79 | :param params_dict: dict of parameters 80 | :returns: a string containing raw JSON data 81 | :raises: *ValueError*, *APIRequestError* 82 | 83 | """ 84 | lat = str(params_dict['lat']) 85 | lon = str(params_dict['lon']) 86 | start = str(params_dict['start']) 87 | end = str(params_dict['end']) 88 | params = dict(lat=lat, lon=lon, start=start, end=end) 89 | 90 | # build request URL 91 | _, json_data = self._client.get_json(UV_INDEX_HISTORY_URL, params=params) 92 | return json_data 93 | 94 | def __repr__(self): 95 | return "<%s.%s - httpclient=%s>" % \ 96 | (__name__, self.__class__.__name__, str(self._client)) -------------------------------------------------------------------------------- /pyowm/weatherapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/pyowm/weatherapi30/__init__.py -------------------------------------------------------------------------------- /pyowm/weatherapi30/location.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pyowm.commons import exceptions 5 | from pyowm.utils import geo 6 | 7 | 8 | class Location: 9 | """ 10 | A class representing a location in the world. A location is defined through 11 | a toponym, a couple of geographic coordinates such as longitude and 12 | latitude and a numeric identifier assigned by the OWM Weather API that uniquely 13 | spots the location in the world. Optionally, the country specification may 14 | be provided. 15 | 16 | Further reference about OWM city IDs can be found at: 17 | http://bugs.openweathermap.org/projects/api/wiki/Api_2_5_weather#3-By-city-ID 18 | 19 | :param name: the location's toponym 20 | :type name: Unicode 21 | :param lon: the location's longitude, must be between -180.0 and 180.0 22 | :type lon: int/float 23 | :param lat: the location's latitude, must be between -90.0 and 90.0 24 | :type lat: int/float 25 | :param _id: the location's OWM city ID 26 | :type ID: int 27 | :param country: the location's country (``None`` by default) 28 | :type country: Unicode 29 | :returns: a *Location* instance 30 | :raises: *ValueError* if lon or lat values are provided out of bounds 31 | """ 32 | 33 | def __init__(self, name, lon, lat, _id, country=None): 34 | self.name = name 35 | if lon is None or lat is None: 36 | raise ValueError("Either 'lon' or 'lat' must be specified") 37 | geo.assert_is_lon(lon) 38 | geo.assert_is_lat(lat) 39 | self.lon = float(lon) 40 | self.lat = float(lat) 41 | self.id = _id 42 | self.country = country 43 | 44 | def to_geopoint(self): 45 | """ 46 | Returns the geoJSON compliant representation of this location 47 | 48 | :returns: a ``pyowm.utils.geo.Point`` instance 49 | 50 | """ 51 | if self.lon is None or self.lat is None: 52 | return None 53 | return geo.Point(self.lon, self.lat) 54 | 55 | @classmethod 56 | def from_dict(cls, the_dict): 57 | """ 58 | Parses a *Location* instance out of a data dictionary. Only certain properties of the data dictionary 59 | are used: if these properties are not found or cannot be parsed, an exception is issued. 60 | 61 | :param the_dict: the input dictionary 62 | :type the_dict: `dict` 63 | :returns: a *Location* instance or ``None`` if no data is available 64 | :raises: *ParseAPIResponseError* if it is impossible to find or parse the data needed to build the result 65 | 66 | """ 67 | if the_dict is None: 68 | raise exceptions.ParseAPIResponseError('Data is None') 69 | country = None 70 | if 'sys' in the_dict and 'country' in the_dict['sys']: 71 | country = the_dict['sys']['country'] 72 | data = the_dict['city'] if 'city' in the_dict else the_dict 73 | name = data['name'] if 'name' in data else None 74 | ID = int(data['id']) if 'id' in data else None 75 | if 'coord' in data: 76 | lon = data['coord'].get('lon', 0.0) 77 | lat = data['coord'].get('lat', 0.0) 78 | elif 'station' in data and 'coord' in data['station']: 79 | if 'lon' in data['station']['coord']: 80 | lon = data['station']['coord'].get('lon', 0.0) 81 | elif 'lng' in data['station']['coord']: 82 | lon = data['station']['coord'].get('lng', 0.0) 83 | else: 84 | lon = 0.0 85 | lat = data['station']['coord'].get('lat', 0.0) 86 | elif 'lat' in the_dict and 'lon' in the_dict: 87 | lat = the_dict['lat'] 88 | lon = the_dict['lon'] 89 | else: 90 | raise KeyError("Impossible to read geographical coordinates from JSON") 91 | if 'country' in data: 92 | country = data['country'] 93 | return Location(name, lon, lat, ID, country) 94 | 95 | def to_dict(self): 96 | """Dumps object to a dictionary 97 | 98 | :returns: a `dict` 99 | 100 | """ 101 | return {'name': self.name, 102 | 'coordinates': {'lon': self.lon, 'lat': self.lat}, 103 | 'ID': self.id, 104 | 'country': self.country} 105 | 106 | def __repr__(self): 107 | return "<%s.%s - id=%s, name=%s, lon=%s, lat=%s>" % (__name__, \ 108 | self.__class__.__name__, self.id, self.name, str(self.lon), \ 109 | str(self.lat)) -------------------------------------------------------------------------------- /pyowm/weatherapi30/one_call.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional 2 | 3 | from pyowm.commons import exceptions 4 | from pyowm.utils import geo 5 | from pyowm.weatherapi30.weather import Weather 6 | from pyowm.weatherapi30.national_weather_alert import NationalWeatherAlert 7 | 8 | 9 | class OneCall: 10 | 11 | def __init__(self, 12 | lat: Union[int, float], 13 | lon: Union[int, float], 14 | timezone: str, 15 | current: Weather, 16 | forecast_minutely: Optional[Weather] = None, 17 | forecast_hourly: Optional[Weather] = None, 18 | forecast_daily: Optional[Weather] = None, 19 | national_weather_alerts: Optional[list] = None 20 | ) -> None: 21 | geo.assert_is_lat(lat) 22 | self.lat = lat 23 | 24 | geo.assert_is_lon(lon) 25 | self.lon = lon 26 | 27 | self.timezone = timezone 28 | 29 | if current is None: 30 | raise ValueError("'current' must be set") 31 | self.current = current 32 | self.forecast_minutely = forecast_minutely 33 | self.forecast_hourly = forecast_hourly 34 | self.forecast_daily = forecast_daily 35 | self.national_weather_alerts = national_weather_alerts 36 | 37 | def __repr__(self): 38 | return "<%s.%s - lat=%s, lon=%s, retrieval_time=%s>" % ( 39 | __name__, self.__class__.__name__, self.lat, self.lon, 40 | self.current.reference_time() if self.current else None) 41 | 42 | def to_geopoint(self): 43 | """ 44 | Returns the geoJSON compliant representation of the place for this One Call 45 | 46 | :returns: a ``pyowm.utils.geo.Point`` instance 47 | 48 | """ 49 | if self.lon is None or self.lat is None: 50 | return None 51 | return geo.Point(self.lon, self.lat) 52 | 53 | 54 | @classmethod 55 | def from_dict(cls, the_dict: dict): 56 | """ 57 | Parses a *OneCall* instance out of a data dictionary. Only certain properties of the data dictionary 58 | are used: if these properties are not found or cannot be parsed, an exception is issued. 59 | 60 | :param the_dict: the input dictionary 61 | :type the_dict: `dict` 62 | :returns: a *OneCall* instance or ``None`` if no data is available 63 | :raises: *ParseAPIResponseError* if it is impossible to find or parse the 64 | data needed to build the result, *APIResponseError* if the input dict embeds an HTTP status error 65 | 66 | """ 67 | 68 | if the_dict is None: 69 | raise exceptions.ParseAPIResponseError('Data is None') 70 | 71 | # Check if server returned errors: this check overcomes the lack of use 72 | # of HTTP error status codes by the OWM API 3.0. This mechanism is 73 | # supposed to be deprecated as soon as the API fully adopts HTTP for 74 | # conveying errors to the clients 75 | if 'message' in the_dict and 'cod' in the_dict: 76 | if the_dict['cod'] == "404": 77 | return None 78 | elif the_dict['cod'] == "429": 79 | raise exceptions.APIResponseError("OWM API: error - Exceeded call limit", the_dict['cod']) 80 | else: 81 | raise exceptions.APIResponseError("OWM API: error - response payload", the_dict['cod']) 82 | 83 | try: 84 | current = Weather.from_dict(the_dict["current"]) 85 | minutely = None 86 | if "minutely" in the_dict: 87 | minutely = [Weather.from_dict(item) for item in the_dict["minutely"]] 88 | hourly = None 89 | if "hourly" in the_dict: 90 | hourly = [Weather.from_dict(item) for item in the_dict["hourly"]] 91 | daily = None 92 | if "daily" in the_dict: 93 | daily = [Weather.from_dict(item) for item in the_dict["daily"]] 94 | 95 | except KeyError: 96 | raise exceptions.ParseAPIResponseError(f"{__name__}: impossible to read weather info from input data") 97 | 98 | alerts = None 99 | if 'alerts' in the_dict: 100 | alerts = [NationalWeatherAlert.from_dict(item) for item in the_dict['alerts']] 101 | 102 | return OneCall( 103 | lat=the_dict.get("lat", None), 104 | lon=the_dict.get("lon", None), 105 | timezone=the_dict.get("timezone", None), 106 | current=current, 107 | forecast_minutely=minutely, 108 | forecast_hourly=hourly, 109 | forecast_daily=daily, 110 | national_weather_alerts=alerts 111 | ) 112 | -------------------------------------------------------------------------------- /pyowm/weatherapi30/uris.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | ROOT_WEATHER_API = 'openweathermap.org/data/3.0' 6 | OBSERVATION_URI = 'weather' 7 | GROUP_OBSERVATIONS_URI = 'group' 8 | STATION_URI = 'station' 9 | FIND_OBSERVATIONS_URI = 'find' 10 | FIND_STATION_URI = 'station/find' 11 | BBOX_STATION_URI = 'box/station' 12 | BBOX_CITY_URI = 'box/city' 13 | THREE_HOURS_FORECAST_URI = 'forecast' 14 | DAILY_FORECAST_URI = 'forecast/daily' 15 | STATION_WEATHER_HISTORY_URI = 'history/station' 16 | ONE_CALL_URI = 'onecall' 17 | ONE_CALL_HISTORICAL_URI = ONE_CALL_URI + '/timemachine' 18 | ICONS_BASE_URI = 'http://openweathermap.org/img/wn/%s%s.png' 19 | -------------------------------------------------------------------------------- /pyowm/weatherapi30/weathercoderegistry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | WEATHER_CODES_INTERVALS = { 6 | "rain": [{ 7 | "start": 500, 8 | "end": 531 9 | }, 10 | { 11 | "start": 300, 12 | "end": 321 13 | }], 14 | "sun": [{ 15 | "start": 800, 16 | "end": 800 17 | }], 18 | "clouds": [{ 19 | "start": 801, 20 | "end": 804 21 | }], 22 | "fog": [{ 23 | "start": 741, 24 | "end": 741 25 | }], 26 | "haze": [{ 27 | "start": 721, 28 | "end": 721 29 | }], 30 | "mist": [{ 31 | "start": 701, 32 | "end": 701 33 | }], 34 | "snow": [{ 35 | "start": 600, 36 | "end": 622 37 | }], 38 | "tornado": [{ 39 | "start": 781, 40 | "end": 781 41 | }, 42 | { 43 | "start": 900, 44 | "end": 900 45 | }], 46 | "storm": [{ 47 | "start": 901, 48 | "end": 901 49 | }, 50 | { 51 | "start": 960, 52 | "end": 961 53 | }], 54 | "hurricane": [{ 55 | "start": 902, 56 | "end": 902 57 | }, 58 | { 59 | "start": 962, 60 | "end": 962 61 | }] 62 | } 63 | 64 | 65 | class WeatherCodeRegistry: 66 | 67 | """ 68 | A registry class for looking up weather statuses from weather codes. 69 | 70 | :param code_ranges_dict: a dict containing the mapping between weather 71 | statuses (eg: "sun","clouds",etc) and weather code ranges 72 | :type code_ranges_dict: dict 73 | :returns: a *WeatherCodeRegistry* instance 74 | 75 | """ 76 | 77 | def __init__(self, code_ranges_dict): 78 | assert isinstance(code_ranges_dict, dict) 79 | self._code_ranges_dict = code_ranges_dict 80 | 81 | def status_for(self, code): 82 | """ 83 | Returns the weather status related to the specified weather status 84 | code, if any is stored, ``None`` otherwise. 85 | 86 | :param code: the weather status code whose status is to be looked up 87 | :type code: int 88 | :returns: the weather status str or ``None`` if the code is not mapped 89 | """ 90 | is_in = lambda start, end, n: start <= n <= end 91 | for status in self._code_ranges_dict: 92 | for _range in self._code_ranges_dict[status]: 93 | if is_in(_range['start'],_range['end'],code): 94 | return status 95 | return None 96 | 97 | @classmethod 98 | def get_instance(cls): 99 | """ 100 | Factory method returning the default weather code registry 101 | :return: a `WeatherCodeRegistry` instance 102 | """ 103 | return WeatherCodeRegistry(WEATHER_CODES_INTERVALS) 104 | 105 | def __repr__(self): 106 | return "<%s.%s>" % (__name__, self.__class__.__name__) -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | python: 2 | version: 3.7 3 | 4 | requirements_file: dev-requirements.txt -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | geojson>=2.3.0,<3 2 | PySocks>=1.7.1,<2 3 | requests>=2.20.0,<3 4 | requests[socks] -------------------------------------------------------------------------------- /scripts/fill_entity_template.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | if __name__ == '__main__': 5 | 6 | import sys 7 | from jinja2 import Template 8 | 9 | param_1 = sys.argv[1] 10 | entity_name = param_1.title() 11 | 12 | try: 13 | param_2 = sys.argv[2] 14 | is_iterable = param_2 == '--iterable' 15 | except: 16 | is_iterable = False 17 | 18 | with open('new_entity.template.txt') as i: 19 | tpl = Template(i.read()) 20 | rendered_text = tpl.render(entity_name=entity_name, is_iterable=is_iterable) 21 | with open('{}.py'.format(entity_name.lower()), 'w') as o: 22 | o.write(rendered_text) 23 | -------------------------------------------------------------------------------- /scripts/generate_new_entity.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | 5 | echo "Generating boilerplate for new entity..." 6 | 7 | python3 fill_entity_template.py "$@" 8 | 9 | echo 'Done' -------------------------------------------------------------------------------- /scripts/generate_pypi_dist.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | 5 | cd .. 6 | rm -fv build/* 7 | rm -fv dist/* 8 | echo 'Generating source distribution...' 9 | python3.7 setup.py sdist 10 | 11 | echo 'Generating .egg distributions...' 12 | python3.7 setup.py bdist_egg 13 | python3.8 setup.py bdist_egg 14 | 15 | echo 'Generating Wheel distributions...' 16 | python3 setup.py sdist bdist_wheel 17 | 18 | echo 'Generating Windows self-extracting .exe ...' 19 | python3 setup.py bdist_wininst 20 | 21 | 22 | echo 'Done' -------------------------------------------------------------------------------- /scripts/generate_sphinx_docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo '*** Generating Sphinx HTML documentations...' 4 | cd ../sphinx 5 | make clean 6 | make html 7 | 8 | if [ "$?" -ne 0 ]; then 9 | echo "*** Errors occurred" 10 | exit 1 11 | fi 12 | 13 | echo '*** Done' 14 | -------------------------------------------------------------------------------- /scripts/new_entity.template.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class {{ entity_name }}: 6 | """ 7 | A class representing metadata for a satellite-acquired image 8 | 9 | :param ex: example 10 | :type ex: str 11 | :returns: something 12 | """ 13 | 14 | def __init__(self, ex=None): 15 | raise NotImplementedError() 16 | 17 | @classmethod 18 | def from_dict(cls, the_dict): 19 | raise NotImplementedError() 20 | 21 | def to_dict(self): 22 | raise NotImplementedError() 23 | {% if is_iterable %} 24 | def __len__(self): 25 | raise NotImplementedError() 26 | 27 | def __contains__(self, item): 28 | raise NotImplementedError() 29 | 30 | def __iter__(self): 31 | raise NotImplementedError() 32 | {% endif %} 33 | def __repr__(self): 34 | return '<{}.{}>'.format(__name__, self.__class__.__name__) 35 | -------------------------------------------------------------------------------- /scripts/publish_to_pypi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit 3 | 4 | cd .. 5 | echo 'Uploading all artifacts to PyPi' 6 | twine upload dist/* 7 | echo 'done' -------------------------------------------------------------------------------- /scripts/update-pipfilelock.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | THIS_FOLDER="$(pwd)" 4 | PYOWM_FOLDER="$(dirname $(pwd))" 5 | 6 | echo '*** Creating temporary virtualenv...' 7 | virtualenv pipfilelocker 8 | source pipfilelocker/bin/activate 9 | pip install pipenv 10 | 11 | echo '*** Updating Pipfile.lock...' 12 | cd "$PYOWM_FOLDER" 13 | pipenv lock 14 | 15 | echo '*** Removing temporary virtualenv...' 16 | deactivate 17 | cd "$THIS_FOLDER" 18 | rm -rf pipfilelocker 19 | 20 | echo '*** Done' -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | from pyowm.__version__ import __author__, __author_email__, __description__, __license__, __title__,\ 5 | __url__, __version__ 6 | 7 | with open('README.md', 'r') as readme: 8 | long_description = readme.read() 9 | 10 | setup( 11 | name=__title__, 12 | version=__version__, 13 | description=__description__, 14 | author=__author__, 15 | author_email=__author_email__, 16 | url=__url__, 17 | packages=find_packages(exclude=['tests','tests.*']), 18 | long_description=long_description, 19 | long_description_content_type='text/markdown', 20 | include_package_data=True, 21 | install_requires=[ 22 | 'requests>=2.20.0,<3', 23 | 'geojson>=2.3.0,<3', 24 | 'PySocks>=1.7.1,<2', 25 | 'requests[socks]' 26 | ], 27 | python_requires='>=3.7', 28 | classifiers=[ 29 | "License :: OSI Approved :: MIT License", 30 | "Programming Language :: Python", 31 | "Programming Language :: Python :: 3.7", 32 | "Programming Language :: Python :: 3.8", 33 | "Programming Language :: Python :: 3.9", 34 | "Natural Language :: English", 35 | "Operating System :: OS Independent", 36 | "Development Status :: 5 - Production/Stable", 37 | "Intended Audience :: Developers", 38 | "Topic :: Software Development :: Libraries"], 39 | package_data={ 40 | '': ['*.bz2', '*.md', '*.txt', '*.json'] 41 | }, 42 | keywords='openweathermap web api client weather forecast uv alerting owm pollution meteostation agro agriculture', 43 | license=__license__ 44 | ) 45 | -------------------------------------------------------------------------------- /sphinx/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributing is easy and welcome 4 | 5 | You can contribute to PyOWM in a lot of ways: 6 | 7 | - reporting a reproducible defect (eg. bug, installation crash, ...) 8 | - make a wish for a reasonable new feature 9 | - increase the test coverage 10 | - refactor the code 11 | - improve PyOWM reach on platforms (eg. bundle it for Linux distros, managers, coding, testing, packaging, reporting issues) are welcome! 12 | 13 | And last but not least... use it! Use PyOWM in your own projects, as [lots of people already do](https://github.com/csparpa/pyowm/wiki/Community-Projects-using-PyOWM). 14 | 15 | 16 | In order to get started, follow these simple steps: 17 | 18 | 1. First, meet the community and wave hello! You can join the **[PyOWM public Slack team](https://pyowm.slack.com)** by signing up [here](http://pyowm-slackin.herokuapp.com/) 19 | 2. Depending on how you want to contribute, take a look at one of the following sections 20 | 3. Don't forget tell @csparpa or the community to add yourself to the `CONTRIBUTORS.md` file - or do it yourself if you're contributing on code 21 | 22 | 23 | # Reporting a PyOWM bug 24 | That's simple: what you need to do is just open a new issue on GitHub. 25 | 26 | 27 | ## Bug reports - general principles 28 | In order to allow the community to understand what the bug is, *you should provide as much information as possible* on it. 29 | Vague or succinct bug reports are not useful and will very likely result in follow ups needed. 30 | 31 | *Only bugs related to PyOWM will be addressed*: it might be that you're using PyOWM in a broader context (eg. a web application) 32 | so bugs affecting the broader context are out of scope - unless they are caused in chain to PyOWM issues. 33 | 34 | Also, please do understand that we can only act on *reproducible bugs*: this means that a bug does not exist if it is 35 | not possible to reproduce it by two different persons. So please provide facts, not "smells of a potential bug" 36 | 37 | 38 | ## What a good bug report should contain 39 | These info are part of a good bug report: 40 | - brief description of the issue 41 | - *how to reproduce the issue* 42 | - what is the impacted PyOWM issue 43 | - what Python version are you running PyOWM with 44 | - what is your operating system 45 | - stacktrace of the error/crash if available 46 | - if you did a bit of research yourself and/or have a fix in mind, just suggest please :) 47 | - (optional) transcripts from the shell/logfiles or screenshots 48 | 49 | 50 | # Requesting for a new PyOWM feature 51 | That's simple as well! 52 | 53 | 1. Open an issue on GitHub (describe with as much detail as possible the feature you're proposing - and also 54 | 2. Depending on the entity of the request: 55 | - if it's going to be a breaking change, the feature will be scheduled for embedding into the next major release - so no code shall be provided by then 56 | - if it's only an enhancement, you might proceed with submitting the code yourself! 57 | 58 | 59 | # Contributing on code 60 | This applies to all kind of works on code (fixing bugs, developing new features, refactoring code, adding tests...) 61 | 62 | A few simple steps: 63 | 1. Fork the PyOWM repository on GitHub 64 | 2. Install the development dependencies on your local development setup 65 | 3. On your fork, work on the **development branch** (_not the master branch!!!_) or on a **ad-hoc feature branch**. Don't forget to insert your name in the `CONTRIBUTORS.md` file! 66 | 4. TEST YOUR CODE please! 67 | 5. DOCUMENT YOUR CODE - especially if new features/complex patches are introduced 68 | 6. Submit a [pull request](https://help.github.com/articles/about-pull-requests/) 69 | 70 | 71 | ## Installing development dependencies 72 | In order to develop code and run tests for PyOWM, you must have installed the dev dependencies. From the project root folder, 73 | just run: 74 | 75 | `pip install -r dev-requirements.txt` 76 | 77 | It is advised that you do it on a [virtualenv](https://virtualenv.pypa.io/en/stable/). 78 | 79 | ## Guidelines for code branching 80 | Simple ones: 81 | 82 | - the "develop" branch contains work-in-progress code 83 | - the "master" branch will contain only stable code and the "develop" branch will be merged back into it only when a milestone is completed or hotfixes need to be applied. Merging of "develop" into "master" will be done by @csparpa when releasing - so please **never apply your modifications to the master branch!!!** 84 | 85 | 86 | ## Guidelines for code testing 87 | Main principles: 88 | 89 | - Each software functionality should have a related unit test 90 | - Each bug should have at least one regression test associated 91 | 92 | 93 | # Contributing on PyOWM bundling/distributing 94 | Please open a GitHub issue and get in touch to discuss your idea! 95 | -------------------------------------------------------------------------------- /sphinx/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/sphinx/favicon.ico -------------------------------------------------------------------------------- /sphinx/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/sphinx/logo.png -------------------------------------------------------------------------------- /sphinx/pyowm.agroapi10.rst: -------------------------------------------------------------------------------- 1 | pyowm.agroapi10 package 2 | ======================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyowm.agroapi10.agro_manager module 8 | ----------------------------------- 9 | 10 | .. automodule:: pyowm.agroapi10.agro_manager 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyowm.agroapi10.enums module 16 | ---------------------------- 17 | 18 | .. automodule:: pyowm.agroapi10.enums 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyowm.agroapi10.imagery module 24 | ------------------------------ 25 | 26 | .. automodule:: pyowm.agroapi10.imagery 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pyowm.agroapi10.polygon module 32 | ------------------------------ 33 | 34 | .. automodule:: pyowm.agroapi10.polygon 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pyowm.agroapi10.search module 40 | ----------------------------- 41 | 42 | .. automodule:: pyowm.agroapi10.search 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | pyowm.agroapi10.soil module 48 | --------------------------- 49 | 50 | .. automodule:: pyowm.agroapi10.soil 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | pyowm.agroapi10.uris module 56 | --------------------------- 57 | 58 | .. automodule:: pyowm.agroapi10.uris 59 | :members: 60 | :undoc-members: 61 | :show-inheritance: 62 | 63 | 64 | Module contents 65 | --------------- 66 | 67 | .. automodule:: pyowm.agroapi10 68 | :members: 69 | :undoc-members: 70 | :show-inheritance: 71 | -------------------------------------------------------------------------------- /sphinx/pyowm.airpollutionapi30.rst: -------------------------------------------------------------------------------- 1 | pyowm.airpollutionapi30 package 2 | =============================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | 10 | Submodules 11 | ---------- 12 | 13 | pyowm.airpollutionapi30.airpollution_client module 14 | -------------------------------------------------- 15 | 16 | .. automodule:: pyowm.airpollutionapi30.airpollution_client 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | pyowm.airpollutionapi30.airpollution_manager module 22 | --------------------------------------------------- 23 | 24 | .. automodule:: pyowm.airpollutionapi30.airpollution_manager 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | pyowm.airpollutionapi30.coindex module 30 | -------------------------------------- 31 | 32 | .. automodule:: pyowm.airpollutionapi30.coindex 33 | :members: 34 | :undoc-members: 35 | :show-inheritance: 36 | 37 | pyowm.airpollutionapi30.ozone module 38 | ------------------------------------ 39 | 40 | .. automodule:: pyowm.airpollutionapi30.ozone 41 | :members: 42 | :undoc-members: 43 | :show-inheritance: 44 | 45 | 46 | pyowm.airpollutionapi30.no2index module 47 | --------------------------------------- 48 | 49 | .. automodule:: pyowm.airpollutionapi30.no2index 50 | :members: 51 | :undoc-members: 52 | :show-inheritance: 53 | 54 | pyowm.airpollutionapi30.so2index module 55 | --------------------------------------- 56 | 57 | .. automodule:: pyowm.airpollutionapi30.so2index 58 | :members: 59 | :undoc-members: 60 | :show-inheritance: 61 | 62 | 63 | Module contents 64 | --------------- 65 | 66 | .. automodule:: pyowm.airpollutionapi30 67 | :members: 68 | :undoc-members: 69 | :show-inheritance: 70 | -------------------------------------------------------------------------------- /sphinx/pyowm.alertapi30.rst: -------------------------------------------------------------------------------- 1 | pyowm.alertapi30 package 2 | ======================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyowm.alertapi30.alert_manager module 8 | ------------------------------------- 9 | 10 | .. automodule:: pyowm.alertapi30.alert_manager 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | pyowm.alertapi30.alert module 17 | ----------------------------- 18 | 19 | .. automodule:: pyowm.alertapi30.alert 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | 25 | pyowm.alertapi30.condition module 26 | --------------------------------- 27 | 28 | .. automodule:: pyowm.alertapi30.condition 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | 34 | pyowm.alertapi30.enums module 35 | ----------------------------- 36 | 37 | .. automodule:: pyowm.alertapi30.enums 38 | :members: 39 | :undoc-members: 40 | :show-inheritance: 41 | 42 | 43 | pyowm.alertapi30.trigger module 44 | ------------------------------- 45 | 46 | .. automodule:: pyowm.alertapi30.trigger 47 | :members: 48 | :undoc-members: 49 | :show-inheritance: 50 | 51 | 52 | Module contents 53 | --------------- 54 | 55 | .. automodule:: pyowm.alertapi30 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | -------------------------------------------------------------------------------- /sphinx/pyowm.commons.rst: -------------------------------------------------------------------------------- 1 | pyowm.commons package 2 | ===================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyowm.commons.cityids 8 | 9 | pyowm.commons.cityidregistry module 10 | ----------------------------------- 11 | 12 | .. automodule:: pyowm.commons.cityidregistry 13 | :members: 14 | :undoc-members: 15 | :show-inheritance: 16 | 17 | pyowm.commons.databoxes module 18 | ------------------------------ 19 | 20 | .. automodule:: pyowm.commons.databoxes 21 | :members: 22 | :undoc-members: 23 | :show-inheritance: 24 | 25 | pyowm.commons.enums module 26 | -------------------------- 27 | 28 | .. automodule:: pyowm.commons.enums 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | 33 | pyowm.commons.exceptions module 34 | ------------------------------- 35 | 36 | .. automodule:: pyowm.commons.exceptions 37 | :members: 38 | :undoc-members: 39 | :show-inheritance: 40 | 41 | pyowm.commons.http_client module 42 | ----------------------------------- 43 | 44 | .. automodule:: pyowm.commons.http_client 45 | :members: 46 | :undoc-members: 47 | :show-inheritance: 48 | 49 | pyowm.commons.image module 50 | -------------------------- 51 | 52 | .. automodule:: pyowm.commons.image 53 | :members: 54 | :undoc-members: 55 | :show-inheritance: 56 | 57 | pyowm.commons.tile module 58 | ------------------------- 59 | 60 | .. automodule:: pyowm.commons.tile 61 | :members: 62 | :undoc-members: 63 | :show-inheritance: 64 | 65 | 66 | Module contents 67 | --------------- 68 | 69 | .. automodule:: pyowm.commons 70 | :members: 71 | :undoc-members: 72 | :show-inheritance: 73 | -------------------------------------------------------------------------------- /sphinx/pyowm.geocodingapi10.rst: -------------------------------------------------------------------------------- 1 | pyowm.geocodingapi10 package 2 | ============================ 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyowm.geocodingapi10.geocoding_manager module 8 | --------------------------------------------- 9 | 10 | .. automodule:: pyowm.geocodingapi10.geocoding_manager 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | Module contents 16 | --------------- 17 | 18 | .. automodule:: pyowm.geocodingapi10 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: -------------------------------------------------------------------------------- /sphinx/pyowm.rst: -------------------------------------------------------------------------------- 1 | pyowm package 2 | ============= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | pyowm.agroapi10 10 | pyowm.alertapi30 11 | pyowm.commons 12 | pyowm.airpollutionapi30 13 | pyowm.geocodingapi10 14 | pyowm.stationsapi30 15 | pyowm.tiles 16 | pyowm.utils 17 | pyowm.uvindexapi30 18 | pyowm.weatherapi25 19 | 20 | Submodules 21 | ---------- 22 | 23 | pyowm.config module 24 | ------------------- 25 | 26 | .. automodule:: pyowm.config 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | pyowm.constants module 32 | ---------------------- 33 | 34 | .. automodule:: pyowm.constants 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pyowm.owm module 40 | ---------------- 41 | 42 | .. automodule:: pyowm.owm 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: pyowm 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /sphinx/pyowm.stationsapi30.rst: -------------------------------------------------------------------------------- 1 | pyowm.stationsapi30 package 2 | =========================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | 10 | Submodules 11 | ---------- 12 | 13 | pyowm.stationsapi30.buffer module 14 | --------------------------------- 15 | 16 | .. automodule:: pyowm.stationsapi30.buffer 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | pyowm.stationsapi30.measurement module 22 | -------------------------------------- 23 | 24 | .. automodule:: pyowm.stationsapi30.measurement 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | pyowm.stationsapi30.persistence_backend module 30 | ---------------------------------------------- 31 | 32 | .. automodule:: pyowm.stationsapi30.persistence_backend 33 | :members: 34 | :undoc-members: 35 | :show-inheritance: 36 | 37 | pyowm.stationsapi30.station module 38 | ---------------------------------- 39 | 40 | .. automodule:: pyowm.stationsapi30.station 41 | :members: 42 | :undoc-members: 43 | :show-inheritance: 44 | 45 | pyowm.stationsapi30.stations_manager module 46 | ------------------------------------------- 47 | 48 | .. automodule:: pyowm.stationsapi30.stations_manager 49 | :members: 50 | :undoc-members: 51 | :show-inheritance: 52 | 53 | Module contents 54 | --------------- 55 | 56 | .. automodule:: pyowm.stationsapi30 57 | :members: 58 | :undoc-members: 59 | :show-inheritance: 60 | -------------------------------------------------------------------------------- /sphinx/pyowm.tiles.rst: -------------------------------------------------------------------------------- 1 | pyowm.tiles package 2 | =================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyowm.tiles.enums module 8 | ------------------------ 9 | 10 | .. automodule:: pyowm.tiles.enums 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyowm.tiles.tile_manager module 16 | ------------------------------- 17 | 18 | .. automodule:: pyowm.tiles.tile_manager 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | 25 | Module contents 26 | --------------- 27 | 28 | .. automodule:: pyowm.tiles 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | -------------------------------------------------------------------------------- /sphinx/pyowm.utils.rst: -------------------------------------------------------------------------------- 1 | pyowm.utils package 2 | =================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyowm.utils.config module 8 | ------------------------- 9 | 10 | .. automodule:: pyowm.utils.config 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyowm.utils.decorators module 16 | ----------------------------- 17 | 18 | .. automodule:: pyowm.utils.decorators 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | pyowm.utils.geo module 25 | ---------------------- 26 | 27 | .. automodule:: pyowm.utils.geo 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | 33 | pyowm.utils.measurables module 34 | ------------------------------ 35 | 36 | .. automodule:: pyowm.utils.measurables 37 | :members: 38 | :undoc-members: 39 | :show-inheritance: 40 | 41 | pyowm.utils.formatting module 42 | ----------------------------- 43 | 44 | .. automodule:: pyowm.utils.formatting 45 | :members: 46 | :undoc-members: 47 | :show-inheritance: 48 | 49 | pyowm.utils.timestamps module 50 | ----------------------------- 51 | 52 | .. automodule:: pyowm.utils.timestamps 53 | :members: 54 | :undoc-members: 55 | :show-inheritance: 56 | 57 | 58 | pyowm.utils.weather module 59 | -------------------------- 60 | 61 | .. automodule:: pyowm.utils.weather 62 | :members: 63 | :undoc-members: 64 | :show-inheritance: 65 | 66 | 67 | Module contents 68 | --------------- 69 | 70 | .. automodule:: pyowm.utils 71 | :members: 72 | :undoc-members: 73 | :show-inheritance: 74 | -------------------------------------------------------------------------------- /sphinx/pyowm.uvindexapi30.rst: -------------------------------------------------------------------------------- 1 | pyowm.uvindexapi30 package 2 | ========================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | 10 | Submodules 11 | ---------- 12 | 13 | pyowm.uvindexapi30.uvindex module 14 | --------------------------------- 15 | 16 | .. automodule:: pyowm.uvindexapi30.uvindex 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | 22 | pyowm.uvindexapi30.uvindex_manager module 23 | ----------------------------------------- 24 | 25 | .. automodule:: pyowm.uvindexapi30.uvindex_manager 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | 31 | pyowm.uvindexapi30.uv_client module 32 | ----------------------------------- 33 | 34 | .. automodule:: pyowm.uvindexapi30.uv_client 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | pyowm.uvindexapi30.uris module 40 | ------------------------------ 41 | 42 | .. automodule:: pyowm.uvindexapi30.uris 43 | :members: 44 | :undoc-members: 45 | :show-inheritance: 46 | 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: pyowm.uvindexapi30 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | -------------------------------------------------------------------------------- /sphinx/pyowm.weatherapi25.rst: -------------------------------------------------------------------------------- 1 | pyowm.weatherapi25 package 2 | ========================== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | 10 | Submodules 11 | ---------- 12 | 13 | pyowm.weatherapi25.forecast module 14 | ---------------------------------- 15 | 16 | .. automodule:: pyowm.weatherapi25.forecast 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | pyowm.weatherapi25.forecaster module 22 | ------------------------------------ 23 | 24 | .. automodule:: pyowm.weatherapi25.forecaster 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | pyowm.weatherapi25.historian module 30 | ----------------------------------- 31 | 32 | .. automodule:: pyowm.weatherapi25.historian 33 | :members: 34 | :undoc-members: 35 | :show-inheritance: 36 | 37 | pyowm.weatherapi25.location module 38 | ---------------------------------- 39 | 40 | .. automodule:: pyowm.weatherapi25.location 41 | :members: 42 | :undoc-members: 43 | :show-inheritance: 44 | 45 | pyowm.weatherapi25.national_weather_alert module 46 | ------------------------------------------------ 47 | 48 | .. automodule:: pyowm.weatherapi25.national_weather_alert 49 | :members: 50 | :undoc-members: 51 | :show-inheritance: 52 | 53 | pyowm.weatherapi25.observation module 54 | ------------------------------------- 55 | 56 | .. automodule:: pyowm.weatherapi25.observation 57 | :members: 58 | :undoc-members: 59 | :show-inheritance: 60 | 61 | pyowm.weatherapi25.one_call module 62 | ---------------------------------- 63 | 64 | .. automodule:: pyowm.weatherapi25.one_call 65 | :members: 66 | :undoc-members: 67 | :show-inheritance: 68 | 69 | pyowm.weatherapi25.stationhistory module 70 | ---------------------------------------- 71 | 72 | .. automodule:: pyowm.weatherapi25.stationhistory 73 | :members: 74 | :undoc-members: 75 | :show-inheritance: 76 | 77 | pyowm.weatherapi25.uris module 78 | ------------------------------ 79 | 80 | .. automodule:: pyowm.weatherapi25.uris 81 | :members: 82 | :undoc-members: 83 | :show-inheritance: 84 | 85 | 86 | pyowm.weatherapi25.weather module 87 | --------------------------------- 88 | 89 | .. automodule:: pyowm.weatherapi25.weather 90 | :members: 91 | :undoc-members: 92 | :show-inheritance: 93 | 94 | pyowm.weatherapi25.weather_manager module 95 | ----------------------------------------- 96 | 97 | .. automodule:: pyowm.weatherapi25.weather_manager 98 | :members: 99 | :undoc-members: 100 | :show-inheritance: 101 | 102 | pyowm.weatherapi25.weathercoderegistry module 103 | --------------------------------------------- 104 | 105 | .. automodule:: pyowm.weatherapi25.weathercoderegistry 106 | :members: 107 | :undoc-members: 108 | :show-inheritance: 109 | 110 | 111 | Module contents 112 | --------------- 113 | 114 | .. automodule:: pyowm.weatherapi25 115 | :members: 116 | :undoc-members: 117 | :show-inheritance: 118 | -------------------------------------------------------------------------------- /sphinx/v3/city-id-registry-examples.md: -------------------------------------------------------------------------------- 1 | # City ID Registry usage examples 2 | 3 | Using city IDS instead of toponyms or geographic coordinates is the preferred way of querying the OWM weather API 4 | 5 | You can obtain the city ID for your toponyms/geocoords of interest via the `City ID Registry`. 6 | 7 | Please refer to the `Code Recipes` page, section: `Identifying cities and places via city IDs`, to get info about it -------------------------------------------------------------------------------- /sphinx/v3/exceptions.md: -------------------------------------------------------------------------------- 1 | # Exceptions 2 | 3 | PyOWM uses custom exception classes. Here you can learn which classes are used and when such exceptions are cast by the library 4 | 5 | ## Exceptions Hierarchy 6 | 7 | ``` 8 | Exception 9 | | 10 | |___PyOWMError 11 | | 12 | |___ConfigurationError 13 | | | 14 | | |__ConfigurationNotFoundError 15 | | |__ConfigurationParseError 16 | | 17 | |___APIRequestError 18 | | | 19 | | |__BadGatewayError 20 | | |__TimeoutError 21 | | |__InvalidSSLCertificateError 22 | | 23 | |___APIResponseError 24 | | 25 | |__NotFoundError 26 | |__UnauthorizedError 27 | |__ParseAPIResponseError 28 | ``` 29 | 30 | ## Exception root causes 31 | 32 | * `PyOWMError` is the base class. Never raised directly 33 | * `ConfigurationError` parent class for configuration-related exceptions. Never raised directly 34 | * `ConfigurationNotFoundError` raised when trying to load configuration from a non-existent file 35 | * `ConfigurationParseError` raised when configuration can be loaded from the file but is in a wrong, unparsable format 36 | * `APIRequestError` base class for network/infrastructural issues when invoking OWM APIs 37 | * `BadGatewayError` raised when upstream OWM API backends suffer communication issues. 38 | * `TimeoutError` raised when calls to the API suffer timeout due to slow response times upstream 39 | * `InvalidSSLCertificateError` raised when it is impossible to verify the SSL certificates provided by the OWM APIs 40 | * `APIResponseError` base class for non-ok API responses from OWM APIs 41 | * `NotFoundError` raised when the user tries to access resources that do not exist on the OWM APIs 42 | * `UnauthorizedError` raised when the user tries to access resources she is not authorized to access (eg. you need a paid API subscription) 43 | * `ParseAPIResponseError` raised upon impossibility to parse the JSON payload of API responses 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /sphinx/v3/faq.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | Common fixes to common errors reported by the Community 3 | 4 | ## AttributeError: 'OWM25' object has no attribute 'xxx' 5 | Your code looks like: 6 | 7 | ```python 8 | >>> from pyowm import OWM 9 | >>> owm = OWM('your-api-key-here') 10 | >>> mgr = owm.weather_manager() 11 | 12 | AttributeError: 'OWM25' object has no attribute 'weather_manager' 13 | ``` 14 | 15 | This happens because **you are not running PyOWM v3** and this is because your code is currently **based on an old Python 2 setup** 16 | Python 2 is officially dead and should be removed in favor of Python 3. 17 | 18 | What you should do is: 19 | - install Python 3.6+ 20 | - install PyOWM v3+ with `pip3 install pyowm` 21 | 22 | The above snippet should just work fine then. 23 | 24 | Remember to port the rest of your code to Python 3: everything related to PyOWM v2 can be ported using [this guide](https://pyowm.readthedocs.io/en/latest/v3/migration-guide-pyowm-v2-to-v3.md:) 25 | 26 | ## UnauthorizedError: Invalid API Key provided 27 | You are able to successfully create an `OWM` object and calling functions **other than One-Call** related ones (eg. getting observed or forecasted weather) 28 | 29 | As stated in the documentation home page, OpenWeatherMap API recently "blocked" calls towards a few legacy API endpoints whenever requested by **clients using non-recent free API keys.** 30 | 31 | This means that PyOWM might return authorization errors in that case. 32 | 33 | This behaviour is not showing if you use API keys issued time ago - unfortunately I have no way to be more precise as OWM never stated this officially. 34 | 35 | **The proper way to obtain the data you are looking for is to call the "OneCall" PyOWM methods using your API key** 36 | 37 | So please refer to the documentation for this 38 | 39 | 40 | ## I cannot use PyOWM 3 so I need to use PyOWM version 2.10 41 | This may happen if you still use Python 2 or you use Python 3 but with a minor version that is not supported by PyOWM 42 | 43 | Please install PyOWM 2.10 with: 44 | ```shell 45 | pip2 install pyowm==2.10 46 | ``` 47 | 48 | And find the PyOWM 2.10 documentation [here](https://pyowm.readthedocs.io/en/2.10/) 49 | 50 | 51 | ## ModuleNotFound error upon installing PyOWM development branch from Github 52 | 53 | Installation of the (potentially unstable) development trunk used to be like this: 54 | 55 | ```shell 56 | $ pip install git+https://github.com/csparpa/pyowm.git@develop 57 | ``` 58 | 59 | You would get something like: 60 | 61 | ```shell 62 | Collecting git+https://github.com/csparpa/pyowm.git@develop 63 | Cloning https://github.com/csparpa/pyowm.git (to revision develop) to /tmp/pip-req-build-_86bl7ty 64 | [......] 65 | ERROR: Command errored out with exit status 1: 66 | command: /home/me/.local/share/virtualenvs/backend-nPPHZqlJ/bin/python -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-req-build-_86bl7ty/setup.py'"'"'; __file__='"'"'/tmp/pip-req-build-_86bl7ty/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-ww_gs9y3 67 | cwd: /tmp/pip-req-build-_86bl7ty/ 68 | Complete output (17 lines): 69 | Traceback (most recent call last): 70 | [......] 71 | File "/tmp/pip-req-build-_86bl7ty/pyowm/commons/tile.py", line 6, in 72 | from pyowm.utils.geo import Polygon 73 | File "/tmp/pip-req-build-_86bl7ty/pyowm/utils/geo.py", line 4, in 74 | import geojson 75 | ModuleNotFoundError: No module named 'geojson' 76 | ---------------------------------------- 77 | ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output. 78 | ``` 79 | 80 | I've realized this way of installing is bad *as it does not install PyOWM's dependencies** along. 81 | Therefore the right way to go is: 82 | 83 | ```shell 84 | $ git clone https://github.com/csparpa/pyowm.git 85 | $ cd pyowm && git checkout develop 86 | $ pip install -r requirements.txt && python setup.py install 87 | ``` -------------------------------------------------------------------------------- /sphinx/v3/geocoding-api-usage-examples.md: -------------------------------------------------------------------------------- 1 | # Geocoding API usage examples 2 | 3 | The OWM Weather API gives you the possibility to perform direct and reverse geocoding: 4 | - DIRECT GEOCODING: from toponym to geocoords 5 | - REVERSE GEOCODING: from geocoords to toponyms 6 | 7 | 8 | Please refer to the `Code Recipes` page, sections: `Direct/reverse geocoding` 9 | -------------------------------------------------------------------------------- /sphinx/v3/global-pyowm-usage-examples.md: -------------------------------------------------------------------------------- 1 | # Global PyOWM library usage examples 2 | 3 | The PyOWM library has one main entry point: the `OWM` class. You just need to instantiate it to get started! 4 | 5 | Please refer to the `Code Recipes` page, section: `Library initialization`, to get info about how to instantiate 6 | the PyOWM library 7 | 8 | 9 | 10 | ## Dumping PyOWM objects to Python dictionaries 11 | PyOWM object instances (eg. `Weather` or `Location` objects) can be dumped to `dict`s: 12 | 13 | ```python 14 | from pyowm.owm import OWM 15 | owm = OWM('your-api-key') 16 | mgr = owm.weather_manager() 17 | weather = mgr.weather_at_place('London,GB').weather # get the weather at London,GB now 18 | dump_dict = weather.to_dict() 19 | ``` 20 | 21 | This is useful as you can save the dump dictionaries to files (eg. using Python `json` or `pickle` modules) 22 | 23 | ## Printing objects 24 | Most of PyOWM objects can be pretty-printed for a quick introspection: 25 | 26 | ```python 27 | from pyowm.owm import OWM 28 | owm = OWM('your-api-key') 29 | print(owm) # 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /sphinx/v3/maintenance-streams-timelines.md: -------------------------------------------------------------------------------- 1 | # Maintenance streams timeline 2 | 3 | Here is the timeline summarizing maintenance streams of our interest: 4 | 5 | ``` 6 | PyOWM Branches 7 | ^ 8 | | 3.0 9 | | O---------------------->>> 10 | | 11 | | bugfix only 12 | | 2.10 (minor releases) 13 | O-----------------O--------------X 14 | | 15 | | 2.9-LTS 16 | | (python 2) 17 | O----X 18 | | 19 | | 20 | '----@------------@--------------@---------> Time 21 | Jan, 1st PyOWM 3.0 +1 year 22 | 2020 release date 23 | 24 | ``` 25 | 26 | Please go ahead reading for further detail 27 | 28 | 29 | ## Python 2 support 30 | 31 | ### In short 32 | - **official support for Python 2 will be discontinued on January, 1st 2020** 33 | - this means that your Python 2 code will not benefit from official fixes and security updates 34 | - PyOWM has already officially switched to Python 3 from version 2.10 but **still supports Python 2 until January, 1st 2020** on the `v2.9-LTS` code branch 35 | - therefore update your PyOWM installation to branch `v2.9-LTS` in order to get the latest bugfixes (no new features will be added) and plan for your move to Python 3 alongside 36 | - after January, 1st 2020 branch `v2.9-LTS` will be tagged on GitHub and then closed 37 | 38 | ### How to install branch `v2.9-LTS` 39 | Branch `v2.9-LTS` is not available on PyPi: it is only installable via: 40 | 41 | ```shell 42 | pip2 install git+https://github.com/csparpa/pyowm.git@v2.9-LTS 43 | ``` 44 | 45 | or alternatively: 46 | 47 | ```shell 48 | git clone https://github.com/csparpa/pyowm.git 49 | cd pyowm 50 | python2 setup.py install 51 | ``` 52 | 53 | ## PyOWM version 2 longterm support 54 | 55 | ### In short 56 | - PyOWM 3.0 will be released at certain point in time... when that happens, the 2.10 release will enter a **"longterm support" lasting 1 year** 57 | - During that grace period, only minor releases will be issued in case of bugs - eg. 2.10.1 58 | - After 1 year, the latest minor release of 2.10 will be tagged and PyOWM version 2 stream will be considered dead 59 | 60 | *Remember:* PyOWM 3 releases will have a different interface/behaviour from 2 releases, so please take time to read the docs and understand the _impacts_ on your code 61 | 62 | -------------------------------------------------------------------------------- /sphinx/v3/map-tiles-client-usage-examples.md: -------------------------------------------------------------------------------- 1 | # Tiles client 2 | 3 | OWM provides tiles for a few map layers displaying world-wide features such as global temperature, pressure, wind speed, 4 | and precipitation amount. 5 | 6 | Each tile is a PNG image that is referenced by a triplet: the (x, y) coordinates and a zoom level 7 | 8 | The zoom level might depend on the type of layers: 0 means no zoom (full globe covered), while usually you can get up 9 | to a zoom level of 18. 10 | 11 | Available map layers are specified by the `pyowm.tiles.enums.MapLayerEnum` values. 12 | 13 | 14 | ## OWM website technical reference 15 | - [http://openweathermap.org/api/weathermaps](http://openweathermap.org/api/weathermaps) 16 | 17 | 18 | ## Usage examples 19 | 20 | Tiles can be fetched this way: 21 | 22 | ```python 23 | from pyowm import OWM 24 | from pyowm.tiles.enums import MapLayerEnum 25 | 26 | owm = OWM('my-API-key') 27 | 28 | # Choose the map layer you want tiles for (eg. temeperature 29 | layer_name = MapLayerEnum.TEMPERATURE 30 | 31 | # Obtain an instance to a tile manager object 32 | tm = owm.tile_manager(layer_name) 33 | 34 | # Now say you want tile at coordinate x=5 y=2 at a zoom level of 6 35 | tile = tm.get_tile(5, 2, 6) 36 | 37 | # You can now save the tile to disk 38 | tile.persist('/path/to/file.png') 39 | 40 | # Wait! but now I need the pressure layer tile at the very same coordinates and zoom level! No worries... 41 | # Just change the map layer name on the TileManager and off you go! 42 | tm.map_layer = MapLayerEnum.PRESSURE 43 | tile = tm.get_tile(5, 2, 6) 44 | ``` 45 | 46 | 47 | ## Tile object 48 | 49 | A `pyowm.commons.tile.Tile` object is a wrapper for the tile coordinates and the image data, which is a 50 | `pyowm.commons.image.Image` object instance. 51 | 52 | You can save a tile to disk by specifying a target file: 53 | 54 | ```python 55 | tile.persist('/path/to/file.png') 56 | ``` 57 | 58 | ## Use cases 59 | 60 | ### I have the lon/lat of a point and I want to get the tile that contains that point at a given zoom level 61 | 62 | Turn the lon/lat couple to a `pyowm.utils.geo.Point` object and pass it 63 | 64 | ```python 65 | from pyowm.utils.geo import Point 66 | from pyowm.commons.tile import Tile 67 | 68 | geopoint = Point(lon, lat) 69 | x_tile, y_tile = Tile.tile_coords_for_point(geopoint, zoom_level): 70 | ``` 71 | 72 | ### I have a tile and I want to know its bounding box in lon/lat coordinates 73 | 74 | Easy! You'll get back a `pyowm.utils.geo.Polygon` object, from which you can extract lon/lat coordinates this way 75 | 76 | ```python 77 | polygon = tile.bounding_polygon() 78 | geopoints = polygon.points 79 | geocoordinates = [(p.lon, p.lat) for p in geopoints] # this gives you tuples with lon/lat 80 | ``` 81 | -------------------------------------------------------------------------------- /sphinx/v3/migration-guide-pyowm-v2-to-v3.md: -------------------------------------------------------------------------------- 1 | # PyOWM v2 to v3 migration guide 2 | 3 | ## Scenario 4 | You have PyOWM v2.x installed and your code uses it. 5 | 6 | ## Goal 7 | You want to swap in PyOWM v3.x and swap out PyOWM v2.x 8 | 9 | ## Steps 10 | 11 | 1. Is your code running on Python < 3 ? If so, migrate your code to Python 3 and then resume from here 12 | 2. If you package and distribute PyOWM as a dependency of your code using please correct the PyOWM version: this includes patching `setup.py`, `Pipfile`, `requirements.txt` or any other mechanism you use to declare the dependency. 13 | Example for `requirements.txt`: from 14 | 15 | ``` 16 | pyowm==2.10 17 | ``` 18 | 19 | to 20 | 21 | ``` 22 | pyowm>=3 23 | ``` 24 | 3. If you have tests for your code then you have a very good way to check that the PyOWM version update won't break your code. If you don't, then you might be at risk - this is a good time to start testing your stuff :-) 25 | 4. Check all lines in your code that invoke PyOWM by reading and comparing the v2 and the v3 documentation - and patch the calls wherever they broke! 26 | 5. Dry run your code 27 | -------------------------------------------------------------------------------- /sphinx/v3/pyowm-configuration-description.md: -------------------------------------------------------------------------------- 1 | # PyOWM configuration description 2 | 3 | PyOWM can be configured at your convenience. 4 | 5 | The library comes with a pre-cooked configuration that you can change according to your needs. The configuration is formulated as a Python dictionary. 6 | 7 | ## Default configuration 8 | The default config is the `DEFAULT_CONFIG` dict living in the `pyowm.config` module (check it to know the defaults) 9 | 10 | ## Configuration format 11 | The config dict is formatted as follows: 12 | 13 | ``` 14 | { 15 | "subscription_type": , 16 | "language": , 17 | "connection": { 18 | "use_ssl": 19 | "verify_ssl_certs": , 20 | "use_proxy": , 21 | "timeout_secs": , 22 | "max_retries": | 23 | }, 24 | "proxies": { 25 | "http": , 26 | "https": 27 | } 28 | } 29 | ``` 30 | 31 | Here are the keys: 32 | 33 | * `subscription_type`: this object represents an OWM API Plan subscription. Possible values are: `free|startup|developer|professional|enterprise` 34 | * `language`: 2-char string representing the language you want the weather statuses returned in. Currently serving: `en|ru|ar|zh_cn|ja|es|it|fr|de|pt` and more. Check [here](https://openweathermap.org/current) for a comprehensive list of supported languages 35 | * `connection`: 36 | * `use_ssl`: whether to use SSL or not for API calls 37 | * `verify_ssl_certs`: speaks by itself.. 38 | * `use_proxy`: whether to use a proxy server or not (useful if you're eg. in a corporate network). HTTP and SOCKS5 proxies are allowed 39 | * `timeout_secs`: after how many seconds the API calls should be timeouted 40 | * `max_retries`: how many times PyOWM should retry to call the API if it responds with an error or timeouts. Defaults to `None`, which means: call forever. 41 | * `proxies` (this sub-dict is ignored if `use_proxy == False`) 42 | * `http`: the HTTP URL of the proxy server 43 | * `https`: the HTTPS/SOCKS5 URL of the proxy server 44 | 45 | ## Providing a custom configuration 46 | You can either pass in your custom dict to the global `OWM` object upon instantiation: 47 | 48 | ```python 49 | from pyowm import OWM 50 | owm = OWM('my-api-key', config=my_custom_config_dict) # pass in your dict as a named argument 51 | ``` 52 | 53 | or you can put your custom configuration inside a JSON text file and have it read by PyOWM: 54 | 55 | ```python 56 | from pyowm.owm import OWM 57 | from pyowm.utils.config import get_config_from 58 | config_dict = get_config_from('/path/to/configfile.json') # This utility comes in handy 59 | owm = OWM('your-free-api-key', config_dict) 60 | ``` 61 | 62 | Be aware that the JSON file must be properly formatted and that the unspecified non-mandatory keys will be filled in with default values. Here is an example: 63 | 64 | ```json 65 | { 66 | "api_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 67 | "subscription_type": "professional", 68 | "language": "ru", 69 | "connection": { 70 | "use_ssl": true, 71 | "verify_ssl_certs": true, 72 | "timeout_secs": 1 73 | } 74 | } 75 | ``` -------------------------------------------------------------------------------- /sphinx/v3/uv-api-usage-examples.md: -------------------------------------------------------------------------------- 1 | You can query the OWM API for current Ultra Violet (UV) intensity data in the surroundings of 2 | specific geocoordinates. 3 | 4 | Please refer to the official API docs for [UV](http://openweathermap.org/api/uvi) 5 | 6 | 7 | ### Querying UV index observations 8 | 9 | Getting the data is easy: 10 | 11 | ```python 12 | from pyowm import OWM 13 | owm = OWM('apikey') 14 | mgr = owm.uvindex_manager() 15 | uvi = mgr.uvindex_around_coords(lat, lon) 16 | ``` 17 | 18 | The query returns an UV Index value entity instance 19 | 20 | 21 | ### Querying UV index forecasts 22 | 23 | As easy as: 24 | 25 | ```python 26 | uvi_list = mgr.uvindex_forecast_around_coords(lat, lon) 27 | ``` 28 | 29 | ### Querying UV index history 30 | 31 | As easy as: 32 | 33 | ```python 34 | uvi_history_list = mgr.uvindex_history_around_coords( 35 | lat, lon, 36 | datetime.datetime(2017, 8, 1, 0, 0, 0, timezone.utc), 37 | end=datetime.datetime(2018, 2, 15, 0, 0, 0, timezone.utc)) 38 | ``` 39 | 40 | `start` and `end` can be ISO-8601 date strings, unix timestamps or Python datetime 41 | objects. 42 | 43 | In case `end` is not provided, then UV historical values will be retrieved 44 | dating back to `start` up to the current timestamp. 45 | 46 | 47 | ### `UVIndex` entity 48 | `UVIndex` is an entity representing a UV intensity measurement on a certain geopoint. 49 | Here are some of the methods: 50 | 51 | ```python 52 | uvi.get_value() 53 | uvi.get_reference_time() 54 | uvi.get_reception_time() 55 | uvi.get_exposure_risk() 56 | ``` 57 | 58 | The `get_exposure_risk()` methods returns a string estimating the risk of harm from 59 | unprotected sun exposure if an average adult was exposed to a UV intensity such as the on 60 | in this measurement. [This is the source mapping](https://en.wikipedia.org/wiki/Ultraviolet_index) 61 | for the statement. 62 | -------------------------------------------------------------------------------- /sphinx/v3/weather-api-usage-examples.md: -------------------------------------------------------------------------------- 1 | # Weather API usage examples 2 | 3 | The OWM Weather API gives you data about currently observed and forecast weather data upon cities in the world. 4 | 5 | Please refer to the `Code Recipes` page, sections: `Weather data`, `Weather forecasts` and `Meteostation historic measurements` to know more. 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/__init__.py -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/agroapi10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/agroapi10/__init__.py -------------------------------------------------------------------------------- /tests/integration/agroapi10/test_integration_polygons_api_subset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | from pyowm import owm 7 | from pyowm.agroapi10.polygon import Polygon, GeoPolygon 8 | 9 | 10 | class IntegrationTestsPolygonsAPISubset(unittest.TestCase): 11 | 12 | __owm = owm.OWM(os.getenv('OWM_API_KEY', None)) 13 | 14 | def test_polygons_CRUD(self): 15 | 16 | mgr = self.__owm.agro_manager() 17 | 18 | # check if any previous polygon exists on this account 19 | n_old_polygons = len(mgr.get_polygons()) 20 | 21 | # create pol1 22 | geopol1 = GeoPolygon([[ 23 | [-121.1958, 37.6683], 24 | [-121.1779, 37.6687], 25 | [-121.1773, 37.6792], 26 | [-121.1958, 37.6792], 27 | [-121.1958, 37.6683] 28 | ]]) 29 | pol1 = mgr.create_polygon(geopol1, 'polygon_1') 30 | 31 | # create pol2 32 | geopol2 = GeoPolygon([[ 33 | [-141.1958, 27.6683], 34 | [-141.1779, 27.6687], 35 | [-141.1773, 27.6792], 36 | [-141.1958, 27.6792], 37 | [-141.1958, 27.6683] 38 | ]]) 39 | pol2 = mgr.create_polygon(geopol2, 'polygon_2') 40 | 41 | # Read all 42 | polygons = mgr.get_polygons() 43 | self.assertEqual(n_old_polygons + 2, len(polygons)) 44 | self.assertTrue(all([isinstance(p, Polygon) for p in polygons])) 45 | 46 | # Read one by one 47 | result = mgr.get_polygon(pol1.id) 48 | self.assertEqual(pol1.id, result.id) 49 | self.assertEqual(pol1.name, pol1.name) 50 | self.assertEqual(pol1.area, result.area) 51 | self.assertEqual(pol1.user_id, result.user_id) 52 | self.assertEqual(pol1.center.lon, result.center.lon) 53 | self.assertEqual(pol1.center.lat, result.center.lat) 54 | self.assertEqual(pol1.geopolygon.geojson(), result.geopolygon.geojson()) 55 | 56 | result = mgr.get_polygon(pol2.id) 57 | self.assertEqual(pol2.id, result.id) 58 | self.assertEqual(pol2.name, result.name) 59 | self.assertEqual(pol2.area, result.area) 60 | self.assertEqual(pol2.user_id, result.user_id) 61 | self.assertEqual(pol2.center.lon, result.center.lon) 62 | self.assertEqual(pol2.center.lat, result.center.lat) 63 | self.assertEqual(pol2.geopolygon.geojson(), result.geopolygon.geojson()) 64 | 65 | # Update a polygon 66 | pol2.name = 'a better name' 67 | mgr.update_polygon(pol2) 68 | result = mgr.get_polygon(pol2.id) 69 | self.assertEqual(pol2.id, result.id) 70 | self.assertEqual(pol2.area, result.area) 71 | self.assertEqual(pol2.user_id, result.user_id) 72 | self.assertEqual(pol2.center.lon, result.center.lon) 73 | self.assertEqual(pol2.center.lat, result.center.lat) 74 | self.assertEqual(pol2.geopolygon.geojson(), result.geopolygon.geojson()) 75 | self.assertNotEqual(pol2.name, pol1.name) # of course, the name has changed 76 | 77 | # Delete polygons one by one 78 | mgr.delete_polygon(pol1) 79 | polygons = mgr.get_polygons() 80 | self.assertEqual(n_old_polygons + 1, len(polygons)) 81 | 82 | mgr.delete_polygon(pol2) 83 | polygons = mgr.get_polygons() 84 | self.assertEqual(n_old_polygons, len(polygons)) 85 | 86 | 87 | if __name__ == "__main__": 88 | unittest.main() 89 | 90 | -------------------------------------------------------------------------------- /tests/integration/agroapi10/test_integration_satellite_imagery_stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | from pyowm import owm 7 | from pyowm.agroapi10.polygon import GeoPolygon 8 | from pyowm.agroapi10.enums import SatelliteEnum, PresetEnum 9 | from pyowm.agroapi10.imagery import MetaImage 10 | 11 | 12 | class IntegrationTestsSatelliteImageryStats(unittest.TestCase): 13 | 14 | __owm = owm.OWM(os.getenv('OWM_API_KEY', None)) 15 | __polygon = None 16 | __acquired_from = 1500336000 # 18 July 2017 17 | __acquired_to = 1508976000 # 26 October 2017 18 | 19 | @classmethod 20 | def setUpClass(cls): 21 | # create a polygon 22 | mgr = cls.__owm.agro_manager() 23 | geopol = GeoPolygon([[ 24 | [-121.1958, 37.6683], 25 | [-121.1779, 37.6687], 26 | [-121.1773, 37.6792], 27 | [-121.1958, 37.6792], 28 | [-121.1958, 37.6683] 29 | ]]) 30 | cls.__polygon = mgr.create_polygon(geopol, 'stats_test_polygon') 31 | 32 | @classmethod 33 | def tearDownClass(cls): 34 | # delete the polygon 35 | mgr = cls.__owm.agro_manager() 36 | mgr.delete_polygon(cls.__polygon) 37 | 38 | def test_stats_for_satellite_image(self): 39 | mgr = self.__owm.agro_manager() 40 | 41 | # search all Landsat 8 images in the specified time frame and with high valid data percentage 42 | result_set = mgr.search_satellite_imagery(self.__polygon.id, self.__acquired_from, self.__acquired_to, None, None, 43 | None, None, SatelliteEnum.LANDSAT_8.symbol, None, 0.5, 99.5, None) 44 | self.assertIsInstance(result_set, list) 45 | self.assertTrue(all([isinstance(i, MetaImage) and i.satellite_name == SatelliteEnum.LANDSAT_8.name for i in result_set])) 46 | 47 | # only keep EVI and NDVI ones 48 | ndvi_only = [mimg for mimg in result_set if mimg.preset == PresetEnum.NDVI] 49 | evi_only = [mimg for mimg in result_set if mimg.preset == PresetEnum.EVI] 50 | 51 | self.assertTrue(len(ndvi_only) > 1) 52 | self.assertTrue(len(evi_only) > 1) 53 | 54 | # now search for stats for both types 55 | stats_ndvi = mgr.stats_for_satellite_image(ndvi_only[0]) 56 | stats_evi = mgr.stats_for_satellite_image(evi_only[0]) 57 | self.assertIsInstance(stats_ndvi, dict) 58 | self.assertIsInstance(stats_evi, dict) 59 | 60 | # try to search for stats of a non NDVI or EVI image 61 | falsecolor_only = [mimg for mimg in result_set if mimg.preset == PresetEnum.FALSE_COLOR] 62 | with self.assertRaises(ValueError): 63 | mgr.stats_for_satellite_image(falsecolor_only[0]) 64 | 65 | 66 | if __name__ == "__main__": 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /tests/integration/agroapi10/test_integration_soil_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | from pyowm import owm 7 | from pyowm.agroapi10.polygon import GeoPolygon 8 | from pyowm.agroapi10.soil import Soil 9 | 10 | 11 | class IntegrationTestsSoilData(unittest.TestCase): 12 | 13 | __owm = owm.OWM(os.getenv('OWM_API_KEY', None)) 14 | 15 | def test_call_soil_data(self): 16 | 17 | mgr = self.__owm.agro_manager() 18 | 19 | # check if any previous polygon exists on this account 20 | n_old_polygons = len(mgr.get_polygons()) 21 | 22 | # create pol1 23 | geopol1 = GeoPolygon([[ 24 | [-121.1958, 37.6683], 25 | [-121.1779, 37.6687], 26 | [-121.1773, 37.6792], 27 | [-121.1958, 37.6792], 28 | [-121.1958, 37.6683] 29 | ]]) 30 | test_pol = mgr.create_polygon(geopol1, 'soil_data_test_pol') 31 | 32 | soil = mgr.soil_data(test_pol) 33 | 34 | self.assertTrue(isinstance(soil, Soil)) 35 | self.assertEqual(test_pol.id, soil.polygon_id) 36 | 37 | # Delete test polygon 38 | mgr.delete_polygon(test_pol) 39 | polygons = mgr.get_polygons() 40 | self.assertEqual(n_old_polygons, len(polygons)) 41 | 42 | 43 | if __name__ == "__main__": 44 | unittest.main() 45 | 46 | -------------------------------------------------------------------------------- /tests/integration/airpollutionapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/airpollutionapi30/__init__.py -------------------------------------------------------------------------------- /tests/integration/airpollutionapi30/test_integration_pollutionapi30.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | from pyowm import owm 7 | 8 | 9 | class IntegrationTestsPollutionAPI30(unittest.TestCase): 10 | 11 | __owm = owm.OWM(os.getenv('OWM_API_KEY', None)).airpollution_manager() 12 | 13 | def test_air_quality_at_coords(self): 14 | """ 15 | Test feature: get all air quality data around geo-coordinates. 16 | """ 17 | airstatus = self.__owm.air_quality_at_coords(45, 9) 18 | self.assertIsNotNone(airstatus) 19 | self.assertIsNotNone(airstatus.air_quality_data) 20 | self.assertIsNotNone(airstatus.reception_time()) 21 | self.assertIsNotNone(airstatus.reference_time()) 22 | self.assertIsNotNone(airstatus.location) 23 | 24 | def test_air_quality_forecast_at_coords(self): 25 | """ 26 | Test feature: get all forecasted air quality data around geo-coordinates. 27 | """ 28 | list_of_airstatuses = self.__owm.air_quality_forecast_at_coords(45, 9) 29 | self.assertTrue(list_of_airstatuses) 30 | for airstatus in list_of_airstatuses: 31 | self.assertIsNotNone(airstatus.air_quality_data) 32 | self.assertIsNotNone(airstatus.reception_time()) 33 | self.assertIsNotNone(airstatus.reference_time()) 34 | self.assertIsNotNone(airstatus.location) 35 | 36 | def test_air_quality_history_at_coords(self): 37 | """ 38 | Test feature: get historical air quality data around geo-coordinates. 39 | """ 40 | start = 1606223802 # Tuesday, November 24, 2020 41 | 42 | list_of_airstatuses = self.__owm.air_quality_history_at_coords(45, 9, start) 43 | self.assertIsInstance(list_of_airstatuses, list) 44 | for airstatus in list_of_airstatuses: 45 | self.assertIsNotNone(airstatus.air_quality_data) 46 | self.assertIsNotNone(airstatus.reception_time()) 47 | self.assertIsNotNone(airstatus.reference_time()) 48 | self.assertIsNotNone(airstatus.location) 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /tests/integration/alertapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/alertapi30/__init__.py -------------------------------------------------------------------------------- /tests/integration/alertapi30/test_integration_alertapi30.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | import copy 7 | from pyowm import owm 8 | from pyowm.alertapi30.condition import Condition 9 | from pyowm.alertapi30.enums import WeatherParametersEnum, OperatorsEnum 10 | from pyowm.utils import geo 11 | 12 | 13 | class IntegrationTestsAlertAPI30(unittest.TestCase): 14 | 15 | __owm = owm.OWM(os.getenv('OWM_API_KEY', None)) 16 | 17 | cond1 = Condition(WeatherParametersEnum.HUMIDITY, OperatorsEnum.LESS_THAN, 10) 18 | cond2 = Condition(WeatherParametersEnum.CLOUDS, OperatorsEnum.LESS_THAN, 90) 19 | start = '2019-07-01 14:17:00+00:00' 20 | end = '2019-07-02 14:17:00+00:00' 21 | # a rectangle around the city of Moscow 22 | area1 = geo.Polygon.from_dict({ 23 | "type": "Polygon", 24 | "coordinates": [ 25 | [ 26 | [ 27 | 36.826171875, 28 | 55.17259379606185 29 | ], 30 | [ 31 | 39.012451171875, 32 | 55.17259379606185 33 | ], 34 | [ 35 | 39.012451171875, 36 | 56.15778819063682 37 | ], 38 | [ 39 | 36.826171875, 40 | 56.15778819063682 41 | ], 42 | [ 43 | 36.826171875, 44 | 55.17259379606185 45 | ] 46 | ] 47 | ]}) 48 | # somewhere in Dubai 49 | 50 | area2 = geo.Point.from_dict({ 51 | "type": "Point", 52 | "coordinates": [ 53 | 55.29693603515625, 54 | 25.186301620540558 55 | ]}) 56 | 57 | def test_triggers_CRUD(self): 58 | 59 | mgr = self.__owm.alert_manager() 60 | 61 | # check if any previous triggers exist on this account 62 | n_old_triggers = len(mgr.get_triggers()) 63 | 64 | # create trigger1 65 | trigger1 = mgr.create_trigger(self.start, self.end, conditions=[self.cond1], area=[self.area1]) 66 | 67 | # create trigger2 68 | trigger2 = mgr.create_trigger(self.start, self.end, conditions=[self.cond2], area=[self.area2]) 69 | 70 | # Read all created triggers 71 | triggers = mgr.get_triggers() 72 | self.assertEqual(n_old_triggers + 2, len(triggers)) 73 | 74 | # Read one by one 75 | result = mgr.get_trigger(trigger1.id) 76 | self.assertEqual(trigger1.id, result.id) 77 | self.assertEqual(trigger1.start_after_millis, result.start_after_millis) 78 | self.assertEqual(trigger1.end_after_millis, result.end_after_millis) 79 | self.assertEqual(len(trigger1.conditions), len(result.conditions)) 80 | self.assertEqual(len(trigger1.area), len(result.area)) 81 | 82 | result = mgr.get_trigger(trigger2.id) 83 | self.assertEqual(trigger2.id, result.id) 84 | self.assertEqual(trigger2.start_after_millis, result.start_after_millis) 85 | self.assertEqual(trigger2.end_after_millis, result.end_after_millis) 86 | self.assertEqual(len(trigger2.conditions), len(result.conditions)) 87 | self.assertEqual(len(trigger2.area), len(result.area)) 88 | 89 | # Update a trigger 90 | modified_trigger2 = copy.deepcopy(trigger2) 91 | modified_trigger2.conditions = [self.cond1, self.cond2] 92 | 93 | mgr.update_trigger(modified_trigger2) 94 | result = mgr.get_trigger(modified_trigger2.id) 95 | 96 | self.assertEqual(modified_trigger2.id, result.id) 97 | self.assertEqual(modified_trigger2.start_after_millis, result.start_after_millis) 98 | self.assertEqual(modified_trigger2.end_after_millis, result.end_after_millis) 99 | self.assertEqual(len(modified_trigger2.area), len(result.area)) 100 | # of course, conditions have been modified with respect to former trigger 2 101 | self.assertNotEqual(len(trigger2.conditions), len(result.conditions)) 102 | self.assertEqual(len(modified_trigger2.conditions), len(result.conditions)) 103 | 104 | # Delete triggers one by one 105 | mgr.delete_trigger(trigger1) 106 | triggers = mgr.get_triggers() 107 | self.assertEqual(n_old_triggers + 1, len(triggers)) 108 | 109 | mgr.delete_trigger(modified_trigger2) 110 | triggers = mgr.get_triggers() 111 | self.assertEqual(n_old_triggers, len(triggers)) 112 | 113 | 114 | if __name__ == "__main__": 115 | unittest.main() 116 | -------------------------------------------------------------------------------- /tests/integration/commons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/commons/256x256.png -------------------------------------------------------------------------------- /tests/integration/commons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/commons/__init__.py -------------------------------------------------------------------------------- /tests/integration/commons/test_http_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import unittest 6 | 7 | import pyowm.commons.exceptions 8 | from pyowm.commons.http_client import HttpClient 9 | from pyowm.config import DEFAULT_CONFIG 10 | 11 | 12 | class TestHTTPClient(unittest.TestCase): 13 | 14 | instance = HttpClient('fakeapikey', DEFAULT_CONFIG, 'httpbin.org', admits_subdomains=False) 15 | 16 | def test_get_json_against_httpbin_ok(self): 17 | # http://httpbin.org/ip 18 | status, data = self.instance.get_json('/ip') 19 | self.assertEqual(200, status) 20 | self.assertIsInstance(data, dict) 21 | 22 | def test_get_json_against_httpbin_status_code_ko(self): 23 | # http://httpbin.org/status/400 24 | expected_status = 400 25 | 26 | self.assertRaises(pyowm.commons.exceptions.APIRequestError, HttpClient.get_json, 27 | self.instance, 'status/{}'.format(str(expected_status))) 28 | 29 | def test_get_json_against_httpbin_parse_error(self): 30 | # http://httpbin.org/xml 31 | try: 32 | status, data = self.instance.get_json('xml') 33 | self.fail() 34 | except pyowm.commons.exceptions.ParseAPIResponseError: 35 | pass 36 | 37 | def test_put_against_httpbin(self): 38 | # http://httpbin.org/put 39 | formdata = dict(a=1, b=2, c=3) 40 | status, data = self.instance.put('put', data=formdata) 41 | self.assertEqual(200, status) 42 | self.assertIsInstance(data, dict) 43 | self.assertEqual(formdata, data['json']) 44 | 45 | def test_delete_against_httpbin(self): 46 | # http://httpbin.org/delete 47 | formdata = dict(a=1, b=2, c=3) 48 | status, data = self.instance.delete('delete', data=formdata) 49 | self.assertEqual(200, status) 50 | self.assertIsInstance(data, dict) 51 | self.assertEqual(formdata, data['json']) 52 | 53 | def test_ssl_certs_verification_failure(self): 54 | # https://wrong.host.badssl.com does not have a valid SSL cert 55 | config = DEFAULT_CONFIG.copy() 56 | config['connection']['use_ssl'] = True 57 | config['connection']['verify_ssl_certs'] = True 58 | instance = HttpClient('fakeapikey', config, 'wrong.host.badssl.com', admits_subdomains=False) 59 | self.assertRaises(pyowm.commons.exceptions.InvalidSSLCertificateError, HttpClient.get_json, instance, '') 60 | 61 | def test_get_png(self): 62 | # http://httpbin.org/image/png 63 | status, data = self.instance.get_png('image/png') 64 | self.assertIsNotNone(data) 65 | self.assertIsInstance(data, bytes) 66 | 67 | def test_get_geotiff(self): 68 | # https://download.osgeo.org/geotiff/samples/made_up/bogota.tif 69 | config = DEFAULT_CONFIG.copy() 70 | config['connection']['use_ssl'] = True 71 | instance = HttpClient('fakeapikey', config, 'download.osgeo.org', admits_subdomains=False) 72 | status, data = instance.get_geotiff('geotiff/samples/made_up/bogota.tif') 73 | self.assertIsNotNone(data) 74 | self.assertIsInstance(data, bytes) 75 | 76 | def test_get_png(self): 77 | # http://httpbin.org/image/png 78 | status, data = self.instance.get_png('http://httpbin.org/image/png') 79 | self.assertIsNotNone(data) 80 | self.assertIsInstance(data, bytes) 81 | 82 | def test_get_geotiff(self): 83 | # https://download.osgeo.org/geotiff/samples/made_up/bogota.tif 84 | status, data = self.instance.get_geotiff('https://download.osgeo.org/geotiff/samples/made_up/bogota.tif') 85 | self.assertIsNotNone(data) 86 | self.assertIsInstance(data, bytes) 87 | 88 | 89 | if __name__ == "__main__": 90 | unittest.main() 91 | 92 | -------------------------------------------------------------------------------- /tests/integration/geocodingapi10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/geocodingapi10/__init__.py -------------------------------------------------------------------------------- /tests/integration/geocodingapi10/test_integration_geocodingapi10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | from pyowm import owm 7 | from pyowm.weatherapi30.location import Location 8 | 9 | 10 | class IntegrationTestsGeocodingAPI(unittest.TestCase): 11 | 12 | __owm = owm.OWM(os.getenv('OWM_API_KEY', None)) 13 | 14 | def test_geocode(self): 15 | mgr = self.__owm.geocoding_manager() 16 | 17 | # Geocode all Paris in the United States 18 | locations = mgr.geocode('Paris', 'US') 19 | self.assertTrue(isinstance(locations, list)) 20 | self.assertTrue(all([isinstance(l, Location) for l in locations])) 21 | self.assertTrue(all([l.name == 'Paris' and l.country == 'US' for l in locations])) 22 | 23 | def test_reverse_geocode(self): 24 | mgr = self.__owm.geocoding_manager() 25 | 26 | # Reverse geocode the geocoords for Florence (Italy) 27 | locations = mgr.reverse_geocode(43.783731, 11.246603) 28 | self.assertTrue(isinstance(locations, list)) 29 | self.assertTrue(all([isinstance(l, Location) for l in locations])) 30 | self.assertTrue(all([l.name == 'Firenze' and l.country == 'IT' for l in locations])) 31 | 32 | -------------------------------------------------------------------------------- /tests/integration/stationsapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/stationsapi30/__init__.py -------------------------------------------------------------------------------- /tests/integration/stationsapi30/measurements.json: -------------------------------------------------------------------------------- 1 | [{"heat_index": null, "clouds_cumulus": null, "wind_deg": null, "visibility_distance": null, "weather_obscuration": null, "dew_point": null, "weather_precipitation": null, "wind_gust": 67, "weather_intensity": null, "humidex": 77, "rain_1h": null, "snow_1h": null, "humidity": null, "wind_speed": 2.1, "timestamp": 1378459200, "visibility_prefix": null, "clouds_distance": null, "temperature": {"max": 100, "min": 0}, "clouds_condition": null, "station_id": "mytest", "pressure": null, "weather_proximity": null, "rain_24h": null, "snow_24h": null, "snow_6h": null, "rain_6h": null, "weather_other": {"key": "val"}, "weather_descriptor": null}, 2 | {"heat_index": null, "clouds_cumulus": null, "wind_deg": null, "visibility_distance": null, "weather_obscuration": null, "dew_point": null, "weather_precipitation": null, "wind_gust": 0, "weather_intensity": null, "humidex": 0, "rain_1h": null, "snow_1h": null, "humidity": null, "wind_speed": 8.5, "timestamp": 1378459700, "visibility_prefix": null, "clouds_distance": null, "temperature": {"max": 800, "min": 30}, "clouds_condition": null, "station_id": "mytest", "pressure": null, "weather_proximity": null, "rain_24h": null, "snow_24h": null, "snow_6h": null, "rain_6h": null, "weather_other": null, "weather_descriptor": null}, 3 | {"heat_index": null, "clouds_cumulus": null, "wind_deg": null, "visibility_distance": null, "weather_obscuration": null, "dew_point": null, "weather_precipitation": null, "wind_gust": -12, "weather_intensity": null, "humidex": 2, "rain_1h": null, "snow_1h": null, "humidity": null, "wind_speed": 4.4, "timestamp": 1378458200, "visibility_prefix": null, "clouds_distance": null, "temperature": {"max": 20, "min": 0}, "clouds_condition": null, "station_id": "mytest", "pressure": null, "weather_proximity": null, "rain_24h": null, "snow_24h": null, "snow_6h": null, "rain_6h": null, "weather_other": null, "weather_descriptor": null}] -------------------------------------------------------------------------------- /tests/integration/stationsapi30/test_persistence_backends_read_fs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | import tempfile 7 | import json 8 | from pyowm.stationsapi30.measurement import Measurement 9 | from pyowm.stationsapi30.buffer import Buffer 10 | from pyowm.stationsapi30.persistence_backend import JSONPersistenceBackend 11 | 12 | 13 | class TestJSONPersistenceBackendsReadFS(unittest.TestCase): 14 | 15 | basepath = os.path.dirname(os.path.abspath(__file__)) 16 | data_dict = dict(station_id='mytest', timestamp=1378459200, 17 | temperature=dict(min=0, max=100), wind_speed=2.1, 18 | wind_gust=67, humidex=77, weather_other=dict(key='val')) 19 | measurement = Measurement.from_dict(data_dict) 20 | 21 | def test_json_persistence_backend_with_no_station_id_specified(self): 22 | be = JSONPersistenceBackend( 23 | self.basepath + os.sep + 'measurements.json', None) 24 | with self.assertRaises(ValueError): 25 | be.load_to_buffer() 26 | 27 | def test_json_persistence_backend_reads(self): 28 | be = JSONPersistenceBackend( 29 | self.basepath + os.sep + 'measurements.json', 'mytest') 30 | buf = be.load_to_buffer() 31 | self.assertTrue(3, len(buf)) 32 | for item in buf: 33 | self.assertTrue(isinstance(item, Measurement)) 34 | 35 | def test_json_persistence_backend_writes(self): 36 | with tempfile.NamedTemporaryFile() as tmp: 37 | # write file 38 | target_file = os.path.abspath(tmp.name) 39 | be = JSONPersistenceBackend(target_file, 'mytest') 40 | buffer = Buffer('mytest') 41 | buffer.append(self.measurement) 42 | be.persist_buffer(buffer) 43 | 44 | # check data 45 | with open(target_file, 'r') as f: 46 | data = f.read() 47 | items = json.loads(data) 48 | self.assertEqual(1, len(items)) 49 | msmt = items[0] 50 | self.assertTrue(all(item in msmt.items() 51 | for item in self.data_dict.items())) 52 | 53 | 54 | if __name__ == "__main__": 55 | unittest.main() 56 | 57 | -------------------------------------------------------------------------------- /tests/integration/tiles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/tiles/__init__.py -------------------------------------------------------------------------------- /tests/integration/tiles/test_integration_tile_manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | from pyowm import owm 7 | from pyowm.tiles.enums import MapLayerEnum 8 | from pyowm.commons.tile import Tile 9 | 10 | 11 | class TesIntegrationTileManager(unittest.TestCase): 12 | 13 | __owm = owm.OWM(os.getenv('OWM_API_KEY', None)) 14 | 15 | def test_tiles_fetch(self): 16 | mgr = self.__owm.tile_manager(MapLayerEnum.PRECIPITATION) 17 | tile = mgr.get_tile(3, 6, 7) 18 | self.assertIsInstance(tile, Tile) 19 | 20 | 21 | if __name__ == "__main__": 22 | unittest.main() 23 | 24 | -------------------------------------------------------------------------------- /tests/integration/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py37, py38 4 | skip_missing_interpreters = 5 | True 6 | skipsdist = True 7 | 8 | [testenv] 9 | passenv = OWM_API_KEY 10 | deps = 11 | -r../../requirements.txt 12 | pytest 13 | commands = pytest . 14 | -------------------------------------------------------------------------------- /tests/integration/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/utils/__init__.py -------------------------------------------------------------------------------- /tests/integration/utils/non_json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/utils/non_json -------------------------------------------------------------------------------- /tests/integration/utils/test_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "b1b15e88fa797225412429c1c50c122a", 3 | "subscription_type": "professional", 4 | "language": "ru", 5 | "connection": { 6 | "use_ssl": true, 7 | "verify_ssl_certs": true, 8 | "timeout_secs": 1, 9 | "max_retries": null 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/integration/utils/test_default_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.commons.enums import SubscriptionTypeEnum 6 | from pyowm.config import DEFAULT_CONFIG 7 | 8 | 9 | class TesDefaultConfig(unittest.TestCase): 10 | 11 | def test_default_config_is_complete(self): 12 | 13 | # subscription type is free 14 | self.assertTrue('subscription_type' in DEFAULT_CONFIG) 15 | self.assertEqual(SubscriptionTypeEnum.FREE, DEFAULT_CONFIG['subscription_type']) 16 | 17 | # language is English 18 | self.assertTrue('language' in DEFAULT_CONFIG) 19 | self.assertEqual('en', DEFAULT_CONFIG['language']) 20 | 21 | # connection is a sub-dict, check its keys 22 | self.assertTrue('connection' in DEFAULT_CONFIG) 23 | connection = DEFAULT_CONFIG['connection'] 24 | self.assertIsInstance(connection, dict) 25 | 26 | self.assertTrue('use_ssl' in connection) 27 | self.assertTrue(connection['use_ssl']) 28 | 29 | self.assertTrue('verify_ssl_certs' in connection) 30 | self.assertTrue(connection['verify_ssl_certs']) 31 | 32 | self.assertTrue('use_proxy' in connection) 33 | self.assertFalse(connection['use_proxy']) 34 | 35 | self.assertTrue('timeout_secs' in connection) 36 | self.assertEqual(5, connection['timeout_secs']) 37 | 38 | self.assertTrue('max_retries' in connection) 39 | self.assertEqual(None, connection['max_retries']) 40 | 41 | # proxies is a sub-dict, check its keys 42 | self.assertTrue('proxies' in DEFAULT_CONFIG) 43 | proxies = DEFAULT_CONFIG['proxies'] 44 | self.assertIsInstance(proxies, dict) 45 | 46 | self.assertTrue('http' in proxies) 47 | self.assertTrue('https' in proxies) 48 | 49 | -------------------------------------------------------------------------------- /tests/integration/utils/test_integration_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import pathlib 5 | import unittest 6 | 7 | import pyowm.commons.exceptions 8 | from pyowm.utils import config 9 | 10 | 11 | class TesIntegrationConfig(unittest.TestCase): 12 | 13 | def test_get_config_from(self): 14 | config_file_name = 'test_config.json' 15 | path = (pathlib.Path(__file__).parent / config_file_name).absolute() 16 | result = config.get_config_from(path) 17 | self.assertIsInstance(result, dict) 18 | 19 | config_file_name = 'non_json' 20 | path = (pathlib.Path(__file__).parent / config_file_name).absolute() 21 | self.assertRaises(pyowm.commons.exceptions.ConfigurationParseError, config.get_config_from, path) 22 | 23 | 24 | if __name__ == "__main__": 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /tests/integration/uvindexapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/uvindexapi30/__init__.py -------------------------------------------------------------------------------- /tests/integration/uvindexapi30/test_integration_uvindexapi30.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import os 6 | from datetime import datetime 7 | from pyowm import owm 8 | 9 | 10 | class IntegrationTestsUVIndexAPI30(unittest.TestCase): 11 | 12 | __owm = owm.OWM(os.getenv('OWM_API_KEY', None)).uvindex_manager() 13 | 14 | def test_uvindex_around_coords(self): 15 | """ 16 | Test feature: get UV index around geo-coordinates. 17 | """ 18 | u = self.__owm.uvindex_around_coords(45, 9) 19 | self.assertIsNotNone(u) 20 | self.assertIsNotNone(u.value) 21 | self.assertIsNotNone(u.reception_time()) 22 | self.assertIsNotNone(u.location) 23 | 24 | def test_uvindex_forecast_around_coords(self): 25 | """ 26 | Test feature: get UV index forecast around geo-coordinates. 27 | """ 28 | uv_list = self.__owm.uvindex_forecast_around_coords(45, 9) 29 | self.assertIsInstance(uv_list, list) 30 | for item in uv_list: 31 | self.assertIsNotNone(item.value) 32 | self.assertIsNotNone(item.reception_time()) 33 | self.assertIsNotNone(item.location) 34 | 35 | def test_uvindex_history_around_coords(self): 36 | """ 37 | Test feature: get UV index history around geo-coordinates. 38 | """ 39 | start = datetime(2017, 6, 21) 40 | end = datetime(2017, 6, 27) 41 | uv_list = self.__owm.uvindex_history_around_coords(37.7, -122.37, 42 | start, 43 | end=end) 44 | self.assertIsInstance(uv_list, list) 45 | for item in uv_list: 46 | self.assertIsNotNone(item.value) 47 | self.assertIsNotNone(item.reception_time()) 48 | self.assertIsNotNone(item.location) 49 | 50 | 51 | if __name__ == "__main__": 52 | unittest.main() -------------------------------------------------------------------------------- /tests/integration/weatherapi25/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/integration/weatherapi25/__init__.py -------------------------------------------------------------------------------- /tests/local_installation_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "*** Start of local installation test ..." 3 | venv="$(readlink -f $(date +%s)_pyowm_installation_test)" 4 | virtualenv "$venv" && cd "$venv" && source bin/activate 5 | echo "*** Created virtualenv: $venv ..." 6 | 7 | # Understand if running on localhost or on Travis CI 8 | if [ -z "$TRAVIS_BUILD_DIR" ]; then 9 | TRAVIS_BUILD_DIR="$(readlink -f ../..)" 10 | fi 11 | echo "*** PyOWM source package is in: $TRAVIS_BUILD_DIR" 12 | 13 | # Test PyOWM installation from local folder 14 | cd "$TRAVIS_BUILD_DIR" 15 | pip install -r requirements.txt 16 | pip install -e . 17 | if [ $? -ne 0 ]; then 18 | echo "*** Pip installation failed!" 19 | exit 1 20 | fi 21 | echo "*** Pip installation from local folder was OK ..." 22 | python -c "import pyowm" 23 | if [ $? -ne 0 ]; then 24 | echo "*** Test import of library failed!" 25 | deactivate 26 | rm -rf "$venv" 27 | exit 2 28 | fi 29 | echo "*** Test import of library was OK" 30 | 31 | # Test dependencies installation 32 | python -c "import requests" && python -c "import geojson" 33 | if [ $? -ne 0 ]; then 34 | echo "*** Test import of dependencies failed!" 35 | deactivate 36 | rm -rf "$venv" 37 | exit 3 38 | fi 39 | echo "*** Test import of dependencies was OK" 40 | 41 | # Cleanup 42 | deactivate 43 | rm -rf "$venv" 44 | exit 0 -------------------------------------------------------------------------------- /tests/proxy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/proxy/__init__.py -------------------------------------------------------------------------------- /tests/proxy/proxy_http.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "b1b15e88fa797225412429c1c50c122a", 3 | "subscription_type": "free", 4 | "language": "en", 5 | "connection": { 6 | "use_ssl": true, 7 | "verify_ssl_certs": false, 8 | "use_proxy": true, 9 | "timeout_secs": 5 10 | }, 11 | "proxies": { 12 | "http": "http://user:pass@127.0.0.1:8899", 13 | "https": "https://user:pass@127.0.0.1:8899" 14 | } 15 | } -------------------------------------------------------------------------------- /tests/proxy/proxy_socks.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "b1b15e88fa797225412429c1c50c122a", 3 | "subscription_type": "free", 4 | "language": "en", 5 | "connection": { 6 | "use_ssl": true, 7 | "verify_ssl_certs": false, 8 | "use_proxy": true, 9 | "timeout_secs": 5 10 | }, 11 | "proxies": { 12 | "http": "socks5://127.0.0.1:8899", 13 | "https": "socks5://127.0.0.1:8899" 14 | } 15 | } -------------------------------------------------------------------------------- /tests/proxy/selfsigned.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIID5zCCAs+gAwIBAgIUWVG9t0QFZLIKXdt8PZl9dqsqdykwDQYJKoZIhvcNAQEL 3 | BQAwgYIxCzAJBgNVBAYTAklUMRIwEAYDVQQIDAlOZXZlcmxhbmQxFDASBgNVBAcM 4 | C0dvdGhhbSBDaXR5MQ0wCwYDVQQKDARBQ01FMQswCQYDVQQLDAJJVDELMAkGA1UE 5 | AwwCQ1MxIDAeBgkqhkiG9w0BCQEWEWNzcGFycGFAZ21haWwuY29tMB4XDTE5MTEx 6 | MzIxMDYzOVoXDTIwMTExMjIxMDYzOVowgYIxCzAJBgNVBAYTAklUMRIwEAYDVQQI 7 | DAlOZXZlcmxhbmQxFDASBgNVBAcMC0dvdGhhbSBDaXR5MQ0wCwYDVQQKDARBQ01F 8 | MQswCQYDVQQLDAJJVDELMAkGA1UEAwwCQ1MxIDAeBgkqhkiG9w0BCQEWEWNzcGFy 9 | cGFAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr+Fv 10 | FxgOqncbxHwTr8pcrEYwO/GXwRwvTD1vWgMjdX7kDSh3Mlgd6TdRvmaojtXaNAaf 11 | iTyrsPBS8LAPIZR+/8Fhxuc+tT5ClrJdK5XLM0Z3xhvAVyiwQOWxJwNapDD2cQ2W 12 | VbT6OorVAlLD9QjToGjCasx+eyL2hOVWAqK0funmHAytA1VzRVcv9ZzWZNWIvpp2 13 | qkkznQyipIl/+Pub2GkeKXjSwzlfAYGFAWN9NcLxUn2NOw9+5FkPeg19YCGBmUJx 14 | KxLq2B8gjLLtd6TYgdreJSpAwsb1PPFTvF2J4EVfiY+NfQtwzDIhGva4FfLeCvHL 15 | qOEwYblx0pkRJ5swKwIDAQABo1MwUTAdBgNVHQ4EFgQUVvFAYKIXJLLje+RRC40A 16 | SCvlAPcwHwYDVR0jBBgwFoAUVvFAYKIXJLLje+RRC40ASCvlAPcwDwYDVR0TAQH/ 17 | BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAWXjbYM8G8E9M0LLu6a5HsHvOUCI6 18 | SDA4XT09tWz5zgltHKj9WQzKTEi0ggO3LwUiTuJhhCMUL8S64lPPbNwUXpU8Mmmr 19 | dHn3F4hXPJfOClCfVK+VPf1wEgvjHx2ajxrac7N7NCGYhfblpWy4lVQWFoY3219J 20 | KKfeHKbIQjo2dmm2NyXzyBDWjcH3YPlNDUwr+fOhmMDAHS94Dlji/Cdpd25vPLj/ 21 | 58/ZFkJ46PGEAcisvT9Rjylw1qo0LJ3xFzvWdS9vWThH3inBCUpeoyebdhlpljW8 22 | da0N8MkhHMy3uZs70y/8xjtp5mO/pQjtzOijz9YV6uQs0zRoWHfGs3fT5Q== 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /tests/proxy/test_integration_proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import pathlib 6 | import unittest 7 | from pyowm import OWM 8 | from pyowm.weatherapi30.observation import Observation 9 | from pyowm.utils import config 10 | 11 | 12 | class TesIntegrationProxy(unittest.TestCase): 13 | 14 | _api_key = os.getenv('OWM_API_KEY', None) 15 | 16 | def test_call_api_behind_http_proxy(self): 17 | # fetch config and overwrite API Key as per env variable 18 | config_file_name = 'proxy_http.json' 19 | path = (pathlib.Path() / config_file_name).absolute() 20 | cfg = config.get_config_from(path) 21 | cfg['api_key'] = self._api_key 22 | 23 | # go 24 | owm = OWM(cfg['api_key'], cfg) 25 | wm = owm.weather_manager() 26 | result = wm.weather_at_place('London,GB') 27 | self.assertIsInstance(result, Observation) 28 | 29 | def test_call_api_behind_socks_proxy(self): 30 | # fetch config and overwrite API Key as per env variable 31 | config_file_name = 'proxy_socks.json' 32 | path = (pathlib.Path() / config_file_name).absolute() 33 | cfg = config.get_config_from(path) 34 | cfg['api_key'] = self._api_key 35 | 36 | # go 37 | owm = OWM(cfg['api_key'], cfg) 38 | wm = owm.weather_manager() 39 | result = wm.weather_at_place('London,GB') 40 | self.assertIsInstance(result, Observation) 41 | 42 | 43 | if __name__ == "__main__": 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /tests/proxy/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py37, py38 4 | skip_missing_interpreters = 5 | True 6 | skipsdist = True 7 | 8 | [testenv] 9 | passenv = OWM_API_KEY 10 | deps = 11 | -r../../requirements.txt 12 | pytest 13 | commands = pytest . -------------------------------------------------------------------------------- /tests/pypi_installation_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "*** Start of Pypi installation test ..." 3 | venv="$(readlink -f $(date +%s)_pyowm_installation_test)" 4 | virtualenv "$venv" && cd "$venv" && source bin/activate 5 | echo "*** Created virtualenv: $venv ..." 6 | 7 | # Test 8 | pip install pyowm 9 | if [ $? -ne 0 ]; then 10 | echo "*** Pip installation failed!" 11 | exit 1 12 | fi 13 | echo "*** Pip installation was OK ..." 14 | python -c "import pyowm" 15 | if [ $? -ne 0 ]; then 16 | echo "*** Test import of library failed!" 17 | exit 2 18 | fi 19 | echo "*** Test import of library was OK" 20 | 21 | # Cleanup 22 | deactivate 23 | rm -rf "$venv" 24 | exit 0 -------------------------------------------------------------------------------- /tests/run_integration_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | 5 | if [ -z "$OWM_API_KEY" ]; then 6 | echo "*** OWM_API_KEY env variable is not set: aborting" 7 | exit 1 8 | fi 9 | 10 | export OWM_API_KEY 11 | cd integration 12 | tox 13 | 14 | echo "*** End of integration tests" 15 | -------------------------------------------------------------------------------- /tests/run_proxy_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$OWM_API_KEY" ]; then 4 | echo "*** OWM_API_KEY env variable is not set: aborting" 5 | exit 1 6 | fi 7 | 8 | export OWM_API_KEY 9 | 10 | cd proxy 11 | 12 | # Build one-off dependency file 13 | DEPS_FILE="$(readlink . -f)/proxyreqs.txt" 14 | echo 'pytest' | cat - ../../requirements.txt > $DEPS_FILE 15 | 16 | 17 | # Run proxy server 18 | PID_FILE="$(readlink . -f)/proxy.pid" 19 | export PID_FILE 20 | 21 | #proxy.py --num-workers 1 --hostname 127.0.0.1 --port 8899 --basic-auth user:pass --pid-file "$PID_FILE" > /dev/null 2>&1 & 22 | pproxy -l http+socks4+socks5://127.0.0.1:8899 --ssl selfsigned.crt > /dev/null 2>&1 & 23 | echo $$ >"$PID_FILE" 24 | 25 | # Run tests 26 | echo "*** Running tests... " 27 | tox 28 | 29 | 30 | # Shut down proxy 31 | echo "*** Killing proxy... " 32 | PID=$(cat $PID_FILE) 33 | rm "$PID_FILE" 34 | rm "$DEPS_FILE" 35 | 36 | kill -9 "$PID" 37 | 38 | echo "*** End of proxy tests" -------------------------------------------------------------------------------- /tests/run_unit_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | 5 | cd .. 6 | tox 7 | -------------------------------------------------------------------------------- /tests/test_readme.py: -------------------------------------------------------------------------------- 1 | from pyowm import OWM 2 | from pyowm.utils import config 3 | from pyowm.utils import timestamps 4 | 5 | # ---------- FREE API KEY examples --------------------- 6 | 7 | owm = OWM('your free OWM API key') 8 | mgr = owm.weather_manager() 9 | 10 | 11 | # Search for current weather in London (Great Britain) and get details 12 | observation = mgr.weather_at_place('London,GB') 13 | w = observation.weather 14 | 15 | w.detailed_status # 'clouds' 16 | w.wind() # {'speed': 4.6, 'deg': 330} 17 | w.humidity # 87 18 | w.temperature('celsius') # {'temp_max': 10.5, 'temp': 9.7, 'temp_min': 9.0} 19 | w.rain # {} 20 | w.heat_index # None 21 | w.clouds # 75 22 | 23 | # Will it be clear tomorrow at this time in Milan (Italy) ? 24 | forecast = mgr.forecast_at_place('Milan,IT', 'daily') 25 | answer = forecast.will_be_clear_at(timestamps.tomorrow()) 26 | 27 | # ---------- PAID API KEY example --------------------- 28 | 29 | config_dict = config.get_default_config_for_subscription_type('professional') 30 | owm = OWM('your paid OWM API key', config_dict) 31 | 32 | # What's the current humidity in Berlin (Germany) ? 33 | one_call_object = mgr.one_call(lat=52.5244, lon=13.4105) 34 | one_call_object.current.humidity 35 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/agroapi10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/agroapi10/__init__.py -------------------------------------------------------------------------------- /tests/unit/agroapi10/test_enums.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyowm.agroapi10.enums import PresetEnum, PaletteEnum, SatelliteEnum 4 | 5 | 6 | class TestPresetEnum(unittest.TestCase): 7 | 8 | def test_items(self): 9 | preset_enum = PresetEnum() 10 | items = preset_enum.items() 11 | 12 | self.assertEqual(4, len(items)) 13 | self.assertEqual(sorted([preset_enum.TRUE_COLOR, 14 | preset_enum.FALSE_COLOR, 15 | preset_enum.NDVI, 16 | preset_enum.EVI]), 17 | sorted(items)) 18 | 19 | 20 | class TestSatelliteEnum(unittest.TestCase): 21 | 22 | def test_items(self): 23 | satellite_enum = SatelliteEnum() 24 | items = satellite_enum.items() 25 | 26 | self.assertEqual(2, len(items)) 27 | self.assertEqual([satellite_enum.LANDSAT_8, satellite_enum.SENTINEL_2], 28 | items) 29 | 30 | 31 | class TestPaletteEnum(unittest.TestCase): 32 | 33 | def test_items(self): 34 | palette_enum = PaletteEnum() 35 | items = palette_enum.items() 36 | 37 | self.assertEqual(4, len(items)) 38 | self.assertEqual(sorted([palette_enum.GREEN, 39 | palette_enum.BLACK_AND_WHITE, 40 | palette_enum.CONTRAST_SHIFTED, 41 | palette_enum.CONTRAST_CONTINUOUS]), 42 | sorted(items)) 43 | -------------------------------------------------------------------------------- /tests/unit/agroapi10/test_polygon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.agroapi10.polygon import Polygon, GeoPoint, GeoPolygon 6 | 7 | 8 | class TestPolygon(unittest.TestCase): 9 | 10 | geopoint= GeoPoint(34, -56.3) 11 | geopolygon = GeoPolygon([ 12 | [[2.3, 57.32], [23.19, -20.2], [-120.4, 19.15], [2.3, 57.32]] 13 | ]) 14 | 15 | def test_polygon_fails_with_wrong_parameters(self): 16 | 17 | self.assertRaises(AssertionError, Polygon, None, 'polygon', self.geopolygon, self.geopoint, 123.4, 'user') 18 | self.assertRaises(AssertionError, Polygon, 'id', 'polygon', 'wrong', self.geopoint, 123.4, 'user') 19 | self.assertRaises(AssertionError, Polygon, None, 'polygon', self.geopolygon, 'wrong', 123.4, 'user') 20 | self.assertRaises(AssertionError, Polygon, None, 'polygon', self.geopolygon, self.geopoint, None, 'user') 21 | self.assertRaises(AssertionError, Polygon, None, 'polygon', self.geopolygon, self.geopoint, -77, 'user') 22 | 23 | def test_area_kilometers_property(self): 24 | area_hs = 456.78 25 | expected = area_hs * 0.01 26 | instance = Polygon('id', 'polygon', self.geopolygon, self.geopoint, area_hs, 'user') 27 | self.assertEqual(expected, instance.area_km) 28 | instance = Polygon('id', 'polygon', self.geopolygon, self.geopoint, None, 'user') 29 | self.assertIsNone(instance.area_km) 30 | 31 | def test_from_dict(self): 32 | _id = "5abb9fb82c8897000bde3e87" 33 | name = "Polygon Sample" 34 | coords = [121.1867, 37.6739] 35 | geopolygon = GeoPolygon([[ 36 | [-121.1958, 37.6683], 37 | [-121.1779, 37.6687], 38 | [-121.1773, 37.6792], 39 | [-121.1958, 37.6792], 40 | [-121.1958, 37.6683]]]) 41 | center = GeoPoint(coords[0], coords[1]) 42 | area = 190.6343 43 | user_id = "557066d0ff7a7e3897531d94" 44 | the_dict = { 45 | "id": _id, 46 | "geo_json": { 47 | "type": "Feature", 48 | "properties": { 49 | 50 | }, 51 | "geometry": { 52 | "type": "Polygon", 53 | "coordinates": [ 54 | [ 55 | [-121.1958, 37.6683], 56 | [-121.1779, 37.6687], 57 | [-121.1773, 37.6792], 58 | [-121.1958, 37.6792], 59 | [-121.1958, 37.6683] 60 | ] 61 | ] 62 | } 63 | }, 64 | "name": name, 65 | "center": coords, 66 | "area": area, 67 | "user_id": user_id 68 | } 69 | expected = Polygon(_id, name, geopolygon, center, area, user_id) 70 | result = Polygon.from_dict(the_dict) 71 | self.assertEqual(expected.id, result.id) 72 | self.assertEqual(expected.name, result.name) 73 | self.assertEqual(expected.area, result.area) 74 | self.assertEqual(expected.user_id, result.user_id) 75 | self.assertEqual(expected.center.lat, result.center.lat) 76 | self.assertEqual(expected.center.lon, result.center.lon) 77 | self.assertEqual(expected.geopolygon.geojson(), result.geopolygon.geojson()) 78 | 79 | # now testing with dirty data 80 | self.assertRaises(AssertionError, Polygon.from_dict, None) 81 | 82 | the_dict['center'] = ['no_lon', 'no_lat'] 83 | self.assertRaises(ValueError, Polygon.from_dict, the_dict) 84 | the_dict['center'] = coords 85 | 86 | del the_dict['id'] 87 | self.assertRaises(AssertionError, Polygon.from_dict, the_dict) 88 | 89 | def test_repr(self): 90 | instance = Polygon('id', 'polygon', self.geopolygon, self.geopoint, 1.2, 'user') 91 | repr(instance) 92 | instance = Polygon('id') 93 | repr(instance) 94 | 95 | -------------------------------------------------------------------------------- /tests/unit/airpollutionapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/airpollutionapi30/__init__.py -------------------------------------------------------------------------------- /tests/unit/alertapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/alertapi30/__init__.py -------------------------------------------------------------------------------- /tests/unit/alertapi30/test_condition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | import pyowm.commons.exceptions 7 | from pyowm.alertapi30.condition import Condition 8 | from pyowm.alertapi30.enums import WeatherParametersEnum, OperatorsEnum 9 | 10 | 11 | class TestCondition(unittest.TestCase): 12 | 13 | def test_condition_fails_with_wrong_parameters(self): 14 | self.assertRaises(AssertionError, Condition, 15 | None, OperatorsEnum.EQUAL, 67.8) 16 | self.assertRaises(AssertionError, Condition, 17 | 123, OperatorsEnum.EQUAL, 67.8) 18 | self.assertRaises(AssertionError, Condition, 19 | WeatherParametersEnum.HUMIDITY, None, 67.8) 20 | self.assertRaises(AssertionError, Condition, 21 | WeatherParametersEnum.HUMIDITY, 123, 67.8) 22 | self.assertRaises(AssertionError, Condition, 23 | WeatherParametersEnum.HUMIDITY, OperatorsEnum.EQUAL, None) 24 | self.assertRaises(AssertionError, Condition, 25 | WeatherParametersEnum.HUMIDITY, OperatorsEnum.EQUAL, 'string') 26 | 27 | def test_from_dict(self): 28 | expected = Condition(WeatherParametersEnum.TEMPERATURE, OperatorsEnum.GREATER_THAN, 78.6, id='123456') 29 | the_dict = dict(name='temp', expression='$gt', 30 | amount=78.6, _id='123456') 31 | result = Condition.from_dict(the_dict) 32 | self.assertEqual(expected.weather_param, result.weather_param) 33 | self.assertEqual(expected.operator, result.operator) 34 | self.assertEqual(expected.amount, result.amount) 35 | self.assertEqual(expected.id, result.id) 36 | 37 | with self.assertRaises(pyowm.commons.exceptions.ParseAPIResponseError): 38 | Condition.from_dict(None) 39 | 40 | with self.assertRaises(pyowm.commons.exceptions.ParseAPIResponseError): 41 | Condition.from_dict(dict(nonexistent='key')) 42 | 43 | def test_to_dict(self): 44 | instance = Condition(WeatherParametersEnum.TEMPERATURE, OperatorsEnum.GREATER_THAN, 78.6, id='123456') 45 | result = instance.to_dict() 46 | self.assertIsInstance(result, dict) 47 | self.assertEqual('123456', result['id']) 48 | self.assertEqual(WeatherParametersEnum.TEMPERATURE, result['weather_param']) 49 | self.assertEqual(OperatorsEnum.GREATER_THAN, result['operator']) 50 | self.assertEqual(78.6, result['amount']) 51 | 52 | def test_repr(self): 53 | print(Condition(WeatherParametersEnum.TEMPERATURE, OperatorsEnum.GREATER_THAN, 78.6, id='123456')) 54 | -------------------------------------------------------------------------------- /tests/unit/alertapi30/test_enums.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pyowm.alertapi30.enums import AlertChannelsEnum, OperatorsEnum, WeatherParametersEnum 4 | 5 | 6 | class TestAlertChannelsEnum(unittest.TestCase): 7 | def test_items(self): 8 | alert_channels_enum = AlertChannelsEnum() 9 | self.assertEqual(alert_channels_enum.items(), 10 | [AlertChannelsEnum.OWM_API_POLLING]) 11 | 12 | 13 | class TestOperatorsEnum(unittest.TestCase): 14 | def test_items(self): 15 | operators_enum = OperatorsEnum() 16 | self.assertEqual(sorted(operators_enum.items()), sorted([operators_enum.GREATER_THAN, 17 | operators_enum.GREATER_THAN_EQUAL, 18 | operators_enum.LESS_THAN, 19 | operators_enum.LESS_THAN_EQUAL, 20 | operators_enum.EQUAL, 21 | operators_enum.NOT_EQUAL])) 22 | 23 | 24 | class TestWeatherParametersEnum(unittest.TestCase): 25 | def test_item(self): 26 | weather_parameters_enum = WeatherParametersEnum() 27 | self.assertEqual(sorted(weather_parameters_enum.items()), 28 | sorted([weather_parameters_enum.CLOUDS, weather_parameters_enum.HUMIDITY, 29 | weather_parameters_enum.PRESSURE, weather_parameters_enum.WIND_DIRECTION, 30 | weather_parameters_enum.WIND_SPEED, weather_parameters_enum.TEMPERATURE])) 31 | -------------------------------------------------------------------------------- /tests/unit/alertapi30/test_parsers.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/alertapi30/test_parsers.py -------------------------------------------------------------------------------- /tests/unit/commons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/commons/__init__.py -------------------------------------------------------------------------------- /tests/unit/commons/test_databoxes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.commons.databoxes import ImageType, Satellite, SubscriptionType 6 | 7 | 8 | class TestImageType(unittest.TestCase): 9 | 10 | def test_repr(self): 11 | instance = ImageType('PDF', 'application/pdf') 12 | repr(instance) 13 | 14 | 15 | class TestSatellite(unittest.TestCase): 16 | 17 | def test_repr(self): 18 | instance = Satellite('Terrasat', 'tst') 19 | repr(instance) 20 | 21 | 22 | class TestSubscriptionType(unittest.TestCase): 23 | 24 | def test_repr(self): 25 | instance = SubscriptionType('startup', 'pro', True) 26 | repr(instance) -------------------------------------------------------------------------------- /tests/unit/commons/test_enums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.commons.enums import ImageTypeEnum, SubscriptionTypeEnum 6 | from pyowm.commons.databoxes import ImageType 7 | 8 | 9 | class TestSubscriptionTypeEnum(unittest.TestCase): 10 | 11 | def test_lookup_by_name(self): 12 | name_not_found = 'non-existent' 13 | with self.assertRaises(ValueError): 14 | SubscriptionTypeEnum.lookup_by_name(name_not_found) 15 | 16 | 17 | class TestImageTypeEnum(unittest.TestCase): 18 | 19 | def test_lookup_by_mime_type(self): 20 | mime_not_found = 'unexistent/xyz' 21 | mime_found = 'image/png' 22 | result = ImageTypeEnum.lookup_by_mime_type(mime_found) 23 | self.assertTrue(isinstance(result, ImageType)) 24 | result = ImageTypeEnum.lookup_by_mime_type(mime_not_found) 25 | self.assertIsNone(result) 26 | 27 | def test_lookup_by_name(self): 28 | name_not_found = 'ZOOMOOO' 29 | name_found = 'GEOTIFF' 30 | result = ImageTypeEnum.lookup_by_name(name_found) 31 | self.assertTrue(isinstance(result, ImageType)) 32 | result = ImageTypeEnum.lookup_by_name(name_not_found) 33 | self.assertIsNone(result) 34 | 35 | -------------------------------------------------------------------------------- /tests/unit/commons/test_tile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.commons.tile import Tile 6 | from pyowm.utils.geo import Polygon 7 | from pyowm.commons.image import Image 8 | 9 | 10 | class TestTile(unittest.TestCase): 11 | 12 | def test_instantiation_fails_with_wrong_arguments(self): 13 | i = Image(b'x/1') 14 | self.assertRaises(AssertionError, Tile, -1, 2, 3, 'layer', i) 15 | self.assertRaises(AssertionError, Tile, 1, -2, 3, 'layer', i) 16 | self.assertRaises(AssertionError, Tile, 1, 2, -3, 'layer', i) 17 | self.assertRaises(AssertionError, Tile, 1, 2, 3, 'layer', 'not-an-image') 18 | 19 | def test_bounding_box(self): 20 | instance = Tile(0, 0, 18, 'temperature', Image(b'x/1')) 21 | result = instance.bounding_polygon() 22 | self.assertIsInstance(result, Polygon) 23 | 24 | def test_repr(self): 25 | print(Tile(0, 0, 18, 'temperature', Image(b'x/1'))) 26 | -------------------------------------------------------------------------------- /tests/unit/geocodingapi10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/geocodingapi10/__init__.py -------------------------------------------------------------------------------- /tests/unit/stationsapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/stationsapi30/__init__.py -------------------------------------------------------------------------------- /tests/unit/stationsapi30/test_persistence_backend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from unittest.mock import patch 6 | 7 | from pyowm.stationsapi30.measurement import Measurement 8 | from pyowm.stationsapi30.persistence_backend import JSONPersistenceBackend 9 | from pyowm.stationsapi30.buffer import Buffer 10 | 11 | 12 | class TestJSONPersistenceBackend(unittest.TestCase): 13 | file = 'file' 14 | test_id = 'test_id' 15 | test_timestamp = 1468274766 16 | 17 | def test_init(self): 18 | with patch('os.path.isfile', return_value=True): 19 | test_instance = JSONPersistenceBackend(self.file, self.test_id) 20 | self.assertEqual(test_instance._file_path, self.file) 21 | self.assertEqual(test_instance._station_id, self.test_id) 22 | 23 | # None json_file_path 24 | with patch('os.path.isfile', return_value=True): 25 | with self.assertRaises(AssertionError): 26 | JSONPersistenceBackend(None, self.test_id) 27 | 28 | # json_file_path is not file 29 | with patch('os.path.isfile', return_value=False): 30 | with self.assertRaises(AssertionError): 31 | JSONPersistenceBackend(self.file, self.test_id) 32 | 33 | def test_load_to_buffer(self): 34 | with patch('os.path.isfile', return_value=True): 35 | mocked_file_data = '[{"station_id": "test_id", "timestamp":142332322}]' 36 | with patch('builtins.open', unittest.mock.mock_open(read_data=mocked_file_data)) as mocked_open: 37 | test_instance = JSONPersistenceBackend(self.file, self.test_id) 38 | test_instance.load_to_buffer() 39 | mocked_open.assert_called_once_with(self.file, 'r') 40 | 41 | with self.assertRaises(ValueError): 42 | test_instance._station_id = None 43 | test_instance.load_to_buffer() 44 | 45 | def test_persist_buffer(self): 46 | with patch('os.path.isfile', return_value=True): 47 | mocked_file_data = '[{"station_id": "test_id", "timestamp":142332322}]' 48 | with patch('builtins.open', unittest.mock.mock_open(read_data=mocked_file_data)) as mocked_open: 49 | test_instance = JSONPersistenceBackend(self.file, self.test_id) 50 | test_buffer = Buffer(self.test_id) 51 | test_measurement = Measurement(self.test_id, self.test_timestamp) 52 | test_buffer.measurements = [test_measurement] 53 | test_instance.persist_buffer(test_buffer) 54 | mocked_open.assert_called_once_with(self.file, 'w') 55 | -------------------------------------------------------------------------------- /tests/unit/test_owm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.owm import OWM 6 | from pyowm.agroapi10.agro_manager import AgroManager 7 | from pyowm.airpollutionapi30.airpollution_manager import AirPollutionManager 8 | from pyowm.alertapi30.alert_manager import AlertManager 9 | from pyowm.commons.cityidregistry import CityIDRegistry 10 | from pyowm.geocodingapi10.geocoding_manager import GeocodingManager 11 | from pyowm.stationsapi30.stations_manager import StationsManager 12 | from pyowm.tiles.tile_manager import TileManager 13 | from pyowm.uvindexapi30.uvindex_manager import UVIndexManager 14 | from pyowm.weatherapi30.weather_manager import WeatherManager 15 | 16 | 17 | class TestOWM(unittest.TestCase): 18 | 19 | __test_instance = OWM('fake-api-key') 20 | 21 | def test_instantiation(self): 22 | with self.assertRaises(TypeError): 23 | OWM() 24 | with self.assertRaises(AssertionError): 25 | OWM(None) 26 | with self.assertRaises(AssertionError): 27 | OWM('fake-api-key', 123456) 28 | result = OWM('fake-api-key', dict()) 29 | self.assertIsInstance(result, OWM) 30 | 31 | def test_properties(self): 32 | version = self.__test_instance.version 33 | self.assertIsInstance(version, tuple) 34 | 35 | languages = self.__test_instance.supported_languages 36 | self.assertIsInstance(languages, list) 37 | self.assertTrue(all([isinstance(lang, str) for lang in languages])) 38 | 39 | config = self.__test_instance.configuration 40 | self.assertIsInstance(config, dict) 41 | 42 | def test_repr(self): 43 | print(self.__test_instance) 44 | 45 | def test_city_id_registry(self): 46 | result = self.__test_instance.city_id_registry() 47 | self.assertIsNotNone(result) 48 | self.assertIsInstance(result, CityIDRegistry) 49 | 50 | def test_stations_manager(self): 51 | result = self.__test_instance.stations_manager() 52 | self.assertTrue(result is not None) 53 | self.assertIsInstance(result, StationsManager) 54 | 55 | def test_alert_manager(self): 56 | result = self.__test_instance.alert_manager() 57 | self.assertTrue(result is not None) 58 | self.assertIsInstance(result, AlertManager) 59 | 60 | def test_uvindex_manager(self): 61 | result = self.__test_instance.uvindex_manager() 62 | self.assertTrue(result is not None) 63 | self.assertIsInstance(result, UVIndexManager) 64 | 65 | def test_airpollution_manager(self): 66 | result = self.__test_instance.airpollution_manager() 67 | self.assertTrue(result is not None) 68 | self.assertIsInstance(result, AirPollutionManager) 69 | 70 | def test_agro_manager(self): 71 | result = self.__test_instance.agro_manager() 72 | self.assertTrue(result is not None) 73 | self.assertIsInstance(result, AgroManager) 74 | 75 | def test_weather_manager(self): 76 | result = self.__test_instance.weather_manager() 77 | self.assertTrue(result is not None) 78 | self.assertIsInstance(result, WeatherManager) 79 | 80 | def test_tile_manager(self): 81 | result = self.__test_instance.tile_manager('test') 82 | self.assertIsNotNone(result) 83 | self.assertIsInstance(result, TileManager) 84 | 85 | def test_geocoding_manager(self): 86 | result = self.__test_instance.geocoding_manager() 87 | self.assertIsNotNone(result) 88 | self.assertIsInstance(result, GeocodingManager) 89 | -------------------------------------------------------------------------------- /tests/unit/tiles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/tiles/__init__.py -------------------------------------------------------------------------------- /tests/unit/tiles/test_tile_manager.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyowm.config import DEFAULT_CONFIG 3 | from pyowm.commons.http_client import HttpClient 4 | from pyowm.tiles.tile_manager import TileManager 5 | from pyowm.commons.tile import Tile 6 | from pyowm.tiles.enums import MapLayerEnum 7 | 8 | 9 | class MockHttpClientReturningTile(HttpClient): 10 | 11 | d = b'1234567890' 12 | 13 | def get_png(self, uri, params=None, headers=None): 14 | return 200, self.d 15 | 16 | 17 | class TestTileManager(unittest.TestCase): 18 | 19 | def test_instantiation_with_wrong_params(self): 20 | self.assertRaises(AssertionError, TileManager, None, MapLayerEnum.PRESSURE, dict()) 21 | self.assertRaises(AssertionError, TileManager, 'apikey', None, dict()) 22 | self.assertRaises(AssertionError, TileManager, 'apikey', MapLayerEnum.PRESSURE, None) 23 | 24 | def test_get_tile(self): 25 | mocked = MockHttpClientReturningTile('apikey', DEFAULT_CONFIG, 'anyurl.com') 26 | instance = TileManager('apikey', 'a_layer', DEFAULT_CONFIG) 27 | instance.http_client = mocked 28 | result = instance.get_tile(1, 2, 3) 29 | self.assertIsInstance(result, Tile) 30 | self.assertEqual(mocked.d, result.image.data) 31 | 32 | def test_repr(self): 33 | print(TileManager('apikey', 'a_layer', DEFAULT_CONFIG)) 34 | -------------------------------------------------------------------------------- /tests/unit/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/utils/__init__.py -------------------------------------------------------------------------------- /tests/unit/utils/test_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import pathlib 6 | import pyowm.commons.exceptions 7 | from pyowm.commons.enums import SubscriptionTypeEnum 8 | from pyowm.config import DEFAULT_CONFIG 9 | from pyowm.utils import config 10 | 11 | 12 | class TestConfig(unittest.TestCase): 13 | 14 | def test_get_default_config(self): 15 | result = config.get_default_config() 16 | self.assertEqual(result, DEFAULT_CONFIG) 17 | 18 | def test_get_config_from_failing(self): 19 | self.assertRaises(AssertionError, config.get_config_from, None) 20 | self.assertRaises(pyowm.commons.exceptions.ConfigurationNotFoundError, config.get_config_from, 21 | str(pathlib.Path('.').absolute())) 22 | 23 | def test_get_default_config_for_subscription_type(self): 24 | result = config.get_default_config_for_subscription_type('free') 25 | self.assertEqual(SubscriptionTypeEnum.FREE, result['subscription_type']) 26 | result = config.get_default_config_for_subscription_type('startup') 27 | self.assertEqual(SubscriptionTypeEnum.STARTUP, result['subscription_type']) 28 | result = config.get_default_config_for_subscription_type('developer') 29 | self.assertEqual(SubscriptionTypeEnum.DEVELOPER, result['subscription_type']) 30 | result = config.get_default_config_for_subscription_type('professional') 31 | self.assertEqual(SubscriptionTypeEnum.PROFESSIONAL, result['subscription_type']) 32 | result = config.get_default_config_for_subscription_type('enterprise') 33 | self.assertEqual(SubscriptionTypeEnum.ENTERPRISE, result['subscription_type']) 34 | with self.assertRaises(ValueError): 35 | config.get_default_config_for_subscription_type('non-existent') 36 | 37 | def test_get_default_config_for_proxy(self): 38 | test_url_1 = 'abc' 39 | test_url_2 = 'def' 40 | result = config.get_default_config_for_proxy(test_url_1, test_url_2) 41 | self.assertTrue(result['connection']['use_proxy']) 42 | self.assertEqual(result['proxies']['http'], test_url_1) 43 | self.assertEqual(result['proxies']['https'], test_url_2) 44 | -------------------------------------------------------------------------------- /tests/unit/utils/test_decorators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | import unittest 6 | import warnings 7 | from pyowm.utils import decorators 8 | 9 | 10 | class TestDecorators(unittest.TestCase): 11 | 12 | def test_deprecated_decorator(self): 13 | 14 | @decorators.deprecated() 15 | def warn_function_no_args(): 16 | pass 17 | 18 | with warnings.catch_warnings(record=True) as cm: 19 | self.assertEqual(len(cm), 0) 20 | warn_function_no_args() 21 | self.assertEqual(len(cm), 1) 22 | self.assertTrue(issubclass(cm[-1].category, DeprecationWarning)) 23 | 24 | action = 'modified' 25 | version = (7, 2, 0) 26 | name = 'test_name' 27 | 28 | @decorators.deprecated(will_be=action, on_version=version, name=name) 29 | def warn_function_all_args(): 30 | pass 31 | 32 | with warnings.catch_warnings(record=True) as cm: 33 | self.assertEqual(len(cm), 0) 34 | warn_function_all_args() 35 | self.assertEqual(len(cm), 1) 36 | self.assertTrue(issubclass(cm[-1].category, DeprecationWarning)) 37 | logged_line = cm[-1].message.args[0] 38 | self.assertIn(action, logged_line) 39 | self.assertIn('.'.join(map(str, version)), logged_line) 40 | self.assertIn(name, logged_line) 41 | -------------------------------------------------------------------------------- /tests/unit/utils/test_formatting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from datetime import datetime, timezone 6 | from pyowm.utils import formatting 7 | 8 | 9 | class TestTimeFormatUtils(unittest.TestCase): 10 | 11 | def test_timeformat(self): 12 | unixtime = 1378459200 13 | iso = "2013-09-06 09:20:00+00:00" 14 | date = datetime(2013, 9, 6, 9, 20, 0, 0, timezone.utc) 15 | self.assertEqual(unixtime, formatting.timeformat(unixtime, "unix")) 16 | self.assertEqual(iso, formatting.timeformat(unixtime, "iso")) 17 | self.assertEqual(date, formatting.timeformat(unixtime, "date")) 18 | self.assertEqual(unixtime, formatting.timeformat(iso, "unix")) 19 | self.assertEqual(iso, formatting.timeformat(iso, "iso")) 20 | self.assertEqual(date, formatting.timeformat(iso, "date")) 21 | self.assertEqual(unixtime, formatting.timeformat(date, "unix")) 22 | self.assertEqual(iso, formatting.timeformat(date, "iso")) 23 | self.assertEqual(date, formatting.timeformat(date, "date")) 24 | 25 | def test_timeformat_when_bad_timeformat_values(self): 26 | self.assertRaises(ValueError, 27 | formatting.timeformat, 1378459200, "xyz") 28 | 29 | def test_to_date(self): 30 | unixtime = 1378459200 31 | iso = "2013-09-06 09:20:00+00:00" 32 | date = datetime(2013, 9, 6, 9, 20, 0, 0, timezone.utc) 33 | self.assertEqual(date, formatting.to_date(unixtime)) 34 | self.assertEqual(date, formatting.to_date(iso)) 35 | self.assertEqual(date, formatting.to_date(date)) 36 | 37 | def test_to_date_fails_with_negative_values(self): 38 | self.assertRaises(ValueError, 39 | formatting.to_date, 40 | -1378459200) 41 | 42 | def test_to_date_fails_with_unproper_argument_type(self): 43 | self.assertRaises(TypeError, 44 | formatting.to_date, 45 | list()) 46 | 47 | def test_to_ISO8601(self): 48 | unixtime = 1378459200 49 | iso = "2013-09-06 09:20:00+00:00" 50 | date = datetime(2013, 9, 6, 9, 20, 0, 0, timezone.utc) 51 | self.assertEqual(iso, formatting.to_ISO8601(unixtime)) 52 | self.assertEqual(iso, formatting.to_ISO8601(iso)) 53 | self.assertEqual(iso, formatting.to_ISO8601(date)) 54 | 55 | def test_to_ISO8601_fails_with_negative_values(self): 56 | self.assertRaises(ValueError, 57 | formatting.to_ISO8601, 58 | -1378459200) 59 | 60 | def test_to_ISO8601_fails_with_unproper_argument_type(self): 61 | self.assertRaises(TypeError, 62 | formatting.to_ISO8601, 63 | list()) 64 | 65 | def test_ISO8601_to_UNIXtime(self): 66 | iso = "2013-09-06 09:20:00+00:00" 67 | expected = 1378459200 68 | self.assertEqual(expected, 69 | formatting.ISO8601_to_UNIXtime(iso)) 70 | 71 | def test_datetime_to_UNIXtime(self): 72 | date = datetime(2013, 9, 19, 12, 0, 0, 0, timezone.utc) 73 | expected = 1379592000 74 | self.assertEqual(formatting.datetime_to_UNIXtime(date), expected) 75 | 76 | def test_ISO8601_to_UNIXtime_fails_with_bad_arugments(self): 77 | self.assertRaises(ValueError, 78 | formatting.ISO8601_to_UNIXtime, 79 | "Tue, Sep 16 2013") 80 | 81 | def test_to_UNIXtime(self): 82 | unix = 1378459200 83 | iso = "2013-09-06 09:20:00+00:00" 84 | date = datetime(2013, 9, 6, 9, 20, 0, 0, timezone.utc) 85 | self.assertEqual(unix, formatting.to_UNIXtime(unix)) 86 | self.assertEqual(unix, formatting.to_UNIXtime(iso)) 87 | self.assertEqual(unix, formatting.to_UNIXtime(date)) 88 | 89 | def test_to_UNIXtime_fails_with_bad_argument(self): 90 | self.assertRaises(TypeError, formatting.to_UNIXtime, None) 91 | 92 | def test_to_UNIXtime_fails_with_negative_unixtime(self): 93 | self.assertRaises(ValueError, formatting.to_UNIXtime, -1234) 94 | -------------------------------------------------------------------------------- /tests/unit/utils/test_strings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.utils import strings 6 | 7 | 8 | class Placeholder: 9 | pass # don't move! 10 | 11 | 12 | class TestStringUtils(unittest.TestCase): 13 | 14 | def test_obfuscate_API_key(self): 15 | API_key = '22e28da2669c4283acdbd9cfa7dc0903' 16 | expected = '************************a7dc0903' 17 | 18 | self.assertEqual(expected, strings.obfuscate_API_key(API_key)) 19 | self.assertIsNone(strings.obfuscate_API_key(None)) 20 | 21 | def test_version_tuple_to_str(self): 22 | version_tuple = (1, 4, 6) 23 | expected = '1.4.6' 24 | result = strings.version_tuple_to_str(version_tuple) 25 | self.assertEqual(expected, result) 26 | version_tuple = (1, 4, 6, 9) 27 | separator = ';' 28 | expected = '1;4;6;9' 29 | result = strings.version_tuple_to_str(version_tuple, separator=separator) 30 | self.assertEqual(expected, result) 31 | 32 | def test_class_from_dotted_path(self): 33 | path = 'tests.unit.utils.test_strings.Placeholder' 34 | result = strings.class_from_dotted_path(path) 35 | assert result == Placeholder 36 | -------------------------------------------------------------------------------- /tests/unit/uvindexapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/uvindexapi30/__init__.py -------------------------------------------------------------------------------- /tests/unit/uvindexapi30/test_uv_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.uvindexapi30.uv_client import UltraVioletHttpClient 6 | from pyowm.commons.http_client import HttpClient 7 | from pyowm.config import DEFAULT_CONFIG 8 | from pyowm.utils import formatting 9 | 10 | 11 | class TestUVClient(unittest.TestCase): 12 | 13 | __instance = UltraVioletHttpClient('xyz', HttpClient('apikey', DEFAULT_CONFIG, 'anyurl.com')) 14 | 15 | def test_trim_to(self): 16 | ts = formatting.to_date(1463041620) # 2016-05-12T08:27:00Z 17 | self.assertEqual(self.__instance._trim_to(ts, 'minute'), 18 | '2016-05-12T08:27Z') 19 | self.assertEqual(self.__instance._trim_to(ts, 'hour'), 20 | '2016-05-12T08Z') 21 | self.assertEqual(self.__instance._trim_to(ts, 'day'), 22 | '2016-05-12Z') 23 | self.assertEqual(self.__instance._trim_to(ts, 'month'), 24 | '2016-05Z') 25 | self.assertEqual(self.__instance._trim_to(ts, 'year'), 26 | '2016Z') 27 | self.assertRaises(ValueError, self.__instance._trim_to, 28 | ts, 'abcdef') 29 | 30 | def test_get_uvi(self): 31 | # case: current UV index 32 | params = {'lon': 8.25, 'lat': 43.75} 33 | expected = {k: str(v) for k, v in params.items()} 34 | 35 | def mock_func(uri, params=None, headers=None): 36 | return 200, (uri, params) 37 | 38 | self.__instance._client.get_json = mock_func 39 | 40 | result = self.__instance.get_uvi(params) 41 | self.assertEqual('uvi', 42 | result[0]) 43 | self.assertEqual(expected, result[1]) 44 | 45 | def test_get_uvi_forecast(self): 46 | params = {'lon': 8.25, 'lat': 43.75} 47 | expected = {k: str(v) for k, v in params.items()} 48 | 49 | def mock_func(uri, params=None, headers=None): 50 | return 200, (uri, params) 51 | 52 | self.__instance._client.get_json = mock_func 53 | 54 | result = self.__instance.get_uvi_forecast(params) 55 | self.assertEqual('uvi/forecast', 56 | result[0]) 57 | self.assertEqual(expected, result[1]) 58 | 59 | def test_get_uvi_history(self): 60 | params = {'lon': 8.25, 'lat': 43.75, 'start': 1498049953, 61 | 'end': 1498481991} 62 | expected = {k: str(v) for k, v in params.items()} 63 | 64 | def mock_func(uri, params=None, headers=None): 65 | return 200, (uri, params) 66 | 67 | self.__instance._client.get_json = mock_func 68 | 69 | result = self.__instance.get_uvi_history(params) 70 | self.assertEqual('uvi/history', 71 | result[0]) 72 | self.assertEqual(expected, result[1]) 73 | 74 | def test_repr(self): 75 | print(self.__instance) 76 | -------------------------------------------------------------------------------- /tests/unit/weatherapi30/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csparpa/pyowm/334d04ae9bbb43689b586ee7a347098e05c1e8e1/tests/unit/weatherapi30/__init__.py -------------------------------------------------------------------------------- /tests/unit/weatherapi30/test_national_weather_alert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import json 6 | from pyowm.weatherapi30.national_weather_alert import NationalWeatherAlert 7 | from pyowm.commons.exceptions import APIResponseError, ParseAPIResponseError 8 | 9 | 10 | class TestNationalWeatherAlert(unittest.TestCase): 11 | 12 | __test_time_start= 1629648000 13 | __test_time_end = 1629723600 14 | __test_sender = "Deutscher Wetterdienst" 15 | __test_title = "very heavy / persistent rain" 16 | __test_description = "There is a high potential for the development of very heavy / heavy persistent rain." 17 | __test_tags = ['Rain'] 18 | __test_instance = NationalWeatherAlert(__test_sender, __test_title, __test_description, __test_time_start, 19 | __test_time_end, __test_tags) 20 | 21 | __bad_json = '{"a": "test", "b": 1.234, "c": [ "hello", "world"] }' 22 | __bad_json_2 = '{"message": "test", "cod": "500"}' 23 | __no_items_json = '{"cod": "200", "count": "0" }' 24 | __404_json = '{"cod": "404" }' 25 | 26 | NATIONAL_WEATHER_ALERT_JSON_DUMP = '{"sender_name": "Deutscher Wetterdienst", "event": "very heavy / persistent rain", ' \ 27 | '"start": 1629648000, "end": 1629723600, "description": "There is a high potential ' \ 28 | 'for the development of very heavy / heavy persistent rain.", "tags": ["Rain"]}' 29 | 30 | def test_init_failures(self): 31 | self.assertRaises(AssertionError, NationalWeatherAlert, None, self.__test_title, self.__test_description, 32 | self.__test_time_start, self.__test_time_end) 33 | self.assertRaises(AssertionError, NationalWeatherAlert, self.__test_sender, None, self.__test_description, 34 | self.__test_time_start, self.__test_time_end) 35 | self.assertRaises(AssertionError, NationalWeatherAlert, self.__test_sender, self.__test_title, None, 36 | self.__test_time_start, self.__test_time_end) 37 | self.assertRaises(AssertionError, NationalWeatherAlert, self.__test_sender, self.__test_title, self.__test_description, 38 | None, self.__test_time_end) 39 | self.assertRaises(AssertionError, NationalWeatherAlert, self.__test_sender, self.__test_title, self.__test_description, 40 | self.__test_time_start, None) 41 | self.assertRaises(ValueError, NationalWeatherAlert, self.__test_sender, self.__test_title, self.__test_description, 42 | self.__test_time_start, self.__test_time_end, 'testtesttest') 43 | 44 | def test_from_dict(self): 45 | d = json.loads(self.NATIONAL_WEATHER_ALERT_JSON_DUMP) 46 | result = NationalWeatherAlert.from_dict(d) 47 | self.assertTrue(result is not None) 48 | self.assertFalse(result.sender is None) 49 | self.assertFalse(result.title is None) 50 | self.assertFalse(result.description is None) 51 | self.assertFalse(result.start_time() is None) 52 | self.assertFalse(result.end_time() is None) 53 | self.assertTrue(isinstance(result.tags, list)) 54 | 55 | def test_from_dict_fails_when_JSON_data_is_None(self): 56 | with self.assertRaises(ParseAPIResponseError): 57 | NationalWeatherAlert.from_dict(None) 58 | 59 | def test_to_dict(self): 60 | expected = json.loads(self.NATIONAL_WEATHER_ALERT_JSON_DUMP) 61 | result = self.__test_instance.to_dict() 62 | self.assertEqual(expected, result) 63 | 64 | def test__repr(self): 65 | print(self.__test_instance) -------------------------------------------------------------------------------- /tests/unit/weatherapi30/test_stationhistory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import unittest 6 | from datetime import datetime 7 | from pyowm.commons.exceptions import APIResponseError, ParseAPIResponseError 8 | from pyowm.weatherapi30.stationhistory import StationHistory 9 | from tests.unit.weatherapi30.json_test_responses import ( 10 | STATION_TICK_WEATHER_HISTORY_JSON, STATION_WEATHER_HISTORY_NOT_FOUND_JSON, 11 | INTERNAL_SERVER_ERROR_JSON) 12 | 13 | 14 | class TestStationHistory(unittest.TestCase): 15 | 16 | __test_station_ID = 2865 17 | __test_interval = "tick" 18 | __test_reception_time = 1378684800 19 | __test_reception_time_iso = '2013-09-09 00:00:00+00:00' 20 | __test_date_reception_time = datetime.fromisoformat(__test_reception_time_iso) 21 | __test_measurements = { 22 | '1362933983': { 23 | "temperature": 266.25, 24 | "humidity": 27.3, 25 | "pressure": 1010.02, 26 | "rain": None, 27 | "wind": 4.7 28 | }, 29 | '1362934043': { 30 | "temperature": 266.85, 31 | "humidity": 27.7, 32 | "pressure": 1010.09, 33 | "rain": None, 34 | "wind": 4.7 35 | } 36 | } 37 | 38 | __test_instance = StationHistory(__test_station_ID, 'tick', 39 | __test_reception_time, 40 | __test_measurements) 41 | 42 | __bad_json = '{"a": "test", "b": 1.234, "c": [ "hello", "world"] }' 43 | 44 | STATIONHISTORY_JSON_DUMP = '{"reception_time": 1378684800, "interval": ' \ 45 | + '"tick", "measurements": {"1362934043": {"wind": 4.7, "pressure": ' \ 46 | + '1010.09, "temperature": 266.85, "rain": null, "humidity": 27.7}, ' \ 47 | + '"1362933983": {"wind": 4.7, "pressure": 1010.02, "temperature": ' \ 48 | + '266.25, "rain": null, "humidity": 27.3}}, "station_ID": 2865}' 49 | 50 | def test_init_fails_when_negative_reception_time(self): 51 | self.assertRaises(ValueError, StationHistory, 1234, 'tick', -1234567, 52 | self.__test_measurements) 53 | 54 | def test_returning_different_formats_for_reception_time(self): 55 | """ 56 | Test get_reception_time returns timestamps in the expected formats 57 | """ 58 | self.assertEqual(self.__test_instance.reception_time(timeformat='iso'), 59 | self.__test_reception_time_iso) 60 | self.assertEqual(self.__test_instance.reception_time(timeformat='unix'), 61 | self.__test_reception_time) 62 | self.assertEqual(self.__test_instance.reception_time(timeformat='date'), \ 63 | self.__test_date_reception_time) 64 | 65 | def test_from_dict(self): 66 | result = StationHistory.from_dict(json.loads(STATION_TICK_WEATHER_HISTORY_JSON)) 67 | self.assertTrue(result) 68 | self.assertTrue(isinstance(result, StationHistory)) 69 | self.assertTrue(result.measurements) 70 | 71 | def test_from_dict_fails_when_JSON_data_is_None(self): 72 | self.assertRaises(ParseAPIResponseError, StationHistory.from_dict, None) 73 | 74 | def test_from_dict_with_malformed_JSON_data(self): 75 | self.assertRaises(ParseAPIResponseError, StationHistory.from_dict, json.loads(self.__bad_json)) 76 | 77 | def test_from_dict_with_empty_data(self): 78 | json_data = '{"message": "","cod": "200","type": "hour","station_id": ' \ 79 | '35579,"calctime": 0.1122,"cnt": 1,"list": [{"main": "test","dt": ' \ 80 | '1381140000}]}' 81 | result = StationHistory.from_dict(json.loads(json_data)) 82 | datapoints = result.measurements 83 | for datapoint in datapoints: 84 | self.assertTrue(all(value is None for value \ 85 | in datapoints[datapoint].values())) 86 | 87 | def test_from_dict_when_station_not_found(self): 88 | self.assertFalse(StationHistory.from_dict(json.loads(STATION_WEATHER_HISTORY_NOT_FOUND_JSON))) 89 | 90 | def test_from_dict_when_server_error(self): 91 | self.assertRaises(APIResponseError, StationHistory.from_dict, json.loads(INTERNAL_SERVER_ERROR_JSON)) 92 | 93 | def test_to_dict(self): 94 | expected = json.loads(self.STATIONHISTORY_JSON_DUMP) 95 | result = self.__test_instance.to_dict() 96 | self.assertEqual(expected, result) 97 | 98 | def test__repr(self): 99 | print(self.__test_instance) 100 | -------------------------------------------------------------------------------- /tests/unit/weatherapi30/test_weathercoderegistry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from pyowm.weatherapi30.weathercoderegistry import WeatherCodeRegistry 6 | 7 | 8 | class TestWeatherCodeRegistry(unittest.TestCase): 9 | 10 | __test_instance = WeatherCodeRegistry({ 11 | "abc": [{ 12 | "start": 1, 13 | "end": 100 14 | }, 15 | { 16 | "start": 120, 17 | "end": 160 18 | }], 19 | "xyz": [{ 20 | "start": 345, 21 | "end": 345 22 | }] 23 | }) 24 | 25 | def test_wrong_instantiation_parameters(self): 26 | self.assertRaises(AssertionError, WeatherCodeRegistry, 'this-is-not-a-dict') 27 | 28 | def test_status_for(self): 29 | self.assertTrue(self.__test_instance.status_for(999) is None) 30 | self.assertEqual("abc", self.__test_instance.status_for(150)) 31 | self.assertEqual("xyz", self.__test_instance.status_for(345)) 32 | 33 | def test_get_instance(self): 34 | result = WeatherCodeRegistry.get_instance() 35 | self.assertTrue(isinstance(result, WeatherCodeRegistry)) 36 | 37 | def test_repr(self): 38 | print(self.__test_instance) 39 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py37, py38, py39, coverage 4 | skip_missing_interpreters = 5 | True 6 | 7 | [testenv] 8 | deps = -rrequirements.txt 9 | commands = 10 | python setup.py test -s tests.unit 11 | 12 | [testenv:coverage] 13 | whitelist_externals = coverage 14 | commands = 15 | coverage run --rcfile=.coveragerc setup.py test -s tests.unit 16 | coverage html 17 | coverage report 18 | --------------------------------------------------------------------------------