Repository: csparpa/pyowm (language: python)

Issue:
Python 3.9 support

Air Pollution Manager
Hello, whenever I call any of the air quality indexes, I get an exception. 
Python 3.9

e.g. Code:
openWMap=pyowm.OWM(APIKEY) 
mgrAir = openWMap.airpollution_manager()
# I get an error when the next line is run
coIndex_1 = mgrAir.coindex_around_coords(51.507351, -0.127758) # London, GB coordinates

Console Output
Traceback (most recent call last):
  File "weatherForecast.py", line 49, in <module>
    coIndex_1 = mgrAir.coindex_around_coords(51.507351, -0.127758)
  File "weatherForecast\venv\lib\site-packages\pyowm\airpollutionapi30\airpollution_manager.py", line 68, in coindex_around_coords
    json_data = self.ap_client.get_coi(params)
  File "weatherForecast\venv\lib\site-packages\pyowm\airpollutionapi30\airpollution_client.py", line 65, in get_coi
    _, json_data = self._client.get_json(uri)
  File "weatherForecast\venv\lib\site-packages\pyowm\commons\http_client.py", line 143, in get_json
    HttpClient.check_status_code(resp.status_code, resp.text)
  File "weatherForecast\venv\lib\site-packages\pyowm\commons\http_client.py", line 288, in check_status_code
    raise exceptions.NotFoundError('Unable to find the resource')
pyowm.commons.exceptions.NotFoundError: Unable to find the resource

Process finished with exit code 1

_________________
Is this a bug? Thank you.
Geocoding API support
https://openweathermap.org/api/geocoding-api


Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index ca7f5a37..2f8c8441 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,5 @@ tests/proxy/.tox/*
 .cache
 .eggs/
 htmlcov/*
+venv
 
diff --git a/.travis.yml b/.travis.yml
index bc9435f7..e667e792 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,7 @@ language: python
 python:
   - "3.7"
   - "3.8"
+  - "3.9-dev"
 script: tox
 after_success:
   - coveralls
@@ -34,6 +35,8 @@ jobs:
       script: bash tests/local_installation_test.sh
     - <<: *local_installation_test
       python: "3.8"
+    - <<: *local_installation_test
+      python: "3.9-dev"
 
     - &coverage
       stage: "Coverage"
@@ -41,6 +44,8 @@ jobs:
       script: coverage run --rcfile=.coveragerc setup.py test -s tests.unit
     - <<: *coverage
       python: "3.8"
+    - <<: *coverage
+      python: "3.9-dev"
 
     - stage: "Build docs"
       script: cd sphinx && make clean && make html
@@ -51,6 +56,8 @@ jobs:
       script: bash deploy/deploy_to_pypi.sh
     - <<: *deploy_to_pypi
       python: "3.8"
+    - <<: *deploy_to_pypi
+      python: "3.9-dev"
 
     - &pypi_installation_test
       stage: "PyPI Installation Test"
@@ -59,3 +66,5 @@ jobs:
       script: bash tests/pypi_installation_test.sh
     - <<: *pypi_installation_test
       python: "3.8"
+    - <<: *pypi_installation_test
+      python: "3.9-dev"
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 1a40ff4f..511a0559 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -5,18 +5,22 @@ Contributors will be shown in alphabetical order
 
 Code
 ----
+  * [ahertz](https://github.com/ahertz)
   * [alechewitt](https://github.com/alechewitt)
   * [camponez](https://github.com/camponez)
+  * [Darumin](https://github.com/Darumin)
   * [dev-iks](https://github.com/dev-iks)
   * [dphildebrandt](https://github.com/dphildebrandt)
   * [dstmar](https://github.com/dstmar)
   * [edenhaus](https://github.com/edenhaus)
   * [eumiro](https://github.com/eumiro)
   * [ggstuart](https://github.com/ggstuart)
+  * [irahorecka](https://github.com/irahorecka)
   * [jwmelvin](https://github.com/jwmelvin)
   * [lardconcepts](https://github.com/lardconcepts)
   * [liato](https://github.com/liato)
   * [LukasBoersma](https://github.com/LukasBoersma)
+  * [MatthiasLohr](https://github.com/MatthiasLohr)  
   * [Misiu](https://github.com/Misiu)
   * [Noid](https://github.com/n0id)
   * [titilambert](https://github.com/titilambert)
@@ -26,9 +30,11 @@ Code
 
 Docs
 ----
+  * [Crozzers](https://github.com/Crozzers)
   * [EJEP](https://github.com/EJEP)
   * [Franzqat](https://github.com/franzqat)
   * [Harmon758](https://github.com/Harmon758)
+  * [joe-meyer](https://github.com/joe-meyer)
 
 Testing
 -------
@@ -40,6 +46,7 @@ Testing
 Packaging and Distribution
 --------------------------
   * [Diapente](https://github.com/Diapente)
+  * [onkelbeh](https://github.com/onkelbeh)
   * [Simone-Zabberoni](https://github.com/Simone-Zabberoni)
 
 Wiki
diff --git a/Pipfile.lock b/Pipfile.lock
index 19c05f91..d6407853 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -16,17 +16,18 @@
     "default": {
         "certifi": {
             "hashes": [
-                "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
-                "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
+                "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
+                "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
             ],
-            "version": "==2020.6.20"
+            "version": "==2020.12.5"
         },
         "chardet": {
             "hashes": [
-                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
-                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
+                "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
+                "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
             ],
-            "version": "==3.0.4"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==4.0.0"
         },
         "geojson": {
             "hashes": [
@@ -58,19 +59,19 @@
                 "socks"
             ],
             "hashes": [
-                "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
-                "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
+                "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
+                "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
             ],
             "index": "pypi",
-            "version": "==2.24.0"
+            "version": "==2.25.1"
         },
         "urllib3": {
             "hashes": [
-                "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
-                "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
+                "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
+                "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
-            "version": "==1.25.10"
+            "version": "==1.26.3"
         }
     },
     "develop": {
@@ -90,90 +91,94 @@
         },
         "attrs": {
             "hashes": [
-                "sha256:26b54ddbbb9ee1d34d5d3668dd37d6cf74990ab23c828c2888dccdceee395594",
-                "sha256:fce7fc47dfc976152e82d53ff92fa0407700c21acd20886a13777a0d20e655dc"
+                "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
+                "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==20.2.0"
+            "version": "==20.3.0"
         },
         "babel": {
             "hashes": [
-                "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
-                "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
+                "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5",
+                "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==2.8.0"
+            "version": "==2.9.0"
         },
         "bleach": {
             "hashes": [
                 "sha256:52b5919b81842b1854196eaae5ca29679a2f2e378905c346d3ca8227c2c66080",
+                "sha256:6123ddc1052673e52bab52cdc955bcb57a015264a1c57d37bea2f6b817af0125",
+                "sha256:98b3170739e5e83dd9dc19633f074727ad848cbedb6026708c8ac2d3b697a433",
                 "sha256:9f8ccbeb6183c6e6cddea37592dfb0167485c1e3b13b3363bc325aa8bda3adbd"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==3.2.1"
+            "version": "==3.3.0"
         },
         "certifi": {
             "hashes": [
-                "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
-                "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
+                "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
+                "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
             ],
-            "version": "==2020.6.20"
+            "version": "==2020.12.5"
         },
         "cffi": {
             "hashes": [
-                "sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d",
-                "sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b",
-                "sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4",
-                "sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f",
-                "sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3",
-                "sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579",
-                "sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537",
-                "sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e",
-                "sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05",
-                "sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171",
-                "sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca",
-                "sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522",
-                "sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c",
-                "sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc",
-                "sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d",
-                "sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808",
-                "sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828",
-                "sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869",
-                "sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d",
-                "sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9",
-                "sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0",
-                "sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc",
-                "sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15",
-                "sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c",
-                "sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a",
-                "sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3",
-                "sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1",
-                "sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768",
-                "sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d",
-                "sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b",
-                "sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e",
-                "sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d",
-                "sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730",
-                "sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394",
-                "sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1",
-                "sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591"
-            ],
-            "version": "==1.14.3"
+                "sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
+                "sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
+                "sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
+                "sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
+                "sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
+                "sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
+                "sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
+                "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
+                "sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
+                "sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
+                "sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
+                "sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
+                "sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
+                "sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
+                "sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
+                "sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
+                "sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
+                "sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
+                "sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
+                "sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
+                "sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
+                "sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
+                "sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
+                "sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
+                "sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
+                "sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
+                "sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
+                "sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
+                "sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
+                "sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
+                "sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
+                "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
+                "sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
+                "sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
+                "sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
+                "sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
+                "sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
+            ],
+            "version": "==1.14.5"
         },
         "chardet": {
             "hashes": [
-                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
-                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
+                "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
+                "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
             ],
-            "version": "==3.0.4"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==4.0.0"
         },
         "colorama": {
             "hashes": [
-                "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
-                "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
+                "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
+                "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==0.4.3"
+            "version": "==0.4.4"
         },
         "commonmark": {
             "hashes": [
@@ -184,79 +189,84 @@
         },
         "coverage": {
             "hashes": [
-                "sha256:0203acd33d2298e19b57451ebb0bed0ab0c602e5cf5a818591b4918b1f97d516",
-                "sha256:0f313707cdecd5cd3e217fc68c78a960b616604b559e9ea60cc16795c4304259",
-                "sha256:1c6703094c81fa55b816f5ae542c6ffc625fec769f22b053adb42ad712d086c9",
-                "sha256:1d44bb3a652fed01f1f2c10d5477956116e9b391320c94d36c6bf13b088a1097",
-                "sha256:280baa8ec489c4f542f8940f9c4c2181f0306a8ee1a54eceba071a449fb870a0",
-                "sha256:29a6272fec10623fcbe158fdf9abc7a5fa032048ac1d8631f14b50fbfc10d17f",
-                "sha256:2b31f46bf7b31e6aa690d4c7a3d51bb262438c6dcb0d528adde446531d0d3bb7",
-                "sha256:2d43af2be93ffbad25dd959899b5b809618a496926146ce98ee0b23683f8c51c",
-                "sha256:381ead10b9b9af5f64646cd27107fb27b614ee7040bb1226f9c07ba96625cbb5",
-                "sha256:47a11bdbd8ada9b7ee628596f9d97fbd3851bd9999d398e9436bd67376dbece7",
-                "sha256:4d6a42744139a7fa5b46a264874a781e8694bb32f1d76d8137b68138686f1729",
-                "sha256:50691e744714856f03a86df3e2bff847c2acede4c191f9a1da38f088df342978",
-                "sha256:530cc8aaf11cc2ac7430f3614b04645662ef20c348dce4167c22d99bec3480e9",
-                "sha256:582ddfbe712025448206a5bc45855d16c2e491c2dd102ee9a2841418ac1c629f",
-                "sha256:63808c30b41f3bbf65e29f7280bf793c79f54fb807057de7e5238ffc7cc4d7b9",
-                "sha256:71b69bd716698fa62cd97137d6f2fdf49f534decb23a2c6fc80813e8b7be6822",
-                "sha256:7858847f2d84bf6e64c7f66498e851c54de8ea06a6f96a32a1d192d846734418",
-                "sha256:78e93cc3571fd928a39c0b26767c986188a4118edc67bc0695bc7a284da22e82",
-                "sha256:7f43286f13d91a34fadf61ae252a51a130223c52bfefb50310d5b2deb062cf0f",
-                "sha256:86e9f8cd4b0cdd57b4ae71a9c186717daa4c5a99f3238a8723f416256e0b064d",
-                "sha256:8f264ba2701b8c9f815b272ad568d555ef98dfe1576802ab3149c3629a9f2221",
-                "sha256:9342dd70a1e151684727c9c91ea003b2fb33523bf19385d4554f7897ca0141d4",
-                "sha256:9361de40701666b034c59ad9e317bae95c973b9ff92513dd0eced11c6adf2e21",
-                "sha256:9669179786254a2e7e57f0ecf224e978471491d660aaca833f845b72a2df3709",
-                "sha256:aac1ba0a253e17889550ddb1b60a2063f7474155465577caa2a3b131224cfd54",
-                "sha256:aef72eae10b5e3116bac6957de1df4d75909fc76d1499a53fb6387434b6bcd8d",
-                "sha256:bd3166bb3b111e76a4f8e2980fa1addf2920a4ca9b2b8ca36a3bc3dedc618270",
-                "sha256:c1b78fb9700fc961f53386ad2fd86d87091e06ede5d118b8a50dea285a071c24",
-                "sha256:c3888a051226e676e383de03bf49eb633cd39fc829516e5334e69b8d81aae751",
-                "sha256:c5f17ad25d2c1286436761b462e22b5020d83316f8e8fcb5deb2b3151f8f1d3a",
-                "sha256:c851b35fc078389bc16b915a0a7c1d5923e12e2c5aeec58c52f4aa8085ac8237",
-                "sha256:cb7df71de0af56000115eafd000b867d1261f786b5eebd88a0ca6360cccfaca7",
-                "sha256:cedb2f9e1f990918ea061f28a0f0077a07702e3819602d3507e2ff98c8d20636",
-                "sha256:e8caf961e1b1a945db76f1b5fa9c91498d15f545ac0ababbe575cfab185d3bd8"
+                "sha256:03ed2a641e412e42cc35c244508cf186015c217f0e4d496bf6d7078ebe837ae7",
+                "sha256:04b14e45d6a8e159c9767ae57ecb34563ad93440fc1b26516a89ceb5b33c1ad5",
+                "sha256:0cdde51bfcf6b6bd862ee9be324521ec619b20590787d1655d005c3fb175005f",
+                "sha256:0f48fc7dc82ee14aeaedb986e175a429d24129b7eada1b7e94a864e4f0644dde",
+                "sha256:107d327071061fd4f4a2587d14c389a27e4e5c93c7cba5f1f59987181903902f",
+                "sha256:1375bb8b88cb050a2d4e0da901001347a44302aeadb8ceb4b6e5aa373b8ea68f",
+                "sha256:14a9f1887591684fb59fdba8feef7123a0da2424b0652e1b58dd5b9a7bb1188c",
+                "sha256:16baa799ec09cc0dcb43a10680573269d407c159325972dd7114ee7649e56c66",
+                "sha256:1b811662ecf72eb2d08872731636aee6559cae21862c36f74703be727b45df90",
+                "sha256:1ccae21a076d3d5f471700f6d30eb486da1626c380b23c70ae32ab823e453337",
+                "sha256:2f2cf7a42d4b7654c9a67b9d091ec24374f7c58794858bff632a2039cb15984d",
+                "sha256:322549b880b2d746a7672bf6ff9ed3f895e9c9f108b714e7360292aa5c5d7cf4",
+                "sha256:32ab83016c24c5cf3db2943286b85b0a172dae08c58d0f53875235219b676409",
+                "sha256:3fe50f1cac369b02d34ad904dfe0771acc483f82a1b54c5e93632916ba847b37",
+                "sha256:4a780807e80479f281d47ee4af2eb2df3e4ccf4723484f77da0bb49d027e40a1",
+                "sha256:4a8eb7785bd23565b542b01fb39115a975fefb4a82f23d407503eee2c0106247",
+                "sha256:5bee3970617b3d74759b2d2df2f6a327d372f9732f9ccbf03fa591b5f7581e39",
+                "sha256:60a3307a84ec60578accd35d7f0c71a3a971430ed7eca6567399d2b50ef37b8c",
+                "sha256:6625e52b6f346a283c3d563d1fd8bae8956daafc64bb5bbd2b8f8a07608e3994",
+                "sha256:66a5aae8233d766a877c5ef293ec5ab9520929c2578fd2069308a98b7374ea8c",
+                "sha256:68fb816a5dd901c6aff352ce49e2a0ffadacdf9b6fae282a69e7a16a02dad5fb",
+                "sha256:6b588b5cf51dc0fd1c9e19f622457cc74b7d26fe295432e434525f1c0fae02bc",
+                "sha256:6c4d7165a4e8f41eca6b990c12ee7f44fef3932fac48ca32cecb3a1b2223c21f",
+                "sha256:6d2e262e5e8da6fa56e774fb8e2643417351427604c2b177f8e8c5f75fc928ca",
+                "sha256:6d9c88b787638a451f41f97446a1c9fd416e669b4d9717ae4615bd29de1ac135",
+                "sha256:755c56beeacac6a24c8e1074f89f34f4373abce8b662470d3aa719ae304931f3",
+                "sha256:7e40d3f8eb472c1509b12ac2a7e24158ec352fc8567b77ab02c0db053927e339",
+                "sha256:812eaf4939ef2284d29653bcfee9665f11f013724f07258928f849a2306ea9f9",
+                "sha256:84df004223fd0550d0ea7a37882e5c889f3c6d45535c639ce9802293b39cd5c9",
+                "sha256:859f0add98707b182b4867359e12bde806b82483fb12a9ae868a77880fc3b7af",
+                "sha256:87c4b38288f71acd2106f5d94f575bc2136ea2887fdb5dfe18003c881fa6b370",
+                "sha256:89fc12c6371bf963809abc46cced4a01ca4f99cba17be5e7d416ed7ef1245d19",
+                "sha256:9564ac7eb1652c3701ac691ca72934dd3009997c81266807aef924012df2f4b3",
+                "sha256:9754a5c265f991317de2bac0c70a746efc2b695cf4d49f5d2cddeac36544fb44",
+                "sha256:a565f48c4aae72d1d3d3f8e8fb7218f5609c964e9c6f68604608e5958b9c60c3",
+                "sha256:a636160680c6e526b84f85d304e2f0bb4e94f8284dd765a1911de9a40450b10a",
+                "sha256:a839e25f07e428a87d17d857d9935dd743130e77ff46524abb992b962eb2076c",
+                "sha256:b62046592b44263fa7570f1117d372ae3f310222af1fc1407416f037fb3af21b",
+                "sha256:b7f7421841f8db443855d2854e25914a79a1ff48ae92f70d0a5c2f8907ab98c9",
+                "sha256:ba7ca81b6d60a9f7a0b4b4e175dcc38e8fef4992673d9d6e6879fd6de00dd9b8",
+                "sha256:bb32ca14b4d04e172c541c69eec5f385f9a075b38fb22d765d8b0ce3af3a0c22",
+                "sha256:c0ff1c1b4d13e2240821ef23c1efb1f009207cb3f56e16986f713c2b0e7cd37f",
+                "sha256:c669b440ce46ae3abe9b2d44a913b5fd86bb19eb14a8701e88e3918902ecd345",
+                "sha256:c67734cff78383a1f23ceba3b3239c7deefc62ac2b05fa6a47bcd565771e5880",
+                "sha256:c6809ebcbf6c1049002b9ac09c127ae43929042ec1f1dbd8bb1615f7cd9f70a0",
+                "sha256:cd601187476c6bed26a0398353212684c427e10a903aeafa6da40c63309d438b",
+                "sha256:ebfa374067af240d079ef97b8064478f3bf71038b78b017eb6ec93ede1b6bcec",
+                "sha256:fbb17c0d0822684b7d6c09915677a32319f16ff1115df5ec05bdcaaee40b35f3",
+                "sha256:fff1f3a586246110f34dc762098b5afd2de88de507559e63553d7da643053786"
             ],
             "index": "pypi",
-            "version": "==5.3"
+            "version": "==5.4"
         },
         "coveralls": {
             "hashes": [
-                "sha256:4430b862baabb3cf090d36d84d331966615e4288d8a8c5957e0fd456d0dd8bd6",
-                "sha256:b3b60c17b03a0dee61952a91aed6f131e0b2ac8bd5da909389c53137811409e1"
+                "sha256:5399c0565ab822a70a477f7031f6c88a9dd196b3de2877b3facb43b51bd13434",
+                "sha256:f8384968c57dee4b7133ae701ecdad88e85e30597d496dcba0d7fbb470dca41f"
             ],
             "index": "pypi",
-            "version": "==2.1.2"
+            "version": "==3.0.0"
         },
         "cryptography": {
             "hashes": [
-                "sha256:21b47c59fcb1c36f1113f3709d37935368e34815ea1d7073862e92f810dc7499",
-                "sha256:451cdf60be4dafb6a3b78802006a020e6cd709c22d240f94f7a0696240a17154",
-                "sha256:4549b137d8cbe3c2eadfa56c0c858b78acbeff956bd461e40000b2164d9167c6",
-                "sha256:48ee615a779ffa749d7d50c291761dc921d93d7cf203dca2db663b4f193f0e49",
-                "sha256:559d622aef2a2dff98a892eef321433ba5bc55b2485220a8ca289c1ecc2bd54f",
-                "sha256:5d52c72449bb02dd45a773a203196e6d4fae34e158769c896012401f33064396",
-                "sha256:65beb15e7f9c16e15934569d29fb4def74ea1469d8781f6b3507ab896d6d8719",
-                "sha256:680da076cad81cdf5ffcac50c477b6790be81768d30f9da9e01960c4b18a66db",
-                "sha256:762bc5a0df03c51ee3f09c621e1cee64e3a079a2b5020de82f1613873d79ee70",
-                "sha256:89aceb31cd5f9fc2449fe8cf3810797ca52b65f1489002d58fe190bfb265c536",
-                "sha256:983c0c3de4cb9fcba68fd3f45ed846eb86a2a8b8d8bc5bb18364c4d00b3c61fe",
-                "sha256:99d4984aabd4c7182050bca76176ce2dbc9fa9748afe583a7865c12954d714ba",
-                "sha256:9d9fc6a16357965d282dd4ab6531013935425d0dc4950df2e0cf2a1b1ac1017d",
-                "sha256:a7597ffc67987b37b12e09c029bd1dc43965f75d328076ae85721b84046e9ca7",
-                "sha256:ab010e461bb6b444eaf7f8c813bb716be2d78ab786103f9608ffd37a4bd7d490",
-                "sha256:b12e715c10a13ca1bd27fbceed9adc8c5ff640f8e1f7ea76416352de703523c8",
-                "sha256:b2bded09c578d19e08bd2c5bb8fed7f103e089752c9cf7ca7ca7de522326e921",
-                "sha256:b372026ebf32fe2523159f27d9f0e9f485092e43b00a5adacf732192a70ba118",
-                "sha256:cb179acdd4ae1e4a5a160d80b87841b3d0e0be84af46c7bb2cd7ece57a39c4ba",
-                "sha256:e97a3b627e3cb63c415a16245d6cef2139cca18bb1183d1b9375a1c14e83f3b3",
-                "sha256:f0e099fc4cc697450c3dd4031791559692dd941a95254cb9aeded66a7aa8b9bc",
-                "sha256:f99317a0fa2e49917689b8cf977510addcfaaab769b3f899b9c481bbd76730c2"
+                "sha256:066bc53f052dfeda2f2d7c195cf16fb3e5ff13e1b6b7415b468514b40b381a5b",
+                "sha256:0923ba600d00718d63a3976f23cab19aef10c1765038945628cd9be047ad0336",
+                "sha256:2d32223e5b0ee02943f32b19245b61a62db83a882f0e76cc564e1cec60d48f87",
+                "sha256:4169a27b818de4a1860720108b55a2801f32b6ae79e7f99c00d79f2a2822eeb7",
+                "sha256:57ad77d32917bc55299b16d3b996ffa42a1c73c6cfa829b14043c561288d2799",
+                "sha256:5ecf2bcb34d17415e89b546dbb44e73080f747e504273e4d4987630493cded1b",
+                "sha256:600cf9bfe75e96d965509a4c0b2b183f74a4fa6f5331dcb40fb7b77b7c2484df",
+                "sha256:66b57a9ca4b3221d51b237094b0303843b914b7d5afd4349970bb26518e350b0",
+                "sha256:93cfe5b7ff006de13e1e89830810ecbd014791b042cbe5eec253be11ac2b28f3",
+                "sha256:9e98b452132963678e3ac6c73f7010fe53adf72209a32854d55690acac3f6724",
+                "sha256:df186fcbf86dc1ce56305becb8434e4b6b7504bc724b71ad7a3239e0c9d14ef2",
+                "sha256:fec7fb46b10da10d9e1d078d1ff8ed9e05ae14f431fdbd11145edd0550b9a964"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
-            "version": "==3.1.1"
+            "markers": "python_version >= '3.6'",
+            "version": "==3.4.6"
         },
         "distlib": {
             "hashes": [
@@ -304,34 +314,34 @@
         },
         "iniconfig": {
             "hashes": [
-                "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437",
-                "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"
+                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
+                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
             ],
-            "version": "==1.0.1"
+            "version": "==1.1.1"
         },
         "jeepney": {
             "hashes": [
-                "sha256:3479b861cc2b6407de5188695fa1a8d57e5072d7059322469b62628869b8e36e",
-                "sha256:d6c6b49683446d2407d2fe3acb7a368a77ff063f9182fe427da15d622adc24cf"
+                "sha256:7d59b6622675ca9e993a6bd38de845051d315f8b0c72cca3aef733a20b648657",
+                "sha256:aec56c0eb1691a841795111e184e13cad504f7703b9a64f63020816afa79a8ae"
             ],
             "markers": "sys_platform == 'linux'",
-            "version": "==0.4.3"
+            "version": "==0.6.0"
         },
         "jinja2": {
             "hashes": [
-                "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
-                "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
+                "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419",
+                "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"
             ],
             "index": "pypi",
-            "version": "==2.11.2"
+            "version": "==2.11.3"
         },
         "keyring": {
             "hashes": [
-                "sha256:4e34ea2fdec90c1c43d6610b5a5fafa1b9097db1802948e90caf5763974b8f8d",
-                "sha256:9aeadd006a852b78f4b4ef7c7556c2774d2432bbef8ee538a3e9089ac8b11466"
+                "sha256:9acb3e1452edbb7544822b12fd25459078769e560fa51f418b6d00afaa6178df",
+                "sha256:9f44660a5d4931bdc14c08a1d01ef30b18a7a8147380710d8c9f9531e1f6c3c0"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==21.4.0"
+            "version": "==22.0.1"
         },
         "markupsafe": {
             "hashes": [
@@ -340,8 +350,12 @@
                 "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
                 "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
                 "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
+                "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
+                "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
                 "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
                 "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
+                "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
+                "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
                 "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
                 "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
                 "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
@@ -350,42 +364,57 @@
                 "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
                 "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
                 "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
+                "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
+                "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
                 "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
                 "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
                 "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
+                "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
+                "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
                 "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
                 "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
+                "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
                 "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
                 "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
                 "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
+                "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
+                "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
                 "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
                 "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
                 "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
+                "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
                 "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
+                "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
                 "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
+                "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
                 "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
+                "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
                 "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
                 "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
+                "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
+                "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
+                "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
                 "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
-                "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
+                "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
+                "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.1.1"
         },
         "packaging": {
             "hashes": [
-                "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
-                "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
+                "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
+                "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==20.4"
+            "version": "==20.9"
         },
         "pkginfo": {
             "hashes": [
-                "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb",
-                "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"
+                "sha256:029a70cb45c6171c329dfc890cde0879f8c52d6f3922794796e06f577bb03db4",
+                "sha256:9fdbea6495622e022cc72c2e5e1b735218e4ffb2a2a69cde2694a6c1f16afb75"
             ],
-            "version": "==1.5.0.1"
+            "version": "==1.7.0"
         },
         "pluggy": {
             "hashes": [
@@ -397,19 +426,19 @@
         },
         "pproxy": {
             "hashes": [
-                "sha256:c6367a5abcae46b4385439772ebfb2a72557fd2675c1c117d0b3038b79e41472",
-                "sha256:f9dea5f1192adf166e7857320f328349f3f158cf6511ccbac0c7c8c09d864ef5"
+                "sha256:e013818b1325603a08769a1c1e36e1ce79482b35069cf64fcdc1c11d34f52fdb",
+                "sha256:f59e18328f18c8edf601a2b09f857be81f525d043c935e3d5dfa3b31d7f48f1b"
             ],
             "index": "pypi",
-            "version": "==2.3.7"
+            "version": "==2.7.4"
         },
         "py": {
             "hashes": [
-                "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2",
-                "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"
+                "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
+                "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.9.0"
+            "version": "==1.10.0"
         },
         "pycparser": {
             "hashes": [
@@ -421,11 +450,11 @@
         },
         "pygments": {
             "hashes": [
-                "sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998",
-                "sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7"
+                "sha256:37a13ba168a02ac54cc5891a42b1caec333e59b66addb7fa633ea8a6d73445c0",
+                "sha256:b21b072d0ccdf29297a82a2363359d99623597b8a265b8081760e4d0f7153c88"
             ],
             "markers": "python_version >= '3.5'",
-            "version": "==2.7.1"
+            "version": "==2.8.0"
         },
         "pyparsing": {
             "hashes": [
@@ -437,44 +466,44 @@
         },
         "pytest": {
             "hashes": [
-                "sha256:1cd09785c0a50f9af72220dd12aa78cfa49cbffc356c61eab009ca189e018a33",
-                "sha256:d010e24666435b39a4cf48740b039885642b6c273a3f77be3e7e03554d2806b7"
+                "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
+                "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
             ],
             "index": "pypi",
-            "version": "==6.1.0"
+            "version": "==6.2.2"
         },
         "pytz": {
             "hashes": [
-                "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
-                "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
+                "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
+                "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
             ],
-            "version": "==2020.1"
+            "version": "==2021.1"
         },
         "readme-renderer": {
             "hashes": [
-                "sha256:cbe9db71defedd2428a1589cdc545f9bd98e59297449f69d721ef8f1cfced68d",
-                "sha256:cc4957a803106e820d05d14f71033092537a22daa4f406dfbdd61177e0936376"
+                "sha256:63b4075c6698fcfa78e584930f07f39e05d46f3ec97f65006e430b595ca6348c",
+                "sha256:92fd5ac2bf8677f310f3303aa4bce5b9d5f9f2094ab98c29f13791d7b805a3db"
             ],
-            "version": "==26.0"
+            "version": "==29.0"
         },
         "recommonmark": {
             "hashes": [
-                "sha256:29cd4faeb6c5268c633634f2d69aef9431e0f4d347f90659fd0aab20e541efeb",
-                "sha256:2ec4207a574289355d5b6ae4ae4abb29043346ca12cdd5f07d374dc5987d2852"
+                "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f",
+                "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"
             ],
             "index": "pypi",
-            "version": "==0.6.0"
+            "version": "==0.7.1"
         },
         "requests": {
             "extras": [
                 "socks"
             ],
             "hashes": [
-                "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
-                "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
+                "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
+                "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
             ],
             "index": "pypi",
-            "version": "==2.24.0"
+            "version": "==2.25.1"
         },
         "requests-toolbelt": {
             "hashes": [
@@ -492,11 +521,11 @@
         },
         "secretstorage": {
             "hashes": [
-                "sha256:15da8a989b65498e29be338b3b279965f1b8f09b9668bd8010da183024c8bff6",
-                "sha256:b5ec909dde94d4ae2fa26af7c089036997030f0cf0a5cb372b4cccabd81c143b"
+                "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f",
+                "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"
             ],
             "markers": "sys_platform == 'linux'",
-            "version": "==3.1.2"
+            "version": "==3.3.1"
         },
         "six": {
             "hashes": [
@@ -508,18 +537,18 @@
         },
         "snowballstemmer": {
             "hashes": [
-                "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0",
-                "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"
+                "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
+                "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
             ],
-            "version": "==2.0.0"
+            "version": "==2.1.0"
         },
         "sphinx": {
             "hashes": [
-                "sha256:321d6d9b16fa381a5306e5a0b76cd48ffbc588e6340059a729c6fdd66087e0e8",
-                "sha256:ce6fd7ff5b215af39e2fcd44d4a321f6694b4530b6f2b2109b64d120773faea0"
+                "sha256:11d521e787d9372c289472513d807277caafb1684b33eb4f08f7574c405893a9",
+                "sha256:e90161222e4d80ce5fc811ace7c6787a226b4f5951545f7f42acf97277bfc35c"
             ],
             "index": "pypi",
-            "version": "==3.2.1"
+            "version": "==3.5.1"
         },
         "sphinx-readable-theme": {
             "hashes": [
@@ -578,18 +607,19 @@
         },
         "toml": {
             "hashes": [
-                "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
-                "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
+                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
+                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
             ],
-            "version": "==0.10.1"
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==0.10.2"
         },
         "tox": {
             "hashes": [
-                "sha256:e6318f404aff16522ff5211c88cab82b39af121735a443674e4e2e65f4e4637b",
-                "sha256:eb629ddc60e8542fd4a1956b2462e3b8771d49f1ff630cecceacaa0fbfb7605a"
+                "sha256:89afa9c59c04beb55eda789c7a65feb1a70fde117f85f1bd1c27c66758456e60",
+                "sha256:ed1e650cf6368bcbc4a071eeeba363c480920e0ed8a9ad1793c7caaa5ad33d49"
             ],
             "index": "pypi",
-            "version": "==3.20.0"
+            "version": "==3.22.0"
         },
         "tox-travis": {
             "hashes": [
@@ -601,35 +631,35 @@
         },
         "tqdm": {
             "hashes": [
-                "sha256:2dd75fdb764f673b8187643496fcfbeac38348015b665878e582b152f3391cdb",
-                "sha256:93b7a6a9129fce904f6df4cf3ae7ff431d779be681a95c3344c26f3e6c09abfa"
+                "sha256:65185676e9fdf20d154cffd1c5de8e39ef9696ff7e59fe0156b1b08e468736af",
+                "sha256:70657337ec104eb4f3fb229285358f23f045433f6aea26846cdd55f0fd68945c"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==4.50.0"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==4.57.0"
         },
         "twine": {
             "hashes": [
-                "sha256:34352fd52ec3b9d29837e6072d5a2a7c6fe4290e97bba46bb8d478b5c598f7ab",
-                "sha256:ba9ff477b8d6de0c89dd450e70b2185da190514e91c42cc62f96850025c10472"
+                "sha256:2f6942ec2a17417e19d2dd372fc4faa424c87ee9ce49b4e20c427eb00a0f3f41",
+                "sha256:fcffa8fc37e8083a5be0728371f299598870ee1eccc94e9a25cef7b1dcfa8297"
             ],
             "index": "pypi",
-            "version": "==3.2.0"
+            "version": "==3.3.0"
         },
         "urllib3": {
             "hashes": [
-                "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a",
-                "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"
+                "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
+                "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
             ],
             "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
-            "version": "==1.25.10"
+            "version": "==1.26.3"
         },
         "virtualenv": {
             "hashes": [
-                "sha256:43add625c53c596d38f971a465553f6318decc39d98512bc100fa1b1e839c8dc",
-                "sha256:e0305af10299a7fb0d69393d8f04cb2965dda9351140d11ac8db4e5e3970451b"
+                "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d",
+                "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3"
             ],
             "index": "pypi",
-            "version": "==20.0.31"
+            "version": "==20.4.2"
         },
         "webencodings": {
             "hashes": [
diff --git a/README.md b/README.md
index 43b40645..ba7166ff 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,11 @@ With PyOWM you can integrate into your code any of the following OpenWeatherMap
  - **Stations API v3.0**, allowing to create and manage meteostation and publish local weather measurements
  - **Weather Alerts API v3.0**, allowing to set triggers on weather conditions and areas and poll for spawned alerts
  - **Image tiles** for several map layers provided by OWM
+ - **Geocoding API v1.0** allowing to perform direct/reverse geocoding 
+
+
+## In case of trouble...
+Please **read the [FAQ](https://pyowm.readthedocs.io/en/latest/v3/faq.html)** before filing a new issue on GitHub! There are many common issues, therefore a fix for your issue might come easier than you think
 
  ##  Get started
 
@@ -107,11 +112,6 @@ The library software API documentation is available on [Read the Docs](https://p
 The [Code recipes](https://pyowm.readthedocs.io/en/latest/v3/code-recipes.html) section comes in handy!
 
 
-## Help - PyOWM is giving me errors!
-Please read the [FAQ](https://pyowm.readthedocs.io/en/latest/v3/faq.html) section of the documentation before filing a new issue on GitHub!
-
-There are many common issues, therefore a fix for your issue might come easier than you think
-
 ## Community & Contributing
 
 Here are [some cool projects](https://github.com/csparpa/pyowm/wiki/Community-Projects-using-PyOWM) that use PyOWM
diff --git a/pyowm/agroapi10/agro_manager.py b/pyowm/agroapi10/agro_manager.py
index 1d9c9870..9b1e350a 100644
--- a/pyowm/agroapi10/agro_manager.py
+++ b/pyowm/agroapi10/agro_manager.py
@@ -51,12 +51,14 @@ def create_polygon(self, geopolygon, name=None):
         """
         assert geopolygon is not None
         assert isinstance(geopolygon, GeoPolygon)
-        data = dict()
-        data['geo_json'] = {
-            "type": "Feature",
-            "properties": {},
-            "geometry": geopolygon.to_dict()
+        data = {
+            'geo_json': {
+                "type": "Feature",
+                "properties": {},
+                "geometry": geopolygon.to_dict(),
+            }
         }
+
         if name is not None:
             data['name'] = name
         status, payload = self.http_client.post(
@@ -144,12 +146,14 @@ def soil_data(self, polygon):
             params={'appid': self.API_key,
                     'polyid': polyd},
             headers={'Content-Type': 'application/json'})
-        the_dict = dict()
-        the_dict['reference_time'] = data['dt']
-        the_dict['surface_temp'] = data['t0']
-        the_dict['ten_cm_temp'] = data['t10']
-        the_dict['moisture'] = data['moisture']
-        the_dict['polygon_id'] = polyd
+        the_dict = {
+            'reference_time': data['dt'],
+            'surface_temp': data['t0'],
+            'ten_cm_temp': data['t10'],
+            'moisture': data['moisture'],
+            'polygon_id': polyd,
+        }
+
         return Soil.from_dict(the_dict)
 
     # Satellite Imagery subset methods
@@ -271,7 +275,7 @@ def download_satellite_image(self, metaimage, x=None, y=None, zoom=None, palette
             params = dict(paletteid=palette)
         else:
             palette = PaletteEnum.GREEN
-            params = dict()
+            params = {}
         # polygon PNG
         if isinstance(metaimage, MetaPNGImage):
             prepared_url = metaimage.url
@@ -309,7 +313,7 @@ def stats_for_satellite_image(self, metaimage):
         :type metaimage: a `pyowm.agroapi10.imagery.MetaImage` subtype
         :return: dict
         """
-        if metaimage.preset != PresetEnum.EVI and metaimage.preset != PresetEnum.NDVI:
+        if metaimage.preset not in [PresetEnum.EVI, PresetEnum.NDVI]:
             raise ValueError("Unsupported image preset: should be EVI or NDVI")
         if metaimage.stats_url is None:
             raise ValueError("URL for image statistics is not defined")
diff --git a/pyowm/agroapi10/imagery.py b/pyowm/agroapi10/imagery.py
index 09ed3b4f..32623c48 100644
--- a/pyowm/agroapi10/imagery.py
+++ b/pyowm/agroapi10/imagery.py
@@ -47,16 +47,16 @@ def __init__(self, url, preset, satellite_name, acquisition_time,
         assert isinstance(acquisition_time, int)
         assert acquisition_time >= 0, 'acquisition_time cannot be negative'
         self._acquisition_time = acquisition_time
-        assert isinstance(valid_data_percentage, float) or isinstance(valid_data_percentage, int)
+        assert isinstance(valid_data_percentage, (float, int))
         assert valid_data_percentage >= 0., 'valid_data_percentage cannot be negative'
         self.valid_data_percentage = valid_data_percentage
-        assert isinstance(cloud_coverage_percentage, float) or isinstance(cloud_coverage_percentage, int)
+        assert isinstance(cloud_coverage_percentage, (float, int))
         assert cloud_coverage_percentage >= 0., 'cloud_coverage_percentage cannot be negative'
         self.cloud_coverage_percentage = cloud_coverage_percentage
-        assert isinstance(sun_azimuth, float) or isinstance(sun_azimuth, int)
+        assert isinstance(sun_azimuth, (float, int))
         assert sun_azimuth >= 0. and sun_azimuth <= 360., 'sun_azimuth must be between 0 and 360 degrees'
         self.sun_azimuth  = sun_azimuth
-        assert isinstance(sun_elevation, float) or isinstance(sun_elevation, int)
+        assert isinstance(sun_elevation, (float, int))
         assert sun_elevation >= 0. and sun_elevation <= 90., 'sun_elevation must be between 0 and 90 degrees'
         self.sun_elevation = sun_elevation
         self.polygon_id = polygon_id
@@ -122,7 +122,7 @@ class SatelliteImage:
     def __init__(self, metadata, data, downloaded_on=None, palette=None):
         assert isinstance(metadata, MetaImage)
         self.metadata = metadata
-        assert isinstance(data, Image) or isinstance(data, Tile)
+        assert isinstance(data, (Image, Tile))
         self.data = data
         if downloaded_on is not None:
             assert isinstance(downloaded_on, int)
diff --git a/pyowm/agroapi10/polygon.py b/pyowm/agroapi10/polygon.py
index 4b2fff27..c4bfb686 100644
--- a/pyowm/agroapi10/polygon.py
+++ b/pyowm/agroapi10/polygon.py
@@ -35,7 +35,7 @@ def __init__(self, id, name=None, geopolygon=None, center=None, area=None, user_
         if center is not None:
             assert isinstance(center, GeoPoint), 'Polygon center must be a valid geopoint type'
         if area is not None:
-            assert isinstance(area, float) or isinstance(area, int), 'Area must be a numeric type'
+            assert isinstance(area, (float, int)), 'Area must be a numeric type'
             assert area >= 0, 'Area must not be negative'
         self.id = id
         self.name = name
diff --git a/pyowm/agroapi10/search.py b/pyowm/agroapi10/search.py
index b117156a..6d809542 100644
--- a/pyowm/agroapi10/search.py
+++ b/pyowm/agroapi10/search.py
@@ -37,7 +37,7 @@ def __init__(self, polygon_id, list_of_dict, query_timestamp):
         self.query_timestamp = query_timestamp
 
         # parse raw data
-        result = list()
+        result = []
         for the_dict in list_of_dict:
             # common metadata
             acquisition_time = the_dict.get('dt', None)
@@ -180,7 +180,7 @@ def with_img_type(self, image_type):
 
     def with_preset(self, preset):
         """
-        Returns the seach results having the specified preset
+        Returns the search results having the specified preset
 
         :param preset: the desired image preset (valid values are provided by the
             `pyowm.agroapi10.enums.PresetEnum` enum)
diff --git a/pyowm/agroapi10/soil.py b/pyowm/agroapi10/soil.py
index dc6971fd..3fd55a56 100644
--- a/pyowm/agroapi10/soil.py
+++ b/pyowm/agroapi10/soil.py
@@ -30,13 +30,13 @@ def __init__(self, reference_time, surface_temp, ten_cm_temp, moisture, polygon_
             raise ValueError("reference_time must be greater than 0")
         self._reference_time = reference_time
         assert surface_temp is not None
-        assert isinstance(surface_temp, float) or isinstance(surface_temp, int), 'surface_temp must be a number'
+        assert isinstance(surface_temp, (float, int)), 'surface_temp must be a number'
         self._surface_temp = surface_temp
         assert ten_cm_temp is not None
-        assert isinstance(ten_cm_temp, float) or isinstance(ten_cm_temp, int), 'ten_cm_temp must be a number'
+        assert isinstance(ten_cm_temp, (float, int)), 'ten_cm_temp must be a number'
         self._ten_cm_temp = ten_cm_temp
         assert moisture is not None
-        assert isinstance(moisture, float) or isinstance(moisture, int), 'moisture must be a number'
+        assert isinstance(moisture, (float, int)), 'moisture must be a number'
         if moisture < 0.:
             raise ValueError("moisture must be greater than 0")
         self.moisture = moisture
diff --git a/pyowm/airpollutionapi30/airpollution_client.py b/pyowm/airpollutionapi30/airpollution_client.py
index 3238fe2a..a92d1855 100644
--- a/pyowm/airpollutionapi30/airpollution_client.py
+++ b/pyowm/airpollutionapi30/airpollution_client.py
@@ -1,7 +1,8 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-from pyowm.airpollutionapi30.uris import CO_INDEX_URL, OZONE_URL, NO2_INDEX_URL, SO2_INDEX_URL
+from pyowm.airpollutionapi30.uris import CO_INDEX_URL, OZONE_URL, NO2_INDEX_URL, SO2_INDEX_URL, AIR_POLLUTION_URL, \
+    AIR_POLLUTION_FORECAST_URL, AIR_POLLUTION_HISTORY_URL
 from pyowm.utils import formatting
 
 
@@ -152,6 +153,42 @@ def get_so2(self, params_dict):
         _, json_data = self._client.get_json(uri)
         return json_data
 
+    def get_air_pollution(self, params_dict):
+        """
+        Invokes the new AirPollution API endpoint
+
+        :param params_dict: dict of parameters
+        :returns: a string containing raw JSON data
+        :raises: *ValueError*, *APIRequestError*
+
+        """
+        _, json_data = self._client.get_json(AIR_POLLUTION_URL, params=params_dict)
+        return json_data
+
+    def get_forecast_air_pollution(self, params_dict):
+        """
+        Invokes the new AirPollution API forecast endpoint
+
+        :param params_dict: dict of parameters
+        :returns: a string containing raw JSON data
+        :raises: *ValueError*, *APIRequestError*
+
+        """
+        _, json_data = self._client.get_json(AIR_POLLUTION_FORECAST_URL, params=params_dict)
+        return json_data
+
+    def get_historical_air_pollution(self, params_dict):
+        """
+        Invokes the new AirPollution API history endpoint
+
+        :param params_dict: dict of parameters
+        :returns: a string containing raw JSON data
+        :raises: *ValueError*, *APIRequestError*
+
+        """
+        _, json_data = self._client.get_json(AIR_POLLUTION_HISTORY_URL, params=params_dict)
+        return json_data
+
     def __repr__(self):
         return "<%s.%s - httpclient=%s>" % \
-               (__name__, self.__class__.__name__, str(self._client))
+               (__name__, self.__class__.__name__, str(self._client))
\ No newline at end of file
diff --git a/pyowm/airpollutionapi30/airpollution_manager.py b/pyowm/airpollutionapi30/airpollution_manager.py
index 1a9076b7..c859b17a 100644
--- a/pyowm/airpollutionapi30/airpollution_manager.py
+++ b/pyowm/airpollutionapi30/airpollution_manager.py
@@ -1,11 +1,11 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-from pyowm.airpollutionapi30 import airpollution_client, coindex, no2index, ozone, so2index
-from pyowm.airpollutionapi30.uris import ROOT_POLLUTION_API_URL
+from pyowm.airpollutionapi30 import airpollution_client, coindex, no2index, ozone, so2index, airstatus
+from pyowm.airpollutionapi30.uris import ROOT_POLLUTION_API_URL, NEW_ROOT_POLLUTION_API_URL
 from pyowm.commons.http_client import HttpClient
 from pyowm.constants import AIRPOLLUTION_API_VERSION
-from pyowm.utils import geo
+from pyowm.utils import geo, decorators, formatting, timestamps
 
 
 class AirPollutionManager:
@@ -29,10 +29,14 @@ def __init__(self, API_key, config):
         self.ap_client = airpollution_client.AirPollutionHttpClient(
             API_key,
             HttpClient(API_key, config, ROOT_POLLUTION_API_URL))
+        self.new_ap_client = airpollution_client.AirPollutionHttpClient(
+            API_key,
+            HttpClient(API_key, config, NEW_ROOT_POLLUTION_API_URL))
 
     def airpollution_api_version(self):
         return AIRPOLLUTION_API_VERSION
 
+    @decorators.deprecated('removed', '4', 'coindex_around_coords')
     def coindex_around_coords(self, lat, lon, start=None, interval=None):
         """
         Queries the OWM AirPollution API for Carbon Monoxide values sampled in the
@@ -72,6 +76,7 @@ def coindex_around_coords(self, lat, lon, start=None, interval=None):
         coi.interval = interval
         return coi
 
+    @decorators.deprecated('removed', '4', 'ozone_around_coords')
     def ozone_around_coords(self, lat, lon, start=None, interval=None):
         """
         Queries the OWM AirPollution API for Ozone (O3) value in Dobson Units sampled in
@@ -110,6 +115,7 @@ def ozone_around_coords(self, lat, lon, start=None, interval=None):
         oz.interval = interval
         return oz
 
+    @decorators.deprecated('removed', '4', 'no2index_around_coords')
     def no2index_around_coords(self, lat, lon, start=None, interval=None):
         """
         Queries the OWM AirPollution API for Nitrogen Dioxide values sampled in the
@@ -149,6 +155,7 @@ def no2index_around_coords(self, lat, lon, start=None, interval=None):
         no2.interval = interval
         return no2
 
+    @decorators.deprecated('removed', '4', 'so2index_around_coords')
     def so2index_around_coords(self, lat, lon, start=None, interval=None):
         """
         Queries the OWM AirPollution API for Sulphur Dioxide values sampled in the
@@ -188,5 +195,86 @@ def so2index_around_coords(self, lat, lon, start=None, interval=None):
         so2.interval = interval
         return so2
 
+    def air_quality_at_coords(self, lat, lon):
+        """
+        Queries the OWM AirPollution API for available air quality indicators around the specified coordinates.
+
+        :param lat: the location's latitude, must be between -90.0 and 90.0
+        :type lat: int/float
+        :param lon: the location's longitude, must be between -180.0 and 180.0
+        :type lon: int/float
+        :return: a *AirStatus* instance or ``None`` if data is not available
+        :raises: *ParseResponseException* when OWM AirPollution API responses' data
+            cannot be parsed, *APICallException* when OWM AirPollution API can not be
+            reached, *ValueError* for wrong input values
+        """
+        geo.assert_is_lon(lon)
+        geo.assert_is_lat(lat)
+        params = {'lon': lon, 'lat': lat}
+        json_data = self.new_ap_client.get_air_pollution(params)
+        try:
+            return airstatus.AirStatus.from_dict(json_data)
+        except:
+            return None
+
+    def air_quality_forecast_at_coords(self, lat, lon):
+        """
+        Queries the OWM AirPollution API for available forecasted air quality indicators around the specified coordinates.
+
+        :param lat: the location's latitude, must be between -90.0 and 90.0
+        :type lat: int/float
+        :param lon: the location's longitude, must be between -180.0 and 180.0
+        :type lon: int/float
+        :return: a `list` of *AirStatus* instances or an empty `list` if data is not available
+        :raises: *ParseResponseException* when OWM AirPollution API responses' data
+            cannot be parsed, *APICallException* when OWM AirPollution API can not be
+            reached, *ValueError* for wrong input values
+        """
+        geo.assert_is_lon(lon)
+        geo.assert_is_lat(lat)
+        params = {'lon': lon, 'lat': lat}
+        json_data = self.new_ap_client.get_forecast_air_pollution(params)
+        try:
+            return airstatus.AirStatus.from_dict(json_data)
+        except:
+            return []
+
+    def air_quality_history_at_coords(self, lat, lon, start, end=None):
+        """
+        Queries the OWM AirPollution API for available forecasted air quality indicators around the specified coordinates.
+
+        :param lat: the location's latitude, must be between -90.0 and 90.0
+        :type lat: int/float
+        :param lon: the location's longitude, must be between -180.0 and 180.0
+        :type lon: int/float
+        :param start: the object conveying the start value of the search time window
+        :type start: int, ``datetime.datetime`` or ISO8601-formatted string
+        :param end: the object conveying the end value of the search time window. Values in the future will be clipped
+           to the current timestamp. Defaults to the current UNIX timestamp.
+        :type end: int, ``datetime.datetime`` or ISO8601-formatted string
+        :return: a `list` of *AirStatus* instances or an empty `list` if data is not available
+        :raises: *ParseResponseException* when OWM AirPollution API responses' data
+            cannot be parsed, *APICallException* when OWM AirPollution API can not be
+            reached, *ValueError* for wrong input values
+        """
+        geo.assert_is_lon(lon)
+        geo.assert_is_lat(lat)
+        now = timestamps.now(timeformat='unix')
+        assert start is not None
+        start = formatting.timeformat(start, 'unix')
+        if end is None:
+            end = now
+        else:
+            end = formatting.timeformat(end, 'unix')
+            if end > now:
+                end = now
+
+        params = {'lon': lon, 'lat': lat, 'start': start, 'end': end}
+        json_data = self.new_ap_client.get_historical_air_pollution(params)
+        try:
+            return airstatus.AirStatus.from_dict(json_data)
+        except:
+            return []
+
     def __repr__(self):
         return '<%s.%s>' % (__name__, self.__class__.__name__)
diff --git a/pyowm/airpollutionapi30/airstatus.py b/pyowm/airpollutionapi30/airstatus.py
new file mode 100644
index 00000000..3602c1b7
--- /dev/null
+++ b/pyowm/airpollutionapi30/airstatus.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from pyowm.commons import exceptions
+from pyowm.utils import formatting, timestamps
+from pyowm.weatherapi25 import location
+
+
+class AirStatus:
+    """
+    A class representing a dataset about air quality
+
+    :param reference_time: GMT UNIXtime telling when the data has been measured
+    :type reference_time: int
+    :param location: the *Location* relative to this measurement
+    :type location: *Location*
+    :param interval: the time granularity of the CO observation
+    :type interval: str
+    :param air_quality_data: the dataset
+    :type air_quality_data: dict
+    :param reception_time: GMT UNIXtime telling when the CO observation has
+        been received from the OWM Weather API
+    :type reception_time: int
+    :returns: an *COIndex* instance
+    :raises: *ValueError* when negative values are provided as reception time,
+      CO samples are not provided in a list
+
+    """
+
+    def __init__(self, reference_time, location, air_quality_data, reception_time):
+        if reference_time < 0:
+            raise ValueError("'reference_time' must be greater than 0")
+        self.ref_time = reference_time
+        self.location = location
+        if not isinstance(air_quality_data, dict):
+            raise ValueError("'air_quality_data' must be a list")
+        self.air_quality_data = air_quality_data
+        for key, val in air_quality_data.items():
+            setattr(self, key, val)
+        if reception_time < 0:
+            raise ValueError("'reception_time' must be greater than 0")
+        self.rec_time = reception_time
+
+    def reference_time(self, timeformat='unix'):
+        """
+        Returns the GMT time telling when the air quality data have been measured
+
+        :param timeformat: the format for the time value. May be:
+            '*unix*' (default) for UNIX time
+            '*iso*' for ISO8601-formatted string in the format ``YYYY-MM-DD HH:MM:SS+00:00``
+            '*date* for ``datetime.datetime`` object instance
+        :type timeformat: str
+        :returns: an int or a str
+        :raises: ValueError when negative values are provided
+
+        """
+        return formatting.timeformat(self.ref_time, timeformat)
+
+    def reception_time(self, timeformat='unix'):
+        """
+        Returns the GMT time telling when the air quality data has been received
+        from the OWM Weather API
+
+        :param timeformat: the format for the time value. May be:
+            '*unix*' (default) for UNIX time
+            '*iso*' for ISO8601-formatted string in the format ``YYYY-MM-DD HH:MM:SS+00:00``
+            '*date* for ``datetime.datetime`` object instance
+        :type timeformat: str
+        :returns: an int or a str
+        :raises: ValueError when negative values are provided
+
+        """
+        return formatting.timeformat(self.rec_time, timeformat)
+
+
+    @classmethod
+    def from_dict(cls, the_dict):
+        """
+        Parses an *AirStatus* instance or `list` of instances out of a data dictionary.
+
+        :param the_dict: the input dictionary
+        :type the_dict: `dict`
+        :returns: a *AirStatus* instance or ``list` of such instances
+        :raises: *ParseAPIResponseError* if it is impossible to find or parse the data needed to build the result
+
+        """
+        if the_dict is None:
+            raise exceptions.ParseAPIResponseError('Data is None')
+        try:
+            # -- location
+            lon = float(the_dict['coord']['lat'])
+            lat = float(the_dict['coord']['lon'])
+            place = location.Location(None, lon, lat, None)
+
+            # -- reception time (now)
+            rcp_time = timestamps.now('unix')
+
+            def build_air_status(item_dict, location, reception_time):
+                # -- reference time (strip away Z and T on ISO8601 format)
+                reference_time = item_dict['dt']
+
+                # -- air quality data
+                data = item_dict['components']
+                data['aqi'] = item_dict['main']['aqi']
+
+                return AirStatus(reference_time, location, data, reception_time)
+
+            items = the_dict['list']
+
+            # one datapoint
+            if len(items) == 1:
+                return build_air_status(items[0], place, rcp_time)
+            # multiple datapoints
+            else:
+                return [build_air_status(item, place, rcp_time) for item in items]
+
+        except KeyError:
+            raise exceptions.ParseAPIResponseError(
+                      ''.join([__name__, ': impossible to parse AirStatus']))
+
+    def to_dict(self):
+        """Dumps object to a dictionary
+
+        :returns: a `dict`
+
+        """
+        return {"reference_time": self.ref_time,
+                "location": self.location.to_dict(),
+                "air_quality_data": self.air_quality_data,
+                "reception_time": self.rec_time}
+
+    def __repr__(self):
+        return "<%s.%s - reference time=%s, reception time=%s, location=%s" % (
+                    __name__,
+                    self.__class__.__name__,
+                    self.reference_time('iso'),
+                    self.reception_time('iso'),
+                    str(self.location))
diff --git a/pyowm/airpollutionapi30/uris.py b/pyowm/airpollutionapi30/uris.py
index 2591623d..60538b71 100644
--- a/pyowm/airpollutionapi30/uris.py
+++ b/pyowm/airpollutionapi30/uris.py
@@ -1,8 +1,16 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
+# deprecated API endpoints
 ROOT_POLLUTION_API_URL = 'openweathermap.org/pollution/v1'
 CO_INDEX_URL = 'co'
 OZONE_URL = 'o3'
 NO2_INDEX_URL = 'no2'
 SO2_INDEX_URL = 'so2'
+
+
+# current API endpoint
+NEW_ROOT_POLLUTION_API_URL = 'openweathermap.org/data/2.5'
+AIR_POLLUTION_URL = 'air_pollution'
+AIR_POLLUTION_FORECAST_URL = 'air_pollution/forecast'
+AIR_POLLUTION_HISTORY_URL = 'air_pollution/history'
diff --git a/pyowm/alertapi30/condition.py b/pyowm/alertapi30/condition.py
index e1f3425a..535586f4 100644
--- a/pyowm/alertapi30/condition.py
+++ b/pyowm/alertapi30/condition.py
@@ -33,7 +33,7 @@ def __init__(self, weather_param, operator, amount, id=None):
         self.operator = operator
 
         assert amount is not None
-        assert isinstance(amount, int) or isinstance(amount, float)
+        assert isinstance(amount, (int, float))
         self.amount = amount
         self.id = id
 
diff --git a/pyowm/alertapi30/trigger.py b/pyowm/alertapi30/trigger.py
index 3cf2e97f..feb824bc 100644
--- a/pyowm/alertapi30/trigger.py
+++ b/pyowm/alertapi30/trigger.py
@@ -56,7 +56,7 @@ def __init__(self, start_after_millis, end_after_millis, conditions, area, alert
             raise ValueError('The area for a trigger must contain at least one geoJSON type: you provided none')
         self.area = area
         if alerts is None or len(alerts) == 0:
-            self.alerts = list()
+            self.alerts = []
         else:
             self.alerts = alerts
         if alert_channels is None or len(alert_channels) == 0:
@@ -91,11 +91,7 @@ def get_alerts_since(self, timestamp):
         :return: list of `Alert` instances
         """
         unix_timestamp = formatting.to_UNIXtime(timestamp)
-        result = []
-        for alert in self.alerts:
-            if alert.last_update >= unix_timestamp:
-                result.append(alert)
-        return result
+        return [alert for alert in self.alerts if alert.last_update >= unix_timestamp]
 
     def get_alerts_on(self, weather_param):
         """
@@ -145,7 +141,7 @@ def from_dict(cls, the_dict):
                 alert_id = key
                 alert_data = alerts_dict[alert_id]
                 alert_last_update = alert_data['last_update']
-                alert_met_conds = list()
+                alert_met_conds = []
                 for c in alert_data['conditions']:
                     if isinstance(c['current_value'], int):
                         cv = c['current_value']
@@ -165,9 +161,14 @@ def from_dict(cls, the_dict):
             alert_channels = None  # defaulting
 
         except ValueError as e:
-            raise pyowm.commons.exceptions.ParseAPIResponseError('Impossible to parse JSON: %s' % e)
+            raise pyowm.commons.exceptions.ParseAPIResponseError(
+                'Impossible to parse JSON: %s' % e
+            )
+
         except KeyError as e:
-            raise pyowm.commons.exceptions.ParseAPIResponseError('Impossible to parse JSON: %s' % e)
+            raise pyowm.commons.exceptions.ParseAPIResponseError(
+                'Impossible to parse JSON: %s' % e
+            )
 
         return Trigger(start, end, conditions, area=area, alerts=alerts, alert_channels=alert_channels, id=trigger_id)
 
diff --git a/pyowm/commons/cityidregistry.py b/pyowm/commons/cityidregistry.py
index 4453e04a..8ecd5ac1 100644
--- a/pyowm/commons/cityidregistry.py
+++ b/pyowm/commons/cityidregistry.py
@@ -14,7 +14,8 @@ class CityIDRegistry:
     MATCHINGS = {
         'exact': lambda city_name, toponym: city_name == toponym,
         'nocase': lambda city_name, toponym: city_name.lower() == toponym.lower(),
-        'like': lambda city_name, toponym: city_name.lower() in toponym.lower()
+        'like': lambda city_name, toponym: city_name.lower() in toponym.lower(),
+        'startswith': lambda city_name, toponym: toponym.lower().startswith(city_name.lower())
     }
 
     def __init__(self, filepath_regex):
@@ -50,10 +51,13 @@ def ids_for(self, city_name, country=None, matching='nocase'):
         :param country: two character str representing the country where to
         search for the city. Defaults to `None`, which means: search in all
         countries.
-        :param matching: str among `exact` (literal, case-sensitive matching),
-        `nocase` (literal, case-insensitive matching) and `like` (matches cities
-        whose name contains as a substring the string fed to the function, no
-        matter the case). Defaults to `nocase`.
+        :param matching: str. Default is `nocase`. Possible values:
+        `exact` - literal, case-sensitive matching,
+        `nocase` - literal, case-insensitive matching,
+        `like` - matches cities whose name contains, as a substring, the string
+        fed to the function, case-insensitive,
+        `startswith` - matches cities whose names start with the string fed
+        to the function, case-insensitive.
         :raises ValueError if the value for `matching` is unknown
         :return: list of tuples
         """
@@ -79,10 +83,13 @@ def locations_for(self, city_name, country=None, matching='nocase'):
         :param country: two character str representing the country where to
         search for the city. Defaults to `None`, which means: search in all
         countries.
-        :param matching: str among `exact` (literal, case-sensitive matching),
-        `nocase` (literal, case-insensitive matching) and `like` (matches cities
-        whose name contains as a substring the string fed to the function, no
-        matter the case). Defaults to `nocase`.
+        :param matching: str. Default is `nocase`. Possible values:
+        `exact` - literal, case-sensitive matching,
+        `nocase` - literal, case-insensitive matching,
+        `like` - matches cities whose name contains, as a substring, the string
+        fed to the function, case-insensitive,
+        `startswith` - matches cities whose names start with the string fed
+        to the function, case-insensitive.
         :raises ValueError if the value for `matching` is unknown
         :return: list of `weatherapi25.location.Location` objects
         """
@@ -109,10 +116,13 @@ def geopoints_for(self, city_name, country=None, matching='nocase'):
         :param country: two character str representing the country where to
         search for the city. Defaults to `None`, which means: search in all
         countries.
-        :param matching: str among `exact` (literal, case-sensitive matching),
-        `nocase` (literal, case-insensitive matching) and `like` (matches cities
-        whose name contains as a substring the string fed to the function, no
-        matter the case). Defaults to `nocase`.
+        :param matching: str. Default is `nocase`. Possible values:
+        `exact` - literal, case-sensitive matching,
+        `nocase` - literal, case-insensitive matching,
+        `like` - matches cities whose name contains, as a substring, the string
+        fed to the function, case-insensitive,
+        `startswith` - matches cities whose names start with the string fed
+        to the function, case-insensitive.
         :raises ValueError if the value for `matching` is unknown
         :return: list of `pyowm.utils.geo.Point` objects
         """
@@ -131,7 +141,7 @@ def _filter_matching_lines(self, city_name, country, matching):
         :param matching: str
         :return: list of lists
         """
-        result = list()
+        result = []
 
         # find the right file to scan and extract its lines. Upon "like"
         # matchings, just read all files
@@ -145,14 +155,12 @@ def _filter_matching_lines(self, city_name, country, matching):
         # the specified matching style
         for line in lines:
             tokens = line.split(",")
-            # sometimes city names have an inner comma...
-            if len(tokens) == 6:
-                tokens = [tokens[0]+','+tokens[1], tokens[2], tokens[3],
-                          tokens[4], tokens[5]]
+            # sometimes city names have one or more inner commas
+            if len(tokens) > 5:
+                tokens = [','.join(tokens[:-4]), *tokens[-4:]]
             # check country
-            if country is not None:
-                if tokens[4] != country:
-                    continue
+            if country is not None and tokens[4] != country:
+                continue
 
             # check city_name
             if self._city_name_matches(city_name, tokens[0], matching):
@@ -193,7 +201,7 @@ def _get_lines(self, filename):
             return lines
 
     def _get_all_lines(self):
-        all_lines = list()
+        all_lines = []
         for city_name in ['a', 'g', 'm', 's']:  # all available city ID files
             filename = self._assess_subfile_from(city_name)
             all_lines.extend(self._get_lines(filename))
diff --git a/pyowm/commons/http_client.py b/pyowm/commons/http_client.py
index 9162d4e2..7ba435b7 100644
--- a/pyowm/commons/http_client.py
+++ b/pyowm/commons/http_client.py
@@ -29,18 +29,15 @@ def __init__(self, root_uri_token, api_key, config, has_subdomains=True):
         self.subdomain = None
         self.proxies = None
         self.path = None
-        self.params = dict()
-        self.headers = dict()
+        self.params = {}
+        self.headers = {}
         self._set_schema()
         self._set_subdomain()
         self._set_proxies()
 
     def _set_schema(self):
         use_ssl = self.config['connection']['use_ssl']
-        if use_ssl:
-            self.schema = 'https'
-        else:
-            self.schema = 'http'
+        self.schema = 'https' if use_ssl else 'http'
 
     def _set_subdomain(self):
         if self.has_subdomains:
@@ -51,7 +48,7 @@ def _set_proxies(self):
         if self.config['connection']['use_proxy']:
             self.proxies = self.config['proxies']
         else:
-            self.proxies = dict()
+            self.proxies = {}
 
     def with_path(self, path_uri_token):
         assert isinstance(path_uri_token, str)
@@ -280,16 +277,14 @@ def delete(self, path, params=None, data=None, headers=None):
     def check_status_code(cls, status_code, payload):
         if status_code < 400:
             return
-        if status_code == 400:
+        if status_code == 400 or status_code not in [401, 404, 502]:
             raise exceptions.APIRequestError(payload)
         elif status_code == 401:
             raise exceptions.UnauthorizedError('Invalid API Key provided')
         elif status_code == 404:
             raise exceptions.NotFoundError('Unable to find the resource')
-        elif status_code == 502:
-            raise exceptions.BadGatewayError('Unable to contact the upstream server')
         else:
-            raise exceptions.APIRequestError(payload)
+            raise exceptions.BadGatewayError('Unable to contact the upstream server')
 
     def __repr__(self):
         return "<%s.%s - root: %s>" % (__name__, self.__class__.__name__, self.root_uri)
diff --git a/pyowm/commons/tile.py b/pyowm/commons/tile.py
index e219e91e..37a4b0b0 100644
--- a/pyowm/commons/tile.py
+++ b/pyowm/commons/tile.py
@@ -92,7 +92,7 @@ def geoocoords_to_tile_coords(cls, lon, lat, zoom):
     @classmethod
     def tile_coords_to_bbox(cls, x, y, zoom):
         """
-        Calculates the lon/lat estrema of the bounding box corresponding to specific tile coordinates. Output coodinates
+        Calculates the lon/lat estrema of the bounding box corresponding to specific tile coordinates. Output coordinates
         are in degrees and in the Mercator Projection (http://en.wikipedia.org/wiki/Mercator_projection)
 
         :param x: the x tile coordinates
diff --git a/pyowm/commons/uris.py b/pyowm/commons/uris.py
new file mode 100644
index 00000000..53fd9db4
--- /dev/null
+++ b/pyowm/commons/uris.py
@@ -0,0 +1,7 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+ROOT_GEOCODING_API_URL = 'openweathermap.org/geo/1.0'
+DIRECT_GEOCODING_URI = 'direct'
+REVERSE_GEOCODING_URI = 'reverse'
+
diff --git a/pyowm/constants.py b/pyowm/constants.py
index 22753ffb..21deef27 100644
--- a/pyowm/constants.py
+++ b/pyowm/constants.py
@@ -1,10 +1,11 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-PYOWM_VERSION = (3, 1, 1)
+PYOWM_VERSION = (3, 2, 0)
 AGRO_API_VERSION = (1, 0, 0)
 AIRPOLLUTION_API_VERSION = (3, 0, 0)
 ALERT_API_VERSION = (3, 0, 0)
+GEOCODING_API_VERSION = (1, 0, 0)
 STATIONS_API_VERSION = (3, 0, 0)
 UVINDEX_API_VERSION = (3, 0, 0)
 WEATHER_API_VERSION = (2, 5, 0)
diff --git a/tests/integration/pollutionapi30/__init__.py b/pyowm/geocodingapi10/__init__.py
similarity index 100%
rename from tests/integration/pollutionapi30/__init__.py
rename to pyowm/geocodingapi10/__init__.py
diff --git a/pyowm/geocodingapi10/geocoding_manager.py b/pyowm/geocodingapi10/geocoding_manager.py
new file mode 100644
index 00000000..5e02efd8
--- /dev/null
+++ b/pyowm/geocodingapi10/geocoding_manager.py
@@ -0,0 +1,85 @@
+from pyowm.commons.http_client import HttpClient
+from pyowm.commons.uris import ROOT_GEOCODING_API_URL, DIRECT_GEOCODING_URI, REVERSE_GEOCODING_URI
+from pyowm.constants import GEOCODING_API_VERSION
+from pyowm.utils import geo
+from pyowm.weatherapi25.location import Location
+
+
+class GeocodingManager:
+
+    """
+    A manager objects that provides a full interface to OWM Geocoding API.
+
+    :param API_key: the OWM API key
+    :type API_key: str
+    :param config: the configuration dictionary
+    :type config: dict
+    :returns: an *GeocodingManager* instance
+    :raises: *AssertionError* when no API Key is provided
+
+    """
+
+    def __init__(self, API_key, config):
+        assert API_key is not None, 'You must provide a valid API Key'
+        self.API_key = API_key
+        assert isinstance(config, dict)
+        self.http_client = HttpClient(API_key, config, ROOT_GEOCODING_API_URL)
+
+    def geocoding_api_version(self):
+        return GEOCODING_API_VERSION
+
+    def geocode(self, toponym, country=None, state_code=None, limit=None):
+        """
+        Invokes the direct geocoding API endpoint
+
+        :param toponym: the name of the location
+        :type toponym: `str`
+        :param country: the 2-chars ISO symbol of the country
+        :type country: `str` or `None`
+        :param state_code: the 2-chars ISO symbol of state (only useful in case the country is US)
+        :type state_code: `str` or `None`
+        :param limit: the max number of results to be returned in case of multiple matchings (no limits by default)
+        :type limit: `int` or `None`
+        :returns: a list of *Location* instances
+        :raises: *AssertionError*, *ValueError*, *APIRequestError*
+
+        """
+        assert toponym, 'Toponym must be specified'
+        if country is not None and len(country) != 2:
+            raise ValueError("Country must be a 2-char string")
+        if state_code is not None and len(state_code) != 2:
+            raise ValueError("State Code must be a 2-char string")
+        if limit is not None:
+            assert isinstance(limit, int)
+            assert limit > 0
+
+        query = toponym
+        if state_code is not None:
+            query += ',' + state_code
+        if country is not None:
+            query += ',' + country
+
+        params = {'q': query}
+
+        if limit is not None:
+            params['limit'] = limit
+
+        _, json_data = self.http_client.get_json(DIRECT_GEOCODING_URI, params=params)
+        return [Location.from_dict(item) for item in json_data]
+
+    def reverse_geocode(self, lat, lon, limit=None):
+        geo.assert_is_lon(lon)
+        geo.assert_is_lat(lat)
+        if limit is not None:
+            assert isinstance(limit, int)
+            assert limit > 0
+
+        params = {'lat': lat, 'lon': lon}
+        if limit is not None:
+            params['limit'] = limit
+
+        _, json_data = self.http_client.get_json(REVERSE_GEOCODING_URI, params=params)
+        return [Location.from_dict(item) for item in json_data]
+
+    def __repr__(self):
+        return '<%s.%s>' % (__name__, self.__class__.__name__)
\ No newline at end of file
diff --git a/pyowm/owm.py b/pyowm/owm.py
index 869bf664..a5e90b3a 100644
--- a/pyowm/owm.py
+++ b/pyowm/owm.py
@@ -5,6 +5,7 @@
 from pyowm.agroapi10 import agro_manager
 from pyowm.airpollutionapi30 import airpollution_manager
 from pyowm.alertapi30 import alert_manager
+from pyowm.geocodingapi10 import geocoding_manager
 from pyowm.stationsapi30 import stations_manager
 from pyowm.tiles import tile_manager
 from pyowm.utils import strings
@@ -125,6 +126,14 @@ def weather_manager(self):
         """
         return weather_manager.WeatherManager(self.api_key, self.config)
 
+    def geocoding_manager(self):
+        """
+        Gives a `pyowm.geocoding10.geocoding_manager.GeocodingManager` instance that can be used to perform direct
+        and reverse geocoding
+        :return: a `pyowm.geocoding10.geocoding_manager.GeocodingManager` instance
+        """
+        return geocoding_manager.GeocodingManager(self.api_key, self.config)
+
     def __repr__(self):
         return "<%s.%s - API key=%s, subscription type=%s, PyOWM version=%s>" % \
                     (__name__,
diff --git a/pyowm/stationsapi30/buffer.py b/pyowm/stationsapi30/buffer.py
index e9e1b94d..34e9d9ad 100644
--- a/pyowm/stationsapi30/buffer.py
+++ b/pyowm/stationsapi30/buffer.py
@@ -17,7 +17,7 @@ def __init__(self, station_id):
         assert station_id is not None
         self.station_id = station_id
         self.created_at = timestamps.now(timeformat='unix')
-        self.measurements = list()
+        self.measurements = []
 
     def creation_time(self, timeformat='unix'):
         """Returns the UTC time of creation of this aggregated measurement
@@ -70,7 +70,7 @@ def empty(self):
         Drops all measurements of this buffer instance
 
         """
-        self.measurements = list()
+        self.measurements = []
 
     def sort_chronologically(self):
         """
@@ -90,10 +90,10 @@ def __len__(self):
         return len(self.measurements)
 
     def __iter__(self):
-        return (m for m in self.measurements)
+        return iter(self.measurements)
 
     def __add__(self, other):
-        assert all([i.station_id == self.station_id for i in other])
+        assert all(i.station_id == self.station_id for i in other)
         result = copy.deepcopy(self)
         for m in other.measurements:
             result.append(m)
diff --git a/pyowm/stationsapi30/persistence_backend.py b/pyowm/stationsapi30/persistence_backend.py
index 5fc8aea2..a2186710 100644
--- a/pyowm/stationsapi30/persistence_backend.py
+++ b/pyowm/stationsapi30/persistence_backend.py
@@ -76,8 +76,6 @@ def load_to_buffer(self):
             return result
 
     def persist_buffer(self, buffer):
-        data = list()
         with open(self._file_path, 'w') as f:
-            for msmt in buffer:
-                data.append(msmt.to_JSON())
+            data = [msmt.to_JSON() for msmt in buffer]
             f.write('[%s]' % ','.join(data))
diff --git a/pyowm/stationsapi30/station.py b/pyowm/stationsapi30/station.py
index 90ee1aed..fc220e55 100644
--- a/pyowm/stationsapi30/station.py
+++ b/pyowm/stationsapi30/station.py
@@ -43,9 +43,8 @@ def __init__(self, id, created_at, updated_at, external_id, name,
         self._lon = float(lon)
         if lat < -90.0 or lat > 90.0:
             raise ValueError("'lat' value must be between -90 and 90")
-        if alt is not None:
-            if alt < 0.0:
-                raise ValueError("'alt' value must not be negative")
+        if alt is not None and alt < 0.0:
+            raise ValueError("'alt' value must not be negative")
         self.id = id
         self.created_at = created_at
         if self.created_at is not None:
@@ -72,10 +71,7 @@ def _format_micros(self, datestring):
             else:
                 return datestring + '.000000Z'
         else:
-            if len(parts[-1]) > 6:
-                micros = parts[-1][:6]
-            else:
-                micros = parts[-1]
+            micros = parts[-1][:6] if len(parts[-1]) > 6 else parts[-1]
             return '.'.join(
                 parts[:-1] + ['{:06d}'.format(int(micros))]) + 'Z'
 
diff --git a/pyowm/stationsapi30/stations_manager.py b/pyowm/stationsapi30/stations_manager.py
index a45c18b5..f585326d 100644
--- a/pyowm/stationsapi30/stations_manager.py
+++ b/pyowm/stationsapi30/stations_manager.py
@@ -88,9 +88,8 @@ def create_station(self, external_id, name, lat, lon, alt=None):
             raise ValueError("'lon' value must be between -180 and 180")
         if lat < -90.0 or lat > 90.0:
             raise ValueError("'lat' value must be between -90 and 90")
-        if alt is not None:
-            if alt < 0.0:
-                raise ValueError("'alt' value must not be negative")
+        if alt is not None and alt < 0.0:
+            raise ValueError("'alt' value must not be negative")
         status, payload = self.http_client.post(
             STATIONS_URI,
             params={'appid': self.API_key},
@@ -163,7 +162,7 @@ def send_measurements(self, list_of_measurements):
         :returns: `None` if creation is successful, an exception otherwise
         """
         assert list_of_measurements is not None
-        assert all([m.station_id is not None for m in list_of_measurements])
+        assert all(m.station_id is not None for m in list_of_measurements)
         msmts = [self._structure_dict(m) for m in list_of_measurements]
         status, _ = self.http_client.post(
             MEASUREMENTS_URI,
@@ -235,37 +234,40 @@ def send_buffer(self, buffer):
 
     def _structure_dict(self, measurement):
         d = measurement.to_dict()
-        item = dict()
-        item['station_id'] = d['station_id']
-        item['dt'] = d['timestamp']
-        item['temperature'] = d['temperature']
-        item['wind_speed'] = d['wind_speed']
-        item['wind_gust'] = d['wind_gust']
-        item['wind_deg'] = d['wind_deg']
-        item['pressure'] = d['pressure']
-        item['humidity'] = d['humidity']
-        item['rain_1h'] = d['rain_1h']
-        item['rain_6h'] = d['rain_6h']
-        item['rain_24h'] = d['rain_24h']
-        item['snow_1h'] = d['snow_1h']
-        item['snow_6h'] = d['snow_6h']
-        item['snow_24h'] = d['snow_24h']
-        item['dew_point'] = d['dew_point']
-        item['humidex'] = d['humidex']
-        item['heat_index'] = d['heat_index']
-        item['visibility_distance'] = d['visibility_distance']
-        item['visibility_prefix'] = d['visibility_prefix']
-        item['clouds'] = [dict(distance=d['clouds_distance']),
-                          dict(condition=d['clouds_condition']),
-                          dict(cumulus=d['clouds_cumulus'])]
-        item['weather'] = [
-            dict(precipitation=d['weather_precipitation']),
-            dict(descriptor=d['weather_descriptor']),
-            dict(intensity=d['weather_intensity']),
-            dict(proximity=d['weather_proximity']),
-            dict(obscuration=d['weather_obscuration']),
-            dict(other=d['weather_other'])]
-        return item
+        return {
+            'station_id': d['station_id'],
+            'dt': d['timestamp'],
+            'temperature': d['temperature'],
+            'wind_speed': d['wind_speed'],
+            'wind_gust': d['wind_gust'],
+            'wind_deg': d['wind_deg'],
+            'pressure': d['pressure'],
+            'humidity': d['humidity'],
+            'rain_1h': d['rain_1h'],
+            'rain_6h': d['rain_6h'],
+            'rain_24h': d['rain_24h'],
+            'snow_1h': d['snow_1h'],
+            'snow_6h': d['snow_6h'],
+            'snow_24h': d['snow_24h'],
+            'dew_point': d['dew_point'],
+            'humidex': d['humidex'],
+            'heat_index': d['heat_index'],
+            'visibility_distance': d['visibility_distance'],
+            'visibility_prefix': d['visibility_prefix'],
+            'clouds': [
+                dict(distance=d['clouds_distance']),
+                dict(condition=d['clouds_condition']),
+                dict(cumulus=d['clouds_cumulus']),
+            ],
+            'weather': [
+                dict(precipitation=d['weather_precipitation']),
+                dict(descriptor=d['weather_descriptor']),
+                dict(intensity=d['weather_intensity']),
+                dict(proximity=d['weather_proximity']),
+                dict(obscuration=d['weather_obscuration']),
+                dict(other=d['weather_other']),
+            ],
+        }
 
     def __repr__(self):
         return '<%s.%s>' % (__name__, self.__class__.__name__)
\ No newline at end of file
diff --git a/pyowm/utils/formatting.py b/pyowm/utils/formatting.py
index 7d167f87..c0dc3c5d 100644
--- a/pyowm/utils/formatting.py
+++ b/pyowm/utils/formatting.py
@@ -134,7 +134,7 @@ def to_UNIXtime(timeobject):
 def ISO8601_to_UNIXtime(iso):
     """
     Converts an ISO8601-formatted string in the format
-    ``YYYY-MM-DD HH:MM:SS+00:00`` to the correspondant UNIXtime
+    ``YYYY-MM-DD HH:MM:SS+00:00`` to the correspondent UNIXtime
 
     :param iso: the ISO8601-formatted string
     :type iso: string
diff --git a/pyowm/utils/geo.py b/pyowm/utils/geo.py
index 5e83e924..255326f2 100644
--- a/pyowm/utils/geo.py
+++ b/pyowm/utils/geo.py
@@ -103,7 +103,7 @@ def bounding_square_polygon(self, inscribed_circle_radius_km=10.0):
          :type inscribed_circle_radius_km: int or float
          :return: a `pyowm.utils.geo.Polygon` instance
          """
-        assert isinstance(inscribed_circle_radius_km, int) or isinstance(inscribed_circle_radius_km, float)
+        assert isinstance(inscribed_circle_radius_km, (int, float))
         assert inscribed_circle_radius_km > 0., 'Radius must be greater than zero'
 
         # turn metric distance to radians on the approximated local sphere
@@ -248,7 +248,7 @@ def __init__(self, list_of_lists):
         if not list_of_lists:
             raise ValueError("A Polygon cannot be empty")
         first, last = list_of_lists[0][0], list_of_lists[0][-1]
-        if not first == last:
+        if first != last:
             raise ValueError("The start and end point of Polygon must coincide")
         self._geom = geojson.Polygon(list_of_lists)
 
@@ -292,9 +292,7 @@ def from_points(cls, list_of_lists):
         """
         result = []
         for l in list_of_lists:
-            curve = []
-            for point in l:
-                curve.append((point.lon, point.lat))
+            curve = [(point.lon, point.lat) for point in l]
             result.append(curve)
         return Polygon(result)
 
diff --git a/pyowm/utils/measurables.py b/pyowm/utils/measurables.py
index ec4421ac..e9877f81 100644
--- a/pyowm/utils/measurables.py
+++ b/pyowm/utils/measurables.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-"""
 
-# Temperature coneversion constants
+# Temperature conversion constants
 KELVIN_OFFSET = 273.15
 FAHRENHEIT_OFFSET = 32.0
 FAHRENHEIT_DEGREE_SCALE = 1.8
@@ -11,6 +11,16 @@
 KM_PER_HOUR_FOR_ONE_METER_PER_SEC = 3.6
 KNOTS_FOR_ONE_METER_PER_SEC = 1.94384
 
+# Barometric conversion constants
+HPA_FOR_ONE_INHG = 33.8639
+
+# Visibility distance conversion constants
+MILE_FOR_ONE_METER = 0.000621371
+KMS_FOR_ONE_METER = .001
+
+# Decimal precision
+ROUNDED_TO = 2
+
 
 def kelvin_dict_to(d, target_temperature_unit):
     """
@@ -85,7 +95,7 @@ def metric_wind_dict_to_imperial(d):
         to miles/hour
 
     """
-    result = dict()
+    result = {}
     for key, value in d.items():
         if key != 'deg':  # do not convert wind degree
             result[key] = value * MILES_PER_HOUR_FOR_ONE_METER_PER_SEC
@@ -105,7 +115,7 @@ def metric_wind_dict_to_km_h(d):
         to km/hour
 
     """
-    result = dict()
+    result = {}
     for key, value in d.items():
         if key != 'deg':  # do not convert wind degree
             result[key] = value * KM_PER_HOUR_FOR_ONE_METER_PER_SEC
@@ -125,7 +135,7 @@ def metric_wind_dict_to_knots(d):
         to km/hour
 
     """
-    result = dict()
+    result = {}
     for key, value in d.items():
         if key != 'deg':  # do not convert wind degree
             result[key] = value * KNOTS_FOR_ONE_METER_PER_SEC
@@ -147,7 +157,7 @@ def metric_wind_dict_to_beaufort(d):
         to Beaufort level
 
     """
-    result = dict()
+    result = {}
     for key, value in d.items():
         if key != 'deg':  # do not convert wind degree
             if value <= 0.2:
@@ -179,4 +189,48 @@ def metric_wind_dict_to_beaufort(d):
             result[key] = bf
         else:
             result[key] = value
-    return result
\ No newline at end of file
+    return result
+
+
+def metric_pressure_dict_to_inhg(d):
+    """
+    Converts all barometric pressure values in a dict to "inches of mercury."
+
+    :param d: the dictionary containing metric values
+    :type d: dict
+    :returns: a dict with the same keys as the input dict and values converted
+        to "Hg or inHg (inches of mercury)
+
+    Note what OWM says about pressure: "Atmospheric pressure [is given in hPa]
+    (on the sea level, if there is no sea_level or grnd_level data)"
+    """
+    result = dict()
+    for key, value in d.items():
+        if value is None:
+            continue
+        result[key] = round((value / HPA_FOR_ONE_INHG), ROUNDED_TO)
+    return result
+
+
+def visibility_distance_to(v, target_visibility_unit='kilometers'):
+    """
+    Converts visibility distance (in meters) to kilometers or miles
+    Defaults to kilometer conversion
+
+    :param distance: the value of visibility_distance
+    :type distance: int
+    :param target_visibility_unit: the unit of conversion
+    :type target_visibility_unit: str
+    :returns: a converted value for visibility_distance (float)
+    """
+    if v is None:
+        return v
+
+    if target_visibility_unit == 'kilometers':
+        const = KMS_FOR_ONE_METER
+    elif target_visibility_unit == 'miles':
+        const = MILE_FOR_ONE_METER
+    else:
+        raise ValueError('Invalid value for target visibility distance unit')
+
+    return round(v * const, ROUNDED_TO)
diff --git a/pyowm/utils/strings.py b/pyowm/utils/strings.py
index cde250d3..7b37ad79 100644
--- a/pyowm/utils/strings.py
+++ b/pyowm/utils/strings.py
@@ -41,5 +41,4 @@ def class_from_dotted_path(dotted_path):
     assert isinstance(dotted_path, str), 'A string must be provided'
     tokens = dotted_path.split('.')
     modpath, class_name = '.'.join(tokens[:-1]), tokens[-1]
-    klass = getattr(importlib.import_module(modpath), class_name)
-    return klass
+    return getattr(importlib.import_module(modpath), class_name)
diff --git a/pyowm/utils/timestamps.py b/pyowm/utils/timestamps.py
index a9decef4..1fab8734 100644
--- a/pyowm/utils/timestamps.py
+++ b/pyowm/utils/timestamps.py
@@ -217,37 +217,33 @@ def next_year(date=None):
 def _timedelta_hours(offset, date=None):
     if date is None:
         return datetime.now(timezone.utc) + timedelta(hours=offset)
-    else:
-        assert isinstance(date, datetime), __name__ + \
-            ": 'date' must be a datetime.datetime object"
-        return date + timedelta(hours=offset)
+    assert isinstance(date, datetime), __name__ + \
+        ": 'date' must be a datetime.datetime object"
+    return date + timedelta(hours=offset)
 
 
 def _timedelta_days(offset, date=None):
     if date is None:
         return datetime.now(timezone.utc) + timedelta(days=offset)
-    else:
-        assert isinstance(date, datetime), __name__ + \
-            ": 'date' must be a datetime.datetime object"
-        return date + timedelta(days=offset)
+    assert isinstance(date, datetime), __name__ + \
+        ": 'date' must be a datetime.datetime object"
+    return date + timedelta(days=offset)
 
 
 def _timedelta_months(offset, date=None):
     if date is None:
         return datetime.now(timezone.utc) + timedelta(days=offset * 30)
-    else:
-        assert isinstance(date, datetime), __name__ + \
-            ": 'date' must be a datetime.datetime object"
-        return date + timedelta(days=offset * 30)
+    assert isinstance(date, datetime), __name__ + \
+        ": 'date' must be a datetime.datetime object"
+    return date + timedelta(days=offset * 30)
 
 
 def _timedelta_years(offset, date=None):
     if date is None:
         return datetime.now(timezone.utc) + timedelta(days=offset * 365)
-    else:
-        assert isinstance(date, datetime), __name__ + \
-            ": 'date' must be a datetime.datetime object"
-        return date + timedelta(days=offset * 365)
+    assert isinstance(date, datetime), __name__ + \
+        ": 'date' must be a datetime.datetime object"
+    return date + timedelta(days=offset * 365)
 
 
 def millis_offset_between_epochs(reference_epoch, target_epoch):
diff --git a/pyowm/utils/weather.py b/pyowm/utils/weather.py
index 2405bf97..0a2da330 100644
--- a/pyowm/utils/weather.py
+++ b/pyowm/utils/weather.py
@@ -39,10 +39,10 @@ def any_status_is(weather_list, status, weather_code_registry):
     :returns: ``True`` if the check is positive, ``False`` otherwise
     
     """
-    for weather in weather_list:
-        if status_is(weather, status, weather_code_registry):
-            return True
-    return False
+    return any(
+        status_is(weather, status, weather_code_registry)
+        for weather in weather_list
+    )
 
 
 def filter_by_status(weather_list, status, weather_code_registry):
@@ -60,11 +60,11 @@ def filter_by_status(weather_list, status, weather_code_registry):
     :returns: ``True`` if the check is positive, ``False`` otherwise
     
     """
-    result = []
-    for weather in weather_list:
-        if status_is(weather, status, weather_code_registry):
-            result.append(weather)
-    return result
+    return [
+        weather
+        for weather in weather_list
+        if status_is(weather, status, weather_code_registry)
+    ]
 
 
 def is_in_coverage(unixtime, weathers_list):
@@ -83,14 +83,10 @@ def is_in_coverage(unixtime, weathers_list):
     """
     if not weathers_list:
         return False
-    else:
-        min_of_coverage = min([weather.reference_time() \
-                               for weather in weathers_list])
-        max_of_coverage = max([weather.reference_time() \
-                               for weather in weathers_list])
-        if unixtime < min_of_coverage or unixtime > max_of_coverage:
-            return False
-        return True
+    min_of_coverage = min(weather.reference_time() for weather in weathers_list)
+    max_of_coverage = max([weather.reference_time() \
+                           for weather in weathers_list])
+    return unixtime >= min_of_coverage and unixtime <= max_of_coverage
 
 
 def find_closest_weather(weathers_list, unixtime):
diff --git a/pyowm/uvindexapi30/uvindex_manager.py b/pyowm/uvindexapi30/uvindex_manager.py
index 7c852ee4..7998f4c3 100644
--- a/pyowm/uvindexapi30/uvindex_manager.py
+++ b/pyowm/uvindexapi30/uvindex_manager.py
@@ -72,8 +72,7 @@ def uvindex_forecast_around_coords(self, lat, lon):
         geo.assert_is_lat(lat)
         params = {'lon': lon, 'lat': lat}
         json_data = self.uv_client.get_uvi_forecast(params)
-        uvindex_list = [uvindex.UVIndex.from_dict(item) for item in json_data]
-        return uvindex_list
+        return [uvindex.UVIndex.from_dict(item) for item in json_data]
 
     def uvindex_history_around_coords(self, lat, lon, start, end=None):
         """
@@ -107,8 +106,7 @@ def uvindex_history_around_coords(self, lat, lon, start, end=None):
             end = formatting.timeformat(end, 'unix')
         params = {'lon': lon, 'lat': lat, 'start': start, 'end': end}
         json_data = self.uv_client.get_uvi_history(params)
-        uvindex_list = [uvindex.UVIndex.from_dict(item) for item in json_data]
-        return uvindex_list
+        return [uvindex.UVIndex.from_dict(item) for item in json_data]
 
     def __repr__(self):
         return '<%s.%s>' % (__name__, self.__class__.__name__)
\ No newline at end of file
diff --git a/pyowm/weatherapi25/forecast.py b/pyowm/weatherapi25/forecast.py
index 221af34b..18dd2d05 100644
--- a/pyowm/weatherapi25/forecast.py
+++ b/pyowm/weatherapi25/forecast.py
@@ -139,7 +139,7 @@ def __len__(self):
         return len(self.weathers)
 
     def __iter__(self):
-        return (w for w in self.weathers)
+        return iter(self.weathers)
 
     def __repr__(self):
         return "<%s.%s - reception_time=%s, interval=%s>" % (__name__, \
diff --git a/pyowm/weatherapi25/forecaster.py b/pyowm/weatherapi25/forecaster.py
index da8f3199..e0362789 100644
--- a/pyowm/weatherapi25/forecaster.py
+++ b/pyowm/weatherapi25/forecaster.py
@@ -37,8 +37,7 @@ def when_starts(self, timeformat='unix'):
         :raises: *ValueError* when invalid time format values are provided
 
         """
-        start_coverage = min([item.reference_time() \
-                              for item in self.forecast])
+        start_coverage = min(item.reference_time() for item in self.forecast)
         return formatting.timeformat(start_coverage, timeformat)
 
     def when_ends(self, timeformat='unix'):
@@ -55,8 +54,7 @@ def when_ends(self, timeformat='unix'):
         :raises: *ValueError* when invalid time format values are provided
 
         """
-        end_coverage = max([item.reference_time()
-                            for item in self.forecast])
+        end_coverage = max(item.reference_time() for item in self.forecast)
         return formatting.timeformat(end_coverage, timeformat)
 
     def will_have_rain(self):
@@ -382,10 +380,9 @@ def most_hot(self):
         hottest = None
         for weather in self.forecast.weathers:
             d = weather.temperature()
-            if 'temp_max' in d:
-                if d['temp_max'] > maxtemp:
-                    maxtemp = d['temp_max']
-                    hottest = weather
+            if 'temp_max' in d and d['temp_max'] > maxtemp:
+                maxtemp = d['temp_max']
+                hottest = weather
         return hottest
 
     def most_cold(self):
@@ -401,10 +398,9 @@ def most_cold(self):
         coldest = None
         for weather in self.forecast.weathers:
             d = weather.temperature()
-            if 'temp_min' in d:
-                if d['temp_min'] < mintemp:
-                    mintemp = d['temp_min']
-                    coldest = weather
+            if 'temp_min' in d and d['temp_min'] < mintemp:
+                mintemp = d['temp_min']
+                coldest = weather
         return coldest
 
     def most_humid(self):
@@ -437,10 +433,9 @@ def most_rainy(self):
         most_rainy = None
         for weather in self.forecast.weathers:
             d = weather.rain
-            if 'all' in d:
-                if d['all'] > max_rain:
-                    max_rain = d['all']
-                    most_rainy = weather
+            if 'all' in d and d['all'] > max_rain:
+                max_rain = d['all']
+                most_rainy = weather
         return most_rainy
 
     def most_snowy(self):
@@ -456,10 +451,9 @@ def most_snowy(self):
         most_snowy = None
         for weather in self.forecast.weathers:
             d = weather.snow
-            if 'all' in d:
-                if d['all'] > max_snow:
-                    max_snow = d['all']
-                    most_snowy = weather
+            if 'all' in d and d['all'] > max_snow:
+                max_snow = d['all']
+                most_snowy = weather
         return most_snowy
 
     def most_windy(self):
@@ -475,10 +469,9 @@ def most_windy(self):
         most_windy = None
         for weather in self.forecast.weathers:
             d = weather.wind()
-            if 'speed' in d:
-                if d['speed'] > max_wind_speed:
-                    max_wind_speed = d['speed']
-                    most_windy = weather
+            if 'speed' in d and d['speed'] > max_wind_speed:
+                max_wind_speed = d['speed']
+                most_windy = weather
         return most_windy
 
     def __repr__(self):
diff --git a/pyowm/weatherapi25/historian.py b/pyowm/weatherapi25/historian.py
index cc12b6a0..2ebd5fc0 100644
--- a/pyowm/weatherapi25/historian.py
+++ b/pyowm/weatherapi25/historian.py
@@ -92,7 +92,7 @@ def wind_series(self):
 
     def max_temperature(self,  unit='kelvin'):
         """Returns a tuple containing the max value in the temperature
-        series preceeded by its timestamp
+        series preceded by its timestamp
         
         :param unit: the unit of measure for the temperature values. May be
             among: '*kelvin*' (default), '*celsius*' or '*fahrenheit*'
@@ -114,7 +114,7 @@ def max_temperature(self,  unit='kelvin'):
         
     def min_temperature(self, unit='kelvin'):
         """Returns a tuple containing the min value in the temperature
-        series preceeded by its timestamp
+        series preceded by its timestamp
         
         :param unit: the unit of measure for the temperature values. May be
             among: '*kelvin*' (default), '*celsius*' or '*fahrenheit*'
@@ -157,7 +157,7 @@ def average_temperature(self, unit='kelvin'):
     
     def max_humidity(self):
         """Returns a tuple containing the max value in the humidity
-        series preceeded by its timestamp
+        series preceded by its timestamp
 
         :returns: a tuple
         :raises: ValueError when the measurement series is empty
@@ -167,7 +167,7 @@ def max_humidity(self):
         
     def min_humidity(self):
         """Returns a tuple containing the min value in the humidity
-        series preceeded by its timestamp
+        series preceded by its timestamp
 
         :returns: a tuple
         :raises: ValueError when the measurement series is empty
@@ -186,7 +186,7 @@ def average_humidity(self):
 
     def max_pressure(self):
         """Returns a tuple containing the max value in the pressure
-        series preceeded by its timestamp
+        series preceded by its timestamp
 
         :returns: a tuple
         :raises: ValueError when the measurement series is empty
@@ -196,7 +196,7 @@ def max_pressure(self):
         
     def min_pressure(self):
         """Returns a tuple containing the min value in the pressure
-        series preceeded by its timestamp
+        series preceded by its timestamp
 
         :returns: a tuple
         :raises: ValueError when the measurement series is empty
@@ -215,7 +215,7 @@ def average_pressure(self):
 
     def max_rain(self):
         """Returns a tuple containing the max value in the rain
-        series preceeded by its timestamp
+        series preceded by its timestamp
 
         :returns: a tuple
         :raises: ValueError when the measurement series is empty
@@ -225,7 +225,7 @@ def max_rain(self):
         
     def min_rain(self):
         """Returns a tuple containing the min value in the rain
-        series preceeded by its timestamp
+        series preceded by its timestamp
 
         :returns: a tuple
         :raises: ValueError when the measurement series is empty
diff --git a/pyowm/weatherapi25/location.py b/pyowm/weatherapi25/location.py
index 0f4a076d..3d79975f 100644
--- a/pyowm/weatherapi25/location.py
+++ b/pyowm/weatherapi25/location.py
@@ -69,18 +69,9 @@ def from_dict(cls, the_dict):
         country = None
         if 'sys' in the_dict and 'country' in the_dict['sys']:
             country = the_dict['sys']['country']
-        if 'city' in the_dict:
-            data = the_dict['city']
-        else:
-            data = the_dict
-        if 'name' in data:
-            name = data['name']
-        else:
-            name = None
-        if 'id' in data:
-            ID = int(data['id'])
-        else:
-            ID = None
+        data = the_dict['city'] if 'city' in the_dict else the_dict
+        name = data['name'] if 'name' in data else None
+        ID = int(data['id']) if 'id' in data else None
         if 'coord' in data:
             lon = data['coord'].get('lon', 0.0)
             lat = data['coord'].get('lat', 0.0)
@@ -92,6 +83,9 @@ def from_dict(cls, the_dict):
             else:
                 lon = 0.0
             lat = data['station']['coord'].get('lat', 0.0)
+        elif 'lat' in the_dict and 'lon' in the_dict:
+            lat = the_dict['lat']
+            lon = the_dict['lon']
         else:
             raise KeyError("Impossible to read geographical coordinates from JSON")
         if 'country' in data:
diff --git a/pyowm/weatherapi25/observation.py b/pyowm/weatherapi25/observation.py
index 8631ed39..c6b1414f 100644
--- a/pyowm/weatherapi25/observation.py
+++ b/pyowm/weatherapi25/observation.py
@@ -71,12 +71,11 @@ def from_dict(cls, the_dict):
         # supposed to be deprecated as soon as the API fully adopts HTTP for
         # conveying errors to the clients
         if 'message' in the_dict and 'cod' in the_dict:
-            if the_dict['cod'] == "404":
-                print("OWM API: observation data not available")
-                return None
-            else:
+            if the_dict['cod'] != "404":
                 raise exceptions.APIResponseError(
                                       "OWM API: error - response payload", the_dict['cod'])
+            print("OWM API: observation data not available")
+            return None
         try:
             place = location.Location.from_dict(the_dict)
         except KeyError:
diff --git a/pyowm/weatherapi25/stationhistory.py b/pyowm/weatherapi25/stationhistory.py
index 64e2009d..b365deec 100644
--- a/pyowm/weatherapi25/stationhistory.py
+++ b/pyowm/weatherapi25/stationhistory.py
@@ -74,10 +74,9 @@ def from_dict(cls, d):
         # are found for a station and when the station does not exist!
         measurements = {}
         try:
-            if 'cod' in d:
-                if d['cod'] != "200":
-                    raise exceptions.APIResponseError(
-                                              "OWM API: error - response payload: " + str(d), d['cod'])
+            if 'cod' in d and d['cod'] != "200":
+                raise exceptions.APIResponseError(
+                                          "OWM API: error - response payload: " + str(d), d['cod'])
             if str(d['cnt']) == "0":
                 return None
             else:
diff --git a/pyowm/weatherapi25/weather.py b/pyowm/weatherapi25/weather.py
index d66ea17d..46b9f5bb 100644
--- a/pyowm/weatherapi25/weather.py
+++ b/pyowm/weatherapi25/weather.py
@@ -54,6 +54,8 @@ class Weather:
     :type utc_offset: int or None
     :param uvi: UV index
     :type uvi: int, float or None
+    :param precipitation_probability: Probability of precipitation (forecast only)
+    :type precipitation_probability: float or None
     :returns:  a *Weather* instance
     :raises: *ValueError* when negative values are provided for non-negative quantities
 
@@ -63,7 +65,7 @@ def __init__(self, reference_time, sunset_time, sunrise_time, clouds, rain,
                  snow, wind, humidity, pressure, temperature, status,
                  detailed_status, weather_code, weather_icon_name,
                  visibility_distance, dewpoint, humidex, heat_index,
-                 utc_offset=None, uvi=None):
+                 utc_offset=None, uvi=None, precipitation_probability=None):
         if reference_time < 0:
             raise ValueError("'reference_time' must be greater than 0")
         self.ref_time = reference_time
@@ -117,6 +119,12 @@ def __init__(self, reference_time, sunset_time, sunrise_time, clouds, rain,
             raise ValueError("'uvi' must be grater than or equal to 0")
         self.uvi = uvi
 
+        if precipitation_probability is not None and \
+           (precipitation_probability < 0.0 or precipitation_probability > 1.0):
+            raise ValueError("'precipitation_probability' must be between " \
+                             "0.0 and 1.0")
+        self.precipitation_probability = precipitation_probability
+        
     def reference_time(self, timeformat='unix'):
         """Returns the GMT time telling when the weather was measured
 
@@ -206,8 +214,8 @@ def temperature(self, unit='kelvin'):
         """
         # This is due to the fact that the OWM Weather API responses are mixing
         # absolute temperatures and temperature deltas together
-        to_be_converted = dict()
-        not_to_be_converted = dict()
+        to_be_converted = {}
+        not_to_be_converted = {}
         for label, temp in self.temp.items():
             if temp is None or temp < 0:
                 not_to_be_converted[label] = temp
@@ -217,6 +225,44 @@ def temperature(self, unit='kelvin'):
         return dict(list(converted.items()) +
                     list(not_to_be_converted.items()))
 
+    def barometric_pressure(self, unit='hPa'):
+        """
+        Returns a dict with pressure info
+
+        :param unit: the unit of measure for the temperature values. May be:
+            '*hPa' (default), '*inHg*'
+        :type unit: str
+        :returns: a dict containing pressure values.
+        :raises: ValueError when unknown pressure units are provided
+
+        """
+        if unit == 'hPa':
+            return self.pressure
+        elif unit == 'inHg':
+            return measurables.metric_pressure_dict_to_inhg(self.pressure)
+        else:
+            raise ValueError('Invalid value for target pressure unit')
+
+    def visibility(self, unit='meters'):
+        """
+        Returns a new value for visibility distance with specified unit
+
+        :param unit: the unit of measure for the temperature values. May be:
+            '*meters' (default), '*kilometers*', or '*miles*'
+        :type unit: str
+        :returns: a converted visibility distance value (float)
+        :raises: ValueError when unknown visibility units are provided
+
+        """
+        if unit == 'meters':
+            return self.visibility_distance
+        elif unit == 'kilometers':
+            return measurables.visibility_distance_to(self.visibility_distance, 'kilometers')
+        elif unit == 'miles':
+            return measurables.visibility_distance_to(self.visibility_distance, 'miles')
+        else:
+            raise ValueError('Invalid value for target visibility distance unit')
+
     def weather_icon_url(self, size=""):
         """Returns weather-related icon URL as a string.
 
@@ -306,7 +352,7 @@ def from_dict(cls, the_dict):
             elif 'distance' in the_dict['visibility']:
                 visibility_distance = the_dict['visibility']['distance']
         elif 'last' in the_dict and 'visibility' in the_dict['last']:
-            if isinstance(the_dict['last']['visibility'], int) or isinstance(the_dict['last']['visibility'], float):
+            if isinstance(the_dict['last']['visibility'], (int, float)):
                 visibility_distance = the_dict['last']['visibility']
             elif 'distance' in the_dict['last']['visibility']:
                 visibility_distance = the_dict['last']['visibility']['distance']
@@ -314,7 +360,7 @@ def from_dict(cls, the_dict):
         # -- clouds
         clouds = 0
         if 'clouds' in the_dict:
-            if isinstance(the_dict['clouds'], int) or isinstance(the_dict['clouds'], float):
+            if isinstance(the_dict['clouds'], (int, float)):
                 clouds = the_dict['clouds']
             elif 'all' in the_dict['clouds']:
                 clouds = the_dict['clouds']['all']
@@ -324,16 +370,16 @@ def from_dict(cls, the_dict):
             the_dict['rain'] = the_dict['precipitation']
 
         # -- rain
-        rain = dict()
+        rain = {}
         if 'rain' in the_dict:
-            if isinstance(the_dict['rain'], int) or isinstance(the_dict['rain'], float):
+            if isinstance(the_dict['rain'], (int, float)):
                 rain = {'all': the_dict['rain']}
             else:
                 if the_dict['rain'] is not None:
                     rain = the_dict['rain'].copy()
 
         # -- wind
-        wind = dict()
+        wind = {}
         if 'wind' in the_dict and the_dict['wind'] is not None:
             wind = the_dict['wind'].copy()
         elif 'last' in the_dict:
@@ -364,9 +410,9 @@ def from_dict(cls, the_dict):
             humidity = 0
 
         # -- snow
-        snow = dict()
+        snow = {}
         if 'snow' in the_dict:
-            if isinstance(the_dict['snow'], int) or isinstance(the_dict['snow'], float):
+            if isinstance(the_dict['snow'], (int, float)):
                 snow = {'all': the_dict['snow']}
             else:
                 if the_dict['snow'] is not None:
@@ -389,9 +435,9 @@ def from_dict(cls, the_dict):
         pressure = {'press': atm_press, 'sea_level': sea_level_press}
 
         # -- temperature
-        temperature = dict()
+        temperature = {}
         if 'temp' in the_dict:
-            if isinstance(the_dict['temp'], int) or isinstance(the_dict['temp'], float):
+            if isinstance(the_dict['temp'], (int, float)):
                 temperature = {
                     'temp': the_dict.get('temp', None)
                 }
@@ -413,7 +459,7 @@ def from_dict(cls, the_dict):
         # add feels_like to temperature if present
         if 'feels_like' in the_dict:
             feels_like = the_dict['feels_like']
-            if isinstance(feels_like, int) or isinstance(feels_like, float):
+            if isinstance(feels_like, (int, float)):
                 temperature['feels_like'] = the_dict.get('feels_like', None)
             elif isinstance(feels_like, dict):
                 for label, temp in feels_like.items():
@@ -432,19 +478,19 @@ def from_dict(cls, the_dict):
             weather_icon_name = ''
 
         # -- timezone
-        if 'timezone' in the_dict:
-            utc_offset = the_dict['timezone']
-        else:
-            utc_offset = None
-
+        utc_offset = the_dict['timezone'] if 'timezone' in the_dict else None
         # -- UV index
         uvi = the_dict.get('uvi', None)
 
+        # -- Precipitation probability
+        precipitation_probability = the_dict.get('pop', None)
+
         return Weather(reference_time, sunset_time, sunrise_time, clouds,
                        rain, snow, wind, humidity, pressure, temperature,
                        status, detailed_status, weather_code, weather_icon_name,
                        visibility_distance, dewpoint, humidex, heat_index,
-                       utc_offset=utc_offset, uvi=uvi)
+                       utc_offset=utc_offset, uvi=uvi,
+                       precipitation_probability=precipitation_probability)
 
     @classmethod
     def from_dict_of_lists(cls, the_dict):
@@ -513,4 +559,5 @@ def to_dict(self):
                 'humidex': self.humidex,
                 'heat_index': self.heat_index,
                 'utc_offset': self.utc_offset,
-                'uvi': self.uvi}
\ No newline at end of file
+                'uvi': self.uvi,
+                'precipitation_probability': self.precipitation_probability}
diff --git a/pyowm/weatherapi25/weather_manager.py b/pyowm/weatherapi25/weather_manager.py
index 3d19c6e9..03b04f0b 100644
--- a/pyowm/weatherapi25/weather_manager.py
+++ b/pyowm/weatherapi25/weather_manager.py
@@ -164,7 +164,7 @@ def weather_at_places(self, pattern, searchtype, limit=None):
         """
         assert isinstance(pattern, str), "'pattern' must be a str"
         assert isinstance(searchtype, str), "'searchtype' must be a str"
-        if searchtype != "accurate" and searchtype != "like":
+        if searchtype not in ["accurate", "like"]:
             raise ValueError("'searchtype' value must be 'accurate' or 'like'")
         if limit is not None:
             assert isinstance(limit, int), "'limit' must be an int or None"
@@ -524,9 +524,10 @@ def one_call(self, lat: Union[int, float], lon: Union[int, float], **kwargs) ->
         geo.assert_is_lat(lat)
         params = {'lon': lon, 'lat': lat}
         for key , value in kwargs.items():
-            if key == 'exclude': params['exclude'] = value
-            if key == 'units': params['units'] = value
-
+            if key == 'exclude':
+                params['exclude'] = value
+            elif key == 'units':
+                params['units'] = value
         _, json_data = self.http_client.get_json(ONE_CALL_URI, params=params)
         return one_call.OneCall.from_dict(json_data)
 
diff --git a/pyowm/weatherapi25/weathercoderegistry.py b/pyowm/weatherapi25/weathercoderegistry.py
index 78bc34ea..c3d615f9 100644
--- a/pyowm/weatherapi25/weathercoderegistry.py
+++ b/pyowm/weatherapi25/weathercoderegistry.py
@@ -87,7 +87,7 @@ def status_for(self, code):
         :type code: int
         :returns: the weather status str or ``None`` if the code is not mapped
         """
-        is_in = lambda start, end, n: True if start <= n <= end else False
+        is_in = lambda start, end, n: start <= n <= end
         for status in self._code_ranges_dict:
             for _range in self._code_ranges_dict[status]:
                 if is_in(_range['start'],_range['end'],code):
diff --git a/scripts/fill_entity_template.py b/scripts/fill_entity_template.py
index 38a2fe38..7c6460c1 100644
--- a/scripts/fill_entity_template.py
+++ b/scripts/fill_entity_template.py
@@ -11,10 +11,7 @@
 
     try:
         param_2 = sys.argv[2]
-        if param_2 == '--iterable':
-            is_iterable = True
-        else:
-            is_iterable = False
+        is_iterable = param_2 == '--iterable'
     except:
         is_iterable = False
 
diff --git a/scripts/generate_city_id_files.py b/scripts/generate_city_id_files.py
index 59646d06..3fa2fd9a 100644
--- a/scripts/generate_city_id_files.py
+++ b/scripts/generate_city_id_files.py
@@ -27,7 +27,7 @@ def download_the_files():
 
 def read_all_cities_into_dict():
     print('Reading city data from files ...')
-    all_cities = dict()
+    all_cities = {}
 
     # All cities
     with gzip.open(city_list_gz, "rb", "utf-8") as i:
@@ -76,21 +76,16 @@ def split_keyset(cities_dict):
             continue
         c = ord(name[0])
         if c < 97: # not a letter
-            continue
+            pass
         elif c in range(97, 103):  # from a to f
             ss[0].append(city_to_string(city_id, cities_dict[city_id]))
-            continue
         elif c in range(103, 109): # from g to l
             ss[1].append(city_to_string(city_id, cities_dict[city_id]))
-            continue
         elif c in range(109, 115): # from m to r
             ss[2].append(city_to_string(city_id, cities_dict[city_id]))
-            continue
         elif c in range (115, 123): # from s to z
             ss[3].append(city_to_string(city_id, cities_dict[city_id]))
-            continue
-        else:
-            continue # not a letter
+        continue
     print('... done')
     return ss
 
diff --git a/setup.py b/setup.py
index 7baf09f3..591ae528 100644
--- a/setup.py
+++ b/setup.py
@@ -4,6 +4,9 @@
 from pyowm.__version__ import __author__, __author_email__, __description__, __license__, __title__,\
     __url__, __version__
 
+with open('README.md', 'r') as readme:
+    long_description = readme.read()
+
 setup(
     name=__title__,
     version=__version__,
@@ -11,9 +14,9 @@
     author=__author__,
     author_email=__author_email__,
     url=__url__,
-    packages=find_packages(),
-    long_description="""PyOWM is a client Python wrapper library for OpenWeatherMap web APIs. It allows quick and easy 
-    consumption of OWM data from Python applications via a simple object model and in a human-friendly fashion.""",
+    packages=find_packages(exclude=['tests','tests.*']),
+    long_description=long_description,
+    long_description_content_type='text/markdown',
     include_package_data=True,
     install_requires=[
         'requests>=2.20.0,<3',
diff --git a/sphinx/contributing.md b/sphinx/contributing.md
index 13527a21..2df8bc94 100644
--- a/sphinx/contributing.md
+++ b/sphinx/contributing.md
@@ -1,6 +1,6 @@
 # Contributing
 
-Contributing is easy anwd welcome
+Contributing is easy and welcome
  
 You can contribute to PyOWM in a lot of ways:
 
@@ -8,7 +8,7 @@ You can contribute to PyOWM in a lot of ways:
   - make a wish for a reasonable new feature
   - increase the test coverage
   - refactor the code
-  - improve PyOWM reach on platforms (eg. bundle it for Linux distros, managers, oding, testing, packaging, reporting issues) are welcome!
+  - improve PyOWM reach on platforms (eg. bundle it for Linux distros, managers, coding, testing, packaging, reporting issues) are welcome!
   
 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).
 
@@ -26,9 +26,9 @@ That's simple: what you need to do is just open a new issue on GitHub.
 
 ## Bug reports - general principles
 In order to allow the community to understand what the bug is, *you should provide as much information as possible* on it.
-Vague or succint bug reports are not useful and will very likely result in follow ups needed.
+Vague or succinct bug reports are not useful and will very likely result in follow ups needed.
 
-*Only bugs related to PyOWM will be addressed*: it might be that you're using PyOWM in a broader context (eg. a webapplication)
+*Only bugs related to PyOWM will be addressed*: it might be that you're using PyOWM in a broader context (eg. a web application)
 so bugs affecting the broader context are out of scope - unless they are caused in chain to PyOWM issues.
 
 Also, please do understand that we can only act on *reproducible bugs*: this means that a bug does not exist if it is
@@ -74,7 +74,7 @@ just run:
 
 `pip install -r dev-requirements.txt`
 
-It is adviced that you do it on a [virtualenv](https://virtualenv.pypa.io/en/stable/).
+It is advised that you do it on a [virtualenv](https://virtualenv.pypa.io/en/stable/).
 
 ## Guidelines for code branching
 Simple ones:
diff --git a/sphinx/index.rst b/sphinx/index.rst
index bae9781f..2890f840 100644
--- a/sphinx/index.rst
+++ b/sphinx/index.rst
@@ -37,6 +37,7 @@ With PyOWM you can interact programmatically with the following OpenWeatherMap w
  - **UV Index API v3.0**, offering data about Ultraviolet exposition
  - **Stations API v3.0**, allowing to create and manage meteostation and publish local weather measurements
  - **Weather Alerts API v3.0**, allowing to set triggers on weather conditions and areas and poll for spawned alerts
+ - **Geocoding API v1.0** allowing to perform direct/reverse geocoding
 
 And you can also get **image tiles** for several map layers provided by OWM
 
@@ -183,6 +184,15 @@ Alerts API examples
 
    v3/alerts-api-usage-examples
 
+Geocoding API examples
+^^^^^^^^^^^^^^^^^^^^^^
+
+.. toctree::
+   :maxdepth: 1
+
+   v3/geocoding-api-usage-examples
+
+
 Map tiles client examples
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 .. toctree::
@@ -229,7 +239,7 @@ If you already have PyOWM 2.x installed and want to upgrade to safely update it
     $ pip install --upgrade pyowm>=2.0,<3.0
 
 
-Get the lastest development version
+Get the latest development version
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 You can install the development trunk with _pip_:
diff --git a/sphinx/pyowm.geocodingapi10.rst b/sphinx/pyowm.geocodingapi10.rst
new file mode 100644
index 00000000..b3810ae2
--- /dev/null
+++ b/sphinx/pyowm.geocodingapi10.rst
@@ -0,0 +1,13 @@
+pyowm.geocodingapi10 package
+============================
+
+Submodules
+----------
+
+pyowm.geocodingapi10.geocoding_manager module
+---------------------------------------------
+
+.. automodule:: pyowm.geocodingapi10.geocoding_manager
+    :members:
+    :undoc-members:
+    :show-inheritance:
diff --git a/sphinx/pyowm.rst b/sphinx/pyowm.rst
index 69857d0f..4ef217c3 100644
--- a/sphinx/pyowm.rst
+++ b/sphinx/pyowm.rst
@@ -10,6 +10,7 @@ Subpackages
     pyowm.alertapi30
     pyowm.commons
     pyowm.airpollutionapi30
+    pyowm.geocodingapi10
     pyowm.stationsapi30
     pyowm.tiles
     pyowm.utils
diff --git a/sphinx/v3/agro-api-usage-examples.md b/sphinx/v3/agro-api-usage-examples.md
index 9fb09d9d..b0f6674b 100644
--- a/sphinx/v3/agro-api-usage-examples.md
+++ b/sphinx/v3/agro-api-usage-examples.md
@@ -32,7 +32,7 @@ Read on to discover what you can do with it.
 
 A polygon represents an area on a map upon which you can issue data queries. Each polygon has a unique ID, an optional
 name and links back to unique OWM ID of the User that owns that polygon. Each polygon has an area that is expressed 
-in hacres, but you can also get it in squared kilometers:
+in acres, but you can also get it in squared kilometers:
 
 ```python
 pol                  # this is a pyowm.agro10.polygon.Polygon instance
@@ -133,7 +133,7 @@ Soil data is updated twice a day.
 
 This is the real meat in Agro API: the possibility to obtain **satellite imagery** right upon your polygons!
 
-### Overwiew
+### Overview
 
 Satellite Imagery comes in 3 formats:
   - **PNG images**
@@ -416,7 +416,7 @@ Currently only PNG and GEOTIFF imagery is available
 
 ### Supported color palettes
 Supported color palettes are provided by the `pyowm.agroapi10.enums.PaletteEnum` enumerator which returns strings,
-each one representing a color palette for NDVI imges:
+each one representing a color palette for NDVI images:
 
  ```python
 from pyowm.agroapi10.enums import PaletteEnum
diff --git a/sphinx/v3/air-pollution-api-usage-examples.md b/sphinx/v3/air-pollution-api-usage-examples.md
index 5f11e7d7..3c346f26 100644
--- a/sphinx/v3/air-pollution-api-usage-examples.md
+++ b/sphinx/v3/air-pollution-api-usage-examples.md
@@ -59,7 +59,7 @@ max_sample = coi.get_co_sample_with_highest_vmr()
 min_sample = coi.get_co_sample_with_lowest_vmr()
 ```
 
-If you want to know if a COIndex refers to the future - aka: is a forecast - wth respect to the
+If you want to know if a COIndex refers to the future - aka: is a forecast - with respect to the
 current timestamp, then use the `is_forecast()` method
 
 
@@ -119,9 +119,9 @@ oz.get_reference_time()
 oz.get_reception_time()
 ```
 
-If you want to know if an Ozone measurement refers to the future - aka: is a forecast - wth respect to the
+If you want to know if an Ozone measurement refers to the future - aka: is a forecast - with respect to the
 current timestamp, then use the `is_forecast()` method
 
 
 ### Querying Nitrogen dioxide (NO2) and Sulfur Dioxide (SO2) data
-This works exactly as for O2 adata - please refer to that bit of the docs
\ No newline at end of file
+This works exactly as for O2 data - please refer to that bit of the docs
\ No newline at end of file
diff --git a/sphinx/v3/alerts-api-usage-examples.md b/sphinx/v3/alerts-api-usage-examples.md
index 3f5af6ee..fe57edd5 100644
--- a/sphinx/v3/alerts-api-usage-examples.md
+++ b/sphinx/v3/alerts-api-usage-examples.md
@@ -89,7 +89,7 @@ am.delete_alert_for(trigger, alert)
 This is the Alert API object model:
 
   - *Trigger*: collection of alerts to be met over specified areas and within a specified time frame according to specified weather params conditions
-  - *Condition*: rule for matching a weather measuerment with a specified threshold
+  - *Condition*: rule for matching a weather measurement with a specified threshold
   - *Alert*: whenever a condition is met, an alert is created (or updated) and can be polled to verify when it has been met and what the actual weather param value was.
   - *Area*: geographic area over which the trigger is checked
   - *AlertChannel*: as OWM plans to add push-oriented alert channels (eg. push notifications), we need to encapsulate this into a specific class
@@ -283,8 +283,8 @@ A Trigger is the local proxy for the corresponding entry on the OWM API: Trigger
 `pyowm.alertapi30.alertmanager.AlertManager` instances.
 
 Each Trigger has these attributes:
-  - start_after_millis: _with resepect to the time when the trigger will be crated on the Alert API_, how many milliseconds after should it begin to be checked for conditions matching
-  - end_after_millis: _with resepect to the time when the trigger will be crated on the Alert API_, how many milliseconds after should it end to be checked for conditions matching
+  - start_after_millis: _with respect to the time when the trigger will be created on the Alert API_, how many milliseconds after should it begin to be checked for conditions matching
+  - end_after_millis: _with respect to the time when the trigger will be created on the Alert API_, how many milliseconds after should it end to be checked for conditions matching
   - alerts: a list of `pyowm.alertapi30.alert.Alert` instances, which are the alerts that the trigger has fired so far
   - conditions: a list of `pyowm.alertapi30.condition.Condition` instances
   - area: a list of `pyowm.utils.geo.Geometry` instances, representing the geographic area on which the trigger's conditions need to be checked
diff --git a/sphinx/v3/city-id-registry-examples.md b/sphinx/v3/city-id-registry-examples.md
index 047dab73..dd919334 100644
--- a/sphinx/v3/city-id-registry-examples.md
+++ b/sphinx/v3/city-id-registry-examples.md
@@ -2,6 +2,6 @@
 
 Using city IDS instead of toponyms or geographic coordinates is the preferred way of querying the OWM weather API
  
-You can obtain the city ID for your toponyms/geoocoords of interest via the `City ID Registry`.
+You can obtain the city ID for your toponyms/geocoords of interest via the `City ID Registry`.
 
 Please refer to the `Code Recipes` page, section: `Identifying cities and places via city IDs`, to get info about it
\ No newline at end of file
diff --git a/sphinx/v3/code-recipes.md b/sphinx/v3/code-recipes.md
index b217f329..0a4dec25 100644
--- a/sphinx/v3/code-recipes.md
+++ b/sphinx/v3/code-recipes.md
@@ -8,6 +8,7 @@ Table of contents:
   * [Identifying cities and places via city IDs](#identifying_places)
   * [OneCall data](#onecall)
   * [Weather data](#weather_data)
+  * [Air pollution data](#airpollution_data)  
   * [Weather forecasts](#weather_forecasts)
   * [Meteostation historic measurements](#station_measurements)
 
@@ -87,6 +88,10 @@ owm = OWM('your-api-key', config_dict)
 ```
 
 ### Language setting
+The OWM API can be asked to return localized *detailed statuses* for weather data
+In PyOWM this means that you can specify a language and you'll retrieve `Weather` objects having the `detailed_status`
+field localized in that language. Localization is not provided for `status` field instead, so pay attention to that. 
+
 The list of supported languages is given by:
 ```python
 from pyowm.owm import OWM
@@ -101,8 +106,11 @@ English is the default language on the OWM API - but you can change it:
 from pyowm.owm import OWM
 from pyowm.utils.config import get_default_config
 config_dict = get_default_config()
-config_dict['language'] = 'pt'  # your language here, eg. Portuguese
+config_dict['language'] = 'fr'  # your language here, eg. French
 owm = OWM('your-api-key', config_dict)
+mgr = owm.weather_manager()
+observation = mgr.weather_at_place('Paris, FR')
+observation.weather.detailed_status  # Nuageux
 ```
 
 ### Get PyOWM configuration
@@ -122,9 +130,16 @@ version_tuple = (major, minor, patch) = owm.version
 
 <div id="identifying_places"/>
 
-## Identifying cities and places via city IDs
+## Identifying cities and places
+
+You can easily get the City ID of a known toponym, as well as its geographic coordinates
+Also you can leverage direct/reverse geocoding 
 
-### Obtain the city ID registry
+### City IDs
+
+The following calls will not result in any OWM API call in the background, so they will only happen locally to your machine.
+
+#### Obtain the city ID registry
 Use the city ID registry to lookup the ID of a city given its name
 ```python
 from pyowm.owm import OWM
@@ -132,8 +147,8 @@ owm = OWM('your-api-key')
 city_id_registry = owm.city_id_registry()
 ```
 
-### Get the ID of a city given its name
-Don't forget that there is a high probabilty that your city is not unique in the world, and multiple cities with the same name exist in other countries
+#### Get the ID of a city given its name
+Don't forget that there is a high probability that your city is not unique in the world, and multiple cities with the same name exist in other countries
 Therefore specify toponyms and country 2-letter names separated by comma. Eg: if you search for the British `London` you'll likely multiple results: 
 you then should also specify the country (`GB`) to narrow the search only to Great Britain.
 
@@ -157,9 +172,8 @@ list_of_tuples = london = reg.ids_for('LoNdoN', country='GB')                 #
 and would get the very same results as above.
 
 
-### Get the IDs of cities whose name contain a specific string
-
-In order yo find all cities with names having your string as a substring you need to use the optional parameter `matching='like'`
+#### Get the IDs of cities whose name contain a specific string
+In order to find all cities with names having your string as a substring you need to use the optional parameter `matching='like'`
 
 In example, let's find IDs for all British cities having the string `london` in their names:
 
@@ -174,7 +188,7 @@ list_of_tuples = reg.ids_for('london', country='GB', matching='like')  # We'll g
                                                                        #            (2643734, 'Londonderry County Borough', 'GB')]
 ```
 
-### Get geographic coordinates of a city given its name
+#### Get geographic coordinates of a city given its name
 Just use call `locations_for` on the registry: this will give you a `Location` object containing lat & lon
 
 Let's find geocoords for Moscow (Russia):
@@ -189,7 +203,7 @@ lat = moscow.lat   # 55.75222
 lon = moscow.lon   # 37.615555
 ```
 
-### Get GeoJSON geometry (point) for a city given its name
+#### Get GeoJSON geometry (point) for a city given its name
 PyOWM encapsulates [GeoJSON](https://pypi.org/project/geojson/) geometry objects that are compliant with the GeoJSON specification.
 
 This means, for example, that you can get a `Point` geometry using the registry. Let's find the geometries for all `Rome` cities in the world:
@@ -201,6 +215,65 @@ reg = owm.city_id_registry()
 list_of_geopoints = reg.geopoints_for('rome')
 ```
 
+### Direct/reverse geocoding
+
+Simply put:
+  - DIRECT GEOCODING: from toponym to geocoords
+  - REVERSE GEOCODING: from geocoords to toponyms
+
+
+Both geocoding actions are performed via a `geocoding_manager` object and will require an actual call to be made to the
+OWM API: so please bear that in mind because that will count against your amount of allowed API calls
+
+#### Direct geocoding of a toponym
+
+The call is very similar to `ids_for` and `locations_for`.
+
+You at least need to specify the toponym name and country ISO code (eg. `GB`, `IT`, `JP`, ...), while if the input 
+toponym is in the United States you should also specify the `state_code` parameter 
+
+The call returns a list of `Location` object instances (in case of no ambiguity, only one item in the list will be returned)
+You can then get the lat/lon from the object instances themselves
+
+Results can be limited with the `limit` parameter
+
+```python
+from pyowm.owm import OWM
+owm = OWM('your-api-key')
+mgr = owm.geocoding_manager()
+
+# geocode London (no country specified) - we'll get many results
+list_of_locations = mgr.geocode('London')
+a_london = list_of_locations[0]  # taking the first London in the list
+a_london.lat
+a_london.lon 
+
+# geocode London (Great Britain) - we'll get up to three Londons that exist in GB
+list_of_locations = mgr.geocode('London', country='GB', limit=3)
+
+# geocode London (Ohio, United States of America): we'll get all the Londons in Ohio
+list_of_locations = mgr.geocode('London', country='US', state_code='OH')
+```
+
+#### Reverse geocoding of geocoordinates
+With reverse geocoding you input a lat/lon float couple and retrieve a list all the `Location` objects associated with 
+these coordinates.
+
+Results can be limited with the `limit` parameter
+
+```python
+from pyowm.owm import OWM
+owm = OWM('your-api-key')
+mgr = owm.geocoding_manager()
+
+# London
+lat = 51.5098
+lon = -0.1180
+
+# reverse geocode London
+list_of_locations = mgr.reverse_geocode(lat, lon)  # list contains: City of London, Islington, Lewisham, ...
+```
+
 
 <div id="onecall"/>
 
@@ -208,7 +281,7 @@ list_of_geopoints = reg.geopoints_for('rome')
 
 With the OneCall Api you can get the current weather, hourly forecast for the next 48 hours and the daily forecast for the next seven days in one call.
 
-One Call objects can be thought of as datasets that "photograhp" of observed and forecasted weather data for a location: such photos are given for a specific timestamp.
+One Call objects can be thought of as datasets that "photograph" of observed and forecasted weather data for a location: such photos are given for a specific timestamp.
 
 It is possible to get:
   - current OneCall data: the "photo" given for today)
@@ -272,7 +345,7 @@ one_call = mgr.one_call(lat=52.5244, lon=13.4105, exclude='minutely,hourly', uni
 # the various units for the different options are shown here: https://openweathermap.org/weather-data
 one_call.current.temperature() # Eg.: 74.07 (deg F)
 
-# the example above does not retrieve minutely or hourly data, so it will not be availabe in the one_call object
+# the example above does not retrieve minutely or hourly data, so it will not be available in the one_call object
 # available exclude options are defined by the One Call API
 # BUT using 'current' will error, as the pyowm one_call requires it
 # as of 2020.08.07 available values are: 'minutely', 'hourly', 'daily'
@@ -403,22 +476,59 @@ Also rain amount is a dict, with keys: `1h` an `3h`, containing the mms of rain
 from pyowm.owm import OWM
 owm = OWM('your-api-key')
 mgr = owm.weather_manager()
-rain_dict = mgr.weather_at_place('Berlin,DE').observation.rain
+rain_dict = mgr.weather_at_place('Berlin,DE').weather.rain
 rain_dict['1h']
 rain_dict['3h']
 ```
 
 ### Get current pressure on a location
-Pressure is similar to rain: you get a dict with keys: `press` (atmospheric pressure on the ground in hPa) and `sea_level` 
-(on the sea level, if location is on the sea)
+Pressure is similar to rain, you get a dict with hPa values and these keys: `press` (atmospheric pressure on the 
+ground, sea level if [no sea level or ground level data](https://openweathermap.org/weather-data)) `sea_level` 
+(on the sea level, if location is on the sea) and `grnd_level`. Note that `press` used below refers to the
+dict value in 
 
 ```python
 from pyowm.owm import OWM
 owm = OWM('your-api-key')
 mgr = owm.weather_manager()
-pressure_dict = mgr.weather_at_place('Berlin,DE').observation.pressure
+pressure_dict = mgr.weather_at_place('Berlin,DE').weather.barometric_pressure()
 pressure_dict['press']
 pressure_dict['sea_level']
+pressure_dict['grnd_level']
+```
+
+Pressure values are given in the metric hPa, or hectopascals (1 hPa is equivalent to 100 pascals). You can easily 
+convert these values to inches of mercury, or inHg, which is a unit commonly used in the United States. Similar to above,
+we can do:
+
+```python
+from pyowm.owm import OWM
+owm = OWM('your-api-key')
+mgr = owm.weather_manager()
+obs = mgr.weather_at_place('Berlin,DE')
+
+# the default unit is hPa
+pressure_dict_unspecified = obs.weather.barometric_pressure()
+pressure_dict_in_hg = obs.weather.barometric_pressure(unit='inHg')
+```
+
+### Get current visibility distance on a location
+You might want to know how clearly you can see objects in Berlin. This is the visibility distance, an average distance
+taken from an Observation object and given in meters. You can also convert this value to kilometers or miles.
+
+```python
+from pyowm.owm import OWM
+
+owm = OWM('your-api-key')
+mgr = owm.weather_manager()
+obs = mgr.weather_at_place('Berlin,DE')
+
+# the default value provided by our call (in meters)
+visibility = obs.weather.visibility_distance
+
+# kilometers is the default conversion unit
+visibility_in_kms = obs.weather.visibility()
+visibility_in_miles = obs.weather.visibility(unit='miles')
 ```
 
 ### Get today's sunrise and sunset times for a location
@@ -552,6 +662,11 @@ obs_list = mgr.weather_at_places_in_bbox(lon_left, lat_bottom, lon_right, lat_to
 
 ## Weather forecasts
 
+**>>>IMPORTANT NOTE<<<**: OpenWeatherMap has deprecated legacy weather forecasts endpoints, therefore you could get
+errors if you invoke them
+The recommended way to get weather forecasts is now the [*OneCall* API]((#onecall))
+
+
 ### Get forecast on a location
 Just like for observed weather info, you can fetch weather forecast info on a specific toponym.
 As usual, provide toponym + country code for better results.
@@ -710,6 +825,89 @@ TBD
 TBD
 
 
+<div id="airpollution_data"/>
+
+## Air pollution data
+
+Instead of getting a `weather_manager`, get from the main OWM object a `airpollution_manager` and use it
+
+### Getting air pollution concentrations and Air Quality Index on geographic coords 
+Air polluting agents concentration can be queried in one shot:
+
+```python
+from pyowm.owm import OWM
+owm = OWM('your-api-key')
+mgr = owm.airpollution_manager()
+
+air_status = mgr.air_quality_at_coords(51.507351, -0.127758)  # London, GB
+
+# you can then get values for all of these air pollutants
+air_status.co
+air_status.no
+air_status.no2
+air_status.o3
+air_status.so2
+air_status.pm2_5
+air_status.pm10
+air_status.nh3
+
+# and for air quality index
+air_status.aqi 
+```
+
+### Getting forecasts for air pollution on geographic coords 
+We can get also get forecasts for air pollution agents concentration and air quality index:
+
+```python
+from pyowm.owm import OWM
+owm = OWM('your-api-key')
+mgr = owm.airpollution_manager()
+
+list_of_forecasts = mgr.air_quality_forecast_at_coords(51.507351, -0.127758)  # London, GB
+
+# Each item in the list_of_forecasts is an AirStatus object 
+for air_status in list_of_forecasts:
+    air_status.co
+    air_status.no
+    air_status.no2
+    air_status.o3
+    air_status.so2
+    air_status.pm2_5
+    air_status.pm10
+    air_status.nh3
+    air_status.aqi  # air quality index
+```
+
+### Getting historical air pollution data on geographic coords 
+We can get also get historical values for air pollution agents concentration and air quality index:
+
+```python
+from pyowm.owm import OWM
+owm = OWM('your-api-key')
+mgr = owm.airpollution_manager()
+
+# fetch history from a certain point in time up to now...
+start = 1606223802  # November 24, 2020
+list_of_historical_values = mgr.air_quality_history_at_coords(51.507351, -0.127758, start)  # London, GB
+
+# ...or fetch history on a closed timeframe in the past
+end = 1613864065  # February 20, 2021
+list_of_historical_values = mgr.air_quality_history_at_coords(51.507351, -0.127758, start, end=end)  # London, GB
+
+# Each item in the list_of_historical_values is an AirStatus object 
+for air_status in list_of_historical_values:
+    air_status.co
+    air_status.no
+    air_status.no2
+    air_status.o3
+    air_status.so2
+    air_status.pm2_5
+    air_status.pm10
+    air_status.nh3
+    air_status.aqi  # air quality index
+```
+
+
 
 <div id="station_measurements"/>
 
diff --git a/sphinx/v3/faq.md b/sphinx/v3/faq.md
index 1ad9c86d..54998142 100644
--- a/sphinx/v3/faq.md
+++ b/sphinx/v3/faq.md
@@ -30,7 +30,7 @@ As stated in the documentation home page, OpenWeatherMap API recently "blocked"
 
 This means that PyOWM might return authorization errors in that case.
 
-This behaviour is not showing if you use API keys issued time ago - unfortunately I have no wasy to be more precise as OWM never stated this officially.
+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.
 
 **The proper way to obtain the data you are looking for is to call the "OneCall" PyOWM methods using your API key**
 
diff --git a/sphinx/v3/geocoding-api-usage-examples.md b/sphinx/v3/geocoding-api-usage-examples.md
new file mode 100644
index 00000000..b82b1b44
--- /dev/null
+++ b/sphinx/v3/geocoding-api-usage-examples.md
@@ -0,0 +1,8 @@
+# Geocoding API usage examples
+
+The OWM Weather API gives you the possibility to perform direct and reverse geocoding:
+  - DIRECT GEOCODING: from toponym to geocoords
+  - REVERSE GEOCODING: from geocoords to toponyms
+
+
+Please refer to the `Code Recipes` page, sections: `Direct/reverse geocoding`
diff --git a/sphinx/v3/global-pyowm-usage-examples.md b/sphinx/v3/global-pyowm-usage-examples.md
index 80025ee7..18f33a00 100644
--- a/sphinx/v3/global-pyowm-usage-examples.md
+++ b/sphinx/v3/global-pyowm-usage-examples.md
@@ -18,7 +18,7 @@ weather = mgr.weather_at_place('London,GB').weather  # get the weather at London
 dump_dict = weather.to_dict()
 ```
 
-This is useful as you can save the dump dictionaries to files (eg. using Pyhon `json` or `pickle` modules)
+This is useful as you can save the dump dictionaries to files (eg. using Python `json` or `pickle` modules)
 
 ## Printing objects
 Most of PyOWM objects can be pretty-printed for a quick introspection:
diff --git a/sphinx/v3/stations-api-usage-examples.md b/sphinx/v3/stations-api-usage-examples.md
index d33a8434..9fc715ff 100644
--- a/sphinx/v3/stations-api-usage-examples.md
+++ b/sphinx/v3/stations-api-usage-examples.md
@@ -46,7 +46,7 @@ datapoints that you query against the API come in the form of:
  `stationsapi30.measurement.AggregatedMeasurement` objects.
 
 
-Each `stationsapi30.measurement.Measurement` cointains a reference to the
+Each `stationsapi30.measurement.Measurement` contains a reference to the
 `Station` it belongs to:
 
 ```python
diff --git a/sphinx/v3/utilities-usage-examples.md b/sphinx/v3/utilities-usage-examples.md
index 41c233f1..d3baafb5 100644
--- a/sphinx/v3/utilities-usage-examples.md
+++ b/sphinx/v3/utilities-usage-examples.md
@@ -223,6 +223,31 @@ knots_wind_dict = measurables.metric_wind_dict_to_knots(msec_wind_dict)
 beaufort_wind_dict = measurables.metric_wind_dict_to_beaufort(msec_wind_dict)
 ```
 
+### Pressure
+OWM gives barometric pressure in hPa values, in a 
+[dict of three pressure items](https://openweathermap.org/weather-data). You can convert these to inHg, which is a 
+common unit of measurement in the United States.
+
+```python
+from pyowm.utils import measurables
+
+hpa_pressure_dict = {'press': 1000, 'sea_level': 1000, 'grnd_level': 1000}
+inhg_pressure_dict = measurables.metric_pressure_dict_to_inhg(hpa_pressure_dict)
+```
+
+### Visibility
+A typical API response contains a single visibility distance value. This is described as the average visibility in
+meters. You can convert this value (from meters) using the function provided to either kms or miles.
+
+```python
+from pyowm.utils import measurables
+
+visibility = 1000
+
+# the default return value is in kilometers
+visibility_kms = measurables.visibility_distance_to(visibility)
+visibility_miles = measurables.visibility_distance_to(visibility, 'miles')
+```
 
 ## `timestamps` module
 
diff --git a/tox.ini b/tox.ini
index 3729ee3d..98bb3e48 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 envlist =
-    py37, py38, coverage
+    py37, py38, py39, coverage
 skip_missing_interpreters =
     True
 


Test patch:
diff --git a/tests/integration/airpollutionapi30/__init__.py b/tests/integration/airpollutionapi30/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/integration/airpollutionapi30/test_integration_pollutionapi30.py b/tests/integration/airpollutionapi30/test_integration_pollutionapi30.py
new file mode 100644
index 00000000..3c499ec6
--- /dev/null
+++ b/tests/integration/airpollutionapi30/test_integration_pollutionapi30.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import unittest
+import os
+from pyowm import owm
+
+
+class IntegrationTestsPollutionAPI30(unittest.TestCase):
+
+    __owm = owm.OWM(os.getenv('OWM_API_KEY', None)).airpollution_manager()
+
+    def test_air_quality_at_coords(self):
+        """
+        Test feature: get all air quality data around geo-coordinates.
+        """
+        airstatus = self.__owm.air_quality_at_coords(45, 9)
+        self.assertIsNotNone(airstatus)
+        self.assertIsNotNone(airstatus.air_quality_data)
+        self.assertIsNotNone(airstatus.reception_time())
+        self.assertIsNotNone(airstatus.reference_time())
+        self.assertIsNotNone(airstatus.location)
+
+    def test_air_quality_forecast_at_coords(self):
+        """
+        Test feature: get all forecasted air quality data around geo-coordinates.
+        """
+        list_of_airstatuses = self.__owm.air_quality_forecast_at_coords(45, 9)
+        self.assertTrue(list_of_airstatuses)
+        for airstatus in list_of_airstatuses:
+            self.assertIsNotNone(airstatus.air_quality_data)
+            self.assertIsNotNone(airstatus.reception_time())
+            self.assertIsNotNone(airstatus.reference_time())
+            self.assertIsNotNone(airstatus.location)
+
+    def test_air_quality_history_at_coords(self):
+        """
+        Test feature: get historical air quality data around geo-coordinates.
+        """
+        start = 1606223802  # Tuesday, November 24, 2020
+
+        list_of_airstatuses = self.__owm.air_quality_history_at_coords(45, 9, start)
+        self.assertIsInstance(list_of_airstatuses, list)
+        for airstatus in list_of_airstatuses:
+            self.assertIsNotNone(airstatus.air_quality_data)
+            self.assertIsNotNone(airstatus.reception_time())
+            self.assertIsNotNone(airstatus.reference_time())
+            self.assertIsNotNone(airstatus.location)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/integration/geocodingapi10/__init__.py b/tests/integration/geocodingapi10/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/integration/geocodingapi10/test_integration_geocodingapi10.py b/tests/integration/geocodingapi10/test_integration_geocodingapi10.py
new file mode 100644
index 00000000..5e12d93d
--- /dev/null
+++ b/tests/integration/geocodingapi10/test_integration_geocodingapi10.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import unittest
+import os
+from pyowm import owm
+from pyowm.weatherapi25.location import Location
+
+
+class IntegrationTestsGeocodingAPI(unittest.TestCase):
+
+    __owm = owm.OWM(os.getenv('OWM_API_KEY', None))
+
+    def test_geocode(self):
+        mgr = self.__owm.geocoding_manager()
+
+        # Geocode all Paris in the United States
+        locations = mgr.geocode('Paris', 'US')
+        self.assertTrue(isinstance(locations, list))
+        self.assertTrue(all([isinstance(l, Location) for l in locations]))
+        self.assertTrue(all([l.name == 'Paris' and l.country == 'US' for l in locations]))
+
+    def test_reverse_geocode(self):
+        mgr = self.__owm.geocoding_manager()
+
+        # Reverse geocode the geocoords for Florence (Italy)
+        locations = mgr.reverse_geocode(43.783731, 11.246603)
+        self.assertTrue(isinstance(locations, list))
+        self.assertTrue(all([isinstance(l, Location) for l in locations]))
+        self.assertTrue(all([l.name == 'Firenze' and l.country == 'IT' for l in locations]))
+
diff --git a/tests/integration/pollutionapi30/test_integration_pollutionapi30.py b/tests/integration/pollutionapi30/test_integration_pollutionapi30.py
deleted file mode 100644
index e1163501..00000000
--- a/tests/integration/pollutionapi30/test_integration_pollutionapi30.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import unittest
-import os
-from pyowm import owm
-
-
-class IntegrationTestsPollutionAPI30(unittest.TestCase):
-
-    __owm = owm.OWM(os.getenv('OWM_API_KEY', None)).airpollution_manager()
-
-    def test_coindex_around_coords(self):
-        """
-        Test feature: get CO index around geo-coordinates.
-        """
-        u = self.__owm.coindex_around_coords(45, 9)
-        self.assertIsNotNone(u)
-        self.assertIsNotNone(u.co_samples)
-        self.assertIsNotNone(u.reception_time())
-        self.assertIsNotNone(u.reference_time())
-        self.assertIsNone(u.interval)
-        self.assertIsNotNone(u.location)
-
-    def test_ozone_around_coords(self):
-        """
-        Test feature: get ozone around geo-coordinates.
-        """
-        u = self.__owm.ozone_around_coords(0.0, 10.0, start='2016-12-31 12:55:55+00:00')
-        self.assertIsNotNone(u)
-        self.assertIsNotNone(u.du_value)
-        self.assertIsNotNone(u.reception_time())
-        self.assertIsNotNone(u.reference_time())
-        self.assertIsNone(u.interval)
-        self.assertIsNotNone(u.location)
-
-    def test_no2index_around_coords(self):
-        """
-        Test feature: get NO2 index around geo-coordinates.
-        """
-        u = self.__owm.no2index_around_coords(0.0, 10.0, start='2016-12-31 12:55:55+00:00')
-        self.assertIsNotNone(u)
-        self.assertIsNotNone(u.no2_samples)
-        self.assertIsNotNone(u.reception_time())
-        self.assertIsNotNone(u.reference_time())
-        self.assertIsNone(u.interval)
-        self.assertIsNotNone(u.location)
-
-    def test_so2index_around_coords(self):
-        """
-        Test feature: get SO2 index around geo-coordinates.
-        """
-        u = self.__owm.so2index_around_coords(0.0, 10.0, start='2016-12-31 12:55:55+00:00')
-        self.assertIsNotNone(u)
-        self.assertIsNotNone(u.so2_samples)
-        self.assertIsNotNone(u.reception_time())
-        self.assertIsNotNone(u.reference_time())
-        self.assertIsNone(u.interval)
-        self.assertIsNotNone(u.location)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/unit/airpollutionapi30/test_airpollution_manager.py b/tests/unit/airpollutionapi30/test_airpollution_manager.py
index ba4c6e0c..d32c0d10 100644
--- a/tests/unit/airpollutionapi30/test_airpollution_manager.py
+++ b/tests/unit/airpollutionapi30/test_airpollution_manager.py
@@ -3,13 +3,15 @@
 
 import json
 import unittest
-from pyowm.airpollutionapi30 import airpollution_client, airpollution_manager, coindex, so2index, ozone, no2index
+from pyowm.airpollutionapi30 import airpollution_client, airpollution_manager, coindex, so2index, ozone, no2index, airstatus
 from pyowm.config import DEFAULT_CONFIG
 from pyowm.constants import AIRPOLLUTION_API_VERSION
+from pyowm.utils import timestamps
 from tests.unit.airpollutionapi30.test_ozone import OZONE_JSON
 from tests.unit.airpollutionapi30.test_coindex import COINDEX_JSON
 from tests.unit.airpollutionapi30.test_no2index import NO2INDEX_JSON
 from tests.unit.airpollutionapi30.test_so2index import SO2INDEX_JSON
+from tests.unit.airpollutionapi30.test_airstatus import AIRSTATUS_JSON, AIRSTATUS_MULTIPLE_JSON
 
 
 class TestAirPollutionManager(unittest.TestCase):
@@ -25,6 +27,15 @@ def mock_get_o3_returning_ozone_around_coords(self, params_dict):
     def mock_get_no2_returning_no2index_around_coords(self, params_dict):
         return json.loads(NO2INDEX_JSON)
 
+    def mock_get_air_pollution(self, params_dict):
+        return json.loads(AIRSTATUS_JSON)
+
+    def mock_get_forecast_air_pollution(self, params_dict):
+        return json.loads(AIRSTATUS_MULTIPLE_JSON)
+
+    def mock_get_historical_air_pollution(self, params_dict):
+        return json.loads(AIRSTATUS_MULTIPLE_JSON)
+
     def mock_get_so2_returning_so2index_around_coords(self, params_dict):
         return json.loads(SO2INDEX_JSON)
 
@@ -170,5 +181,98 @@ def test_so2index_around_coords_fails_with_wrong_parameters(self):
         self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.so2index_around_coords, \
                           self.__test_instance, 200, 2.5)
 
+    def test_air_quality_at_coords(self):
+        ref_to_original = airpollution_client.AirPollutionHttpClient.get_air_pollution
+        airpollution_client.AirPollutionHttpClient.get_air_pollution = \
+            self.mock_get_air_pollution
+        result = self.__test_instance.air_quality_at_coords(45, 9)
+        airpollution_client.AirPollutionHttpClient.get_air_pollution = ref_to_original
+        self.assertTrue(isinstance(result, airstatus.AirStatus))
+        self.assertIsNotNone(result.reference_time)
+        self.assertIsNotNone(result.reception_time())
+        loc = result.location
+        self.assertIsNotNone(loc)
+        self.assertIsNotNone(loc.lat)
+        self.assertIsNotNone(loc.lon)
+        self.assertIsNotNone(result.air_quality_data)
+
+    def test_air_quality_at_coords_fails_with_wrong_parameters(self):
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_at_coords, \
+                          self.__test_instance, 43.7, -200.0)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_at_coords, \
+                          self.__test_instance, 43.7, 200.0)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_at_coords, \
+                          self.__test_instance, -200, 2.5)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_at_coords, \
+                          self.__test_instance, 200, 2.5)
+
+    def test_air_quality_forecast_at_coords(self):
+        ref_to_original = airpollution_client.AirPollutionHttpClient.get_forecast_air_pollution
+        airpollution_client.AirPollutionHttpClient.get_forecast_air_pollution = \
+            self.mock_get_forecast_air_pollution
+        result = self.__test_instance.air_quality_forecast_at_coords(45, 9)
+        airpollution_client.AirPollutionHttpClient.get_forecast_air_pollution = ref_to_original
+        self.assertTrue(isinstance(result, list))
+        for item in result:
+            self.assertIsInstance(item, airstatus.AirStatus)
+            self.assertIsNotNone(item.reference_time)
+            self.assertIsNotNone(item.reception_time())
+            loc = item.location
+            self.assertIsNotNone(loc)
+            self.assertIsNotNone(loc.lat)
+            self.assertIsNotNone(loc.lon)
+            self.assertIsNotNone(item.air_quality_data)
+
+    def test_air_quality_forecast_at_coords_fails_with_wrong_parameters(self):
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_forecast_at_coords, \
+                          self.__test_instance, 43.7, -200.0)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_forecast_at_coords, \
+                          self.__test_instance, 43.7, 200.0)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_forecast_at_coords, \
+                          self.__test_instance, -200, 2.5)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_forecast_at_coords, \
+                          self.__test_instance, 200, 2.5)
+
+    def test_air_quality_history_at_coords(self):
+        ref_to_original = airpollution_client.AirPollutionHttpClient.get_historical_air_pollution
+        airpollution_client.AirPollutionHttpClient.get_historical_air_pollution = \
+            self.mock_get_historical_air_pollution
+        result = self.__test_instance.air_quality_history_at_coords(45, 9, 12345678)
+        airpollution_client.AirPollutionHttpClient.get_historical_air_pollution = ref_to_original
+        self.assertTrue(isinstance(result, list))
+        for item in result:
+            self.assertIsInstance(item, airstatus.AirStatus)
+            self.assertIsNotNone(item.reference_time)
+            self.assertIsNotNone(item.reception_time())
+            loc = item.location
+            self.assertIsNotNone(loc)
+            self.assertIsNotNone(loc.lat)
+            self.assertIsNotNone(loc.lon)
+            self.assertIsNotNone(item.air_quality_data)
+
+    def test_air_quality_history_at_coords_fails_with_wrong_parameters(self):
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_history_at_coords, \
+                          self.__test_instance, 43.7, -200.0, 12345678, 12349999)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_history_at_coords, \
+                          self.__test_instance, 43.7, 200.0, 12345678, 12349999)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_history_at_coords, \
+                          self.__test_instance, -200, 2.5, 12345678, 12349999)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_history_at_coords, \
+                          self.__test_instance, 200, 2.5, 12345678, 12349999)
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_history_at_coords, \
+                          self.__test_instance, 200, 2.5, 'test')
+        self.assertRaises(ValueError, airpollution_manager.AirPollutionManager.air_quality_history_at_coords, \
+                          self.__test_instance, 200, 2.5, 'test', 'test2')
+
+    def test_air_quality_history_at_coords_clips_end_param_to_current_timestamp(self):
+        now = timestamps.now(timeformat='unix')
+        end = now + 99999999999
+
+        def assert_clipped(obj, params_dict):
+            self.assertEqual(params_dict['end'], now)
+
+        airpollution_client.AirPollutionHttpClient.get_historical_air_pollution = assert_clipped
+        _ = self.__test_instance.air_quality_history_at_coords(45, 9, 12345678, end=end)
+
     def test_repr(self):
         print(self.__test_instance)
diff --git a/tests/unit/airpollutionapi30/test_airstatus.py b/tests/unit/airpollutionapi30/test_airstatus.py
new file mode 100644
index 00000000..700ee6bd
--- /dev/null
+++ b/tests/unit/airpollutionapi30/test_airstatus.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import unittest
+import json
+from datetime import datetime
+import pyowm.commons.exceptions
+from pyowm.weatherapi25.location import Location
+from pyowm.airpollutionapi30.airstatus import AirStatus
+
+
+AIRSTATUS_JSON = '{"coord":{"lon":-0.1278,"lat":51.5074},"list":[{"main":{"aqi":1},"components":{"co":250.34,"no":0.19,"no2":35.99,"o3":30.76,"so2":8.11,"pm2_5":3.15,"pm10":3.81,"nh3":0.74},"dt":1611597600}]}'
+AIRSTATUS_MULTIPLE_JSON = '{"coord":{"lon":50,"lat":50},"list":[{"main":{"aqi":1},"components":{"co":240.33,"no":0,"no2":1.07,"o3":79.39,"so2":0.97,"pm2_5":1.84,"pm10":1.9,"nh3":1.25},"dt":1613606400},{"main":{"aqi":1},"components":{"co":240.33,"no":0,"no2":0.98,"o3":79.39,"so2":0.69,"pm2_5":1.92,"pm10":1.97,"nh3":1.36},"dt":1613610000}]}'
+AIRSTATUS_MALFORMED_JSON = '{"time":"2016-10-01T13:07:01Z","xyz":[]}'
+AIRSTATUS_JSON_DUMP = '{"reference_time": 1234567, "location": {"name": "test", "coordinates": {"lon": 12.3, "lat": 43.7}, "ID": 987, "country": "UK"}, "air_quality_data": {"aqi": 1, "co": 250.34, "no": 0.19, "no2": 35.99, "o3": 30.76, "so2": 8.11, "pm2_5": 3.15, "pm10": 3.81, "nh3": 0.74}, "reception_time": 1475283600}'
+
+
+class TestAirStatus(unittest.TestCase):
+
+    __test_reception_time = 1475283600
+    __test_iso_reception_time = "2016-10-01 01:00:00+00:00"
+    __test_date_reception_time = datetime.fromisoformat(__test_iso_reception_time)
+
+    __test_reference_time = 1234567
+    __test_iso_reference_time = "1970-01-15 06:56:07+00:00"
+    __test_date_reference_time = datetime.fromisoformat(__test_iso_reference_time)
+    __test_location = Location('test', 12.3, 43.7, 987, 'UK')
+    __test_air_quality_data = {"aqi": 1, "co": 250.34, "no": 0.19, "no2": 35.99, "o3": 30.76, "so2": 8.11, "pm2_5": 3.15, "pm10": 3.81, "nh3": 0.74}
+    __test_interval = 'day'
+    __test_instance = AirStatus(
+        __test_reference_time, __test_location, __test_air_quality_data, __test_reception_time)
+
+    def test_init_fails_when_reference_time_is_negative(self):
+        self.assertRaises(ValueError, AirStatus, -1234567,
+                          self.__test_location,
+                          self.__test_air_quality_data,
+                          self.__test_reception_time)
+
+    def test_init_fails_when_reception_time_is_negative(self):
+        self.assertRaises(ValueError, AirStatus,
+                          self.__test_reference_time,
+                          self.__test_location,
+                          self.__test_air_quality_data,
+                          -1234567)
+
+    def test_init_fails_when_air_quality_data_is_not_a_dict(self):
+        self.assertRaises(ValueError, AirStatus, self.__test_reference_time,
+                          self.__test_location, 'test',
+                          self.__test_reception_time)
+
+    def test_returning_different_formats_for_reference_time(self):
+        self.assertEqual(self.__test_instance.reference_time(timeformat='iso'), \
+                         self.__test_iso_reference_time)
+        self.assertEqual(self.__test_instance.reference_time(timeformat='unix'), \
+                         self.__test_reference_time)
+        self.assertEqual(self.__test_instance.reference_time(timeformat='date'), \
+                         self.__test_date_reference_time)
+
+    def test_returning_different_formats_for_reception_time(self):
+        self.assertEqual(self.__test_instance.reception_time(timeformat='iso'), \
+                         self.__test_iso_reception_time)
+        self.assertEqual(self.__test_instance.reception_time(timeformat='unix'), \
+                         self.__test_reception_time)
+        self.assertEqual(self.__test_instance.reception_time(timeformat='date'), \
+                         self.__test_date_reception_time)
+
+    def test_from_dict(self):
+        # one item
+        d = json.loads(AIRSTATUS_JSON)
+        result = AirStatus.from_dict(d)
+        self.assertIsNotNone(result)
+        self.assertIsNotNone(result.reference_time())
+        loc = result.location
+        self.assertIsNotNone(loc)
+        self.assertIsNone(loc.name)
+        self.assertIsNone(loc.id)
+        self.assertIsNotNone(loc.lon)
+        self.assertIsNotNone(loc.lat)
+        for key in self.__test_air_quality_data:
+            getattr(result, key)
+
+        # multiple items
+        d = json.loads(AIRSTATUS_MULTIPLE_JSON)
+        result = AirStatus.from_dict(d)
+        self.assertIsInstance(result, list)
+        for item in result:
+            self.assertIsInstance(item, AirStatus)
+            self.assertIsNotNone(item.reference_time())
+            loc = item.location
+            self.assertIsNotNone(loc)
+            self.assertIsNone(loc.name)
+            self.assertIsNone(loc.id)
+            self.assertIsNotNone(loc.lon)
+            self.assertIsNotNone(loc.lat)
+            for key in self.__test_air_quality_data:
+                getattr(item, key)
+
+    def test_from_dict_fails_when_JSON_data_is_None(self):
+        self.assertRaises(pyowm.commons.exceptions.ParseAPIResponseError, AirStatus.from_dict, None)
+
+    def test_from_dict_fails_with_malformed_JSON_data(self):
+        self.assertRaises(pyowm.commons.exceptions.ParseAPIResponseError, AirStatus.from_dict, json.loads(AIRSTATUS_MALFORMED_JSON))
+
+    def test_to_dict(self):
+        expected = json.loads(AIRSTATUS_JSON_DUMP)
+        result = self.__test_instance.to_dict()
+
+        ordered_str_expected = sorted(str(expected))
+        ordered_str_result = sorted(str(result))
+        self.assertEqual(ordered_str_expected, ordered_str_result)
+
+    def test_repr(self):
+        print(self.__test_instance)
diff --git a/tests/unit/commons/test_cityidregistry.py b/tests/unit/commons/test_cityidregistry.py
index 6ef5f3d7..b033fde9 100644
--- a/tests/unit/commons/test_cityidregistry.py
+++ b/tests/unit/commons/test_cityidregistry.py
@@ -30,7 +30,9 @@ class TestCityIDRegistry(unittest.TestCase):
 Abbeville,4829449,31.57184,-85.250488,US
 Bologna,2829449,30.57184,-83.250488,IT"""
     _test_file_contents_with_commas_in_names = """Thalassery,1254780,11.75,75.533333,IN
-Thale, Stadt,6550950,51.7528,11.058,DE"""
+Thale, Stadt,6550950,51.7528,11.058,DE
+Pitcairn,5206361,40.403118,-79.778099,PA
+Pitcairn, Henderson, Ducie and Oeno Islands,4030699,-25.066669,-130.100006,PN"""
 
     test_filelines = [
         'Londinieres,2997784,49.831871,1.40232,FR\n',
@@ -111,6 +113,14 @@ def test_city_name_matches(self):
             'test', 'test me', 'like'))
         self.assertFalse(self._instance._city_name_matches(
             'foo', 'bar', 'like'))
+        self.assertTrue(self._instance._city_name_matches(
+            'Me', 'test me', 'like'))
+        self.assertTrue(self._instance._city_name_matches(
+            'Test', 'test me', 'startswith'))
+        self.assertFalse(self._instance._city_name_matches(
+            'me', 'test me', 'startswith'))
+        self.assertFalse(self._instance._city_name_matches(
+            'foo', 'bar', 'startswith'))
 
     # tests for IDs retrieval
 
@@ -220,6 +230,23 @@ def test_ids_for_matching_criteria(self):
         self.assertTrue((3038800, 'Abbans-Dessus', 'FR') in result)
         self.assertTrue((6452202, 'Abbans-Dessus', 'FR') in result)
 
+        # startswith
+        result = self._instance.ids_for("abban", matching='startswith')
+        self.assertEqual(2, len(result))
+        self.assertTrue((3038800, 'Abbans-Dessus', 'FR') in result)
+        self.assertTrue((6452202, 'Abbans-Dessus', 'FR') in result)
+
+        result = self._instance.ids_for("dessus", matching='startswith')
+        self.assertEqual(0, len(result))
+
+        result = self._instance.ids_for("abbe", matching='startswith')
+        self.assertEqual(5, len(result))
+        self.assertTrue((3038789, 'Abbeville', 'FR') in result)
+        self.assertTrue((4568985, 'Abbeville', 'US') in result)
+
+        result = self._instance.ids_for("ville", matching='startswith')
+        self.assertEqual(0, len(result))
+
         CityIDRegistry._get_lines = original_get_lines
         CityIDRegistry._get_all_lines = original_get_all_lines
 
@@ -251,6 +278,54 @@ def test_ids_for_with_commas_in_city_names(self):
         self.assertEqual(1, len(result))
         self.assertTrue((6550950, 'Thale, Stadt', 'DE') in result)
 
+        result = self._instance.ids_for("Pitcairn, Henderson, Ducie and Oeno Islands")
+        self.assertEqual(1, len(result))
+        self.assertTrue((4030699, 'Pitcairn, Henderson, Ducie and Oeno Islands', 'PN') in result)
+
+        CityIDRegistry._get_lines = ref_to_original
+
+    def test_ids_for_with_commas_in_city_names_like(self):
+        ref_to_original = CityIDRegistry._get_lines
+        CityIDRegistry._get_lines = self._mock_test_file_contents_with_commas_in_names
+
+        result = self._instance.ids_for("Pitca", matching='like')
+        self.assertTrue((5206361, 'Pitcairn', 'PA') in result)
+        self.assertTrue((4030699, 'Pitcairn, Henderson, Ducie and Oeno Islands', 'PN') in result)
+
+        CityIDRegistry._get_lines = ref_to_original
+
+    def test_ids_for_with_commas_in_city_names_like_country(self):
+        ref_to_original = CityIDRegistry._get_lines
+        CityIDRegistry._get_lines = self._mock_test_file_contents_with_commas_in_names
+
+        result = self._instance.ids_for("Pitc", country='PA',matching='like')
+        self.assertTrue((5206361, 'Pitcairn', 'PA') in result)
+
+        result = self._instance.ids_for("Ducie and Oeno", country='PN',matching='like')
+        self.assertTrue((4030699, 'Pitcairn, Henderson, Ducie and Oeno Islands', 'PN') in result)
+
+        CityIDRegistry._get_lines = ref_to_original
+    
+    def test_ids_for_with_commas_in_city_names_startswith(self):
+        ref_to_original = CityIDRegistry._get_lines
+        CityIDRegistry._get_lines = self._mock_test_file_contents_with_commas_in_names
+
+        result = self._instance.ids_for("Pitc", matching='startswith')
+        self.assertEqual(2, len(result))
+        self.assertTrue((5206361, 'Pitcairn', 'PA') in result)
+        self.assertTrue((4030699, 'Pitcairn, Henderson, Ducie and Oeno Islands', 'PN') in result)
+
+        CityIDRegistry._get_lines = ref_to_original
+    
+    def test_ids_for_with_commas_in_city_names_startswith_country(self):
+        ref_to_original = CityIDRegistry._get_lines
+        CityIDRegistry._get_lines = self._mock_test_file_contents_with_commas_in_names
+
+        result = self._instance.ids_for("Pitc", country="PA", matching='startswith')
+        self.assertEqual(1, len(result))
+        self.assertTrue((5206361, 'Pitcairn', 'PA') in result)
+        self.assertFalse((4030699, 'Pitcairn, Henderson, Ducie and Oeno Islands', 'PN') in result)
+
         CityIDRegistry._get_lines = ref_to_original
 
     # tests for locations retrieval
@@ -330,6 +405,15 @@ def test_locations_for_matching_criteria(self):
         self._assertLocationsEqual(expected1, result[0])
         self._assertLocationsEqual(expected2, result[1])
 
+        # startswith
+        result = self._instance.locations_for("abba", matching='startswith')
+        self.assertEqual(2, len(result))
+        self._assertLocationsEqual(expected1, result[0])
+        self._assertLocationsEqual(expected2, result[1])
+
+        result = self._instance.locations_for("bbans", matching='startswith')
+        self.assertEqual(0, len(result))
+
         CityIDRegistry._get_lines = original_get_lines
         CityIDRegistry._get_all_lines = original_get_all_lines
 
@@ -373,6 +457,60 @@ def test_locations_for_with_commas_in_city_names(self):
             Location('Thale, Stadt', 11.058, 51.7528, 6550950, 'DE'),
             result[0])
 
+        result = self._instance.locations_for("Pitcairn, Henderson, Ducie and Oeno Islands")
+        self.assertEqual(1, len(result))
+        self._assertLocationsEqual(
+            Location('Pitcairn, Henderson, Ducie and Oeno Islands', -130.100006, -25.066669, 4030699, 'PN'),
+            result[0])
+
+        CityIDRegistry._get_lines = ref_to_original
+
+    def test_locations_for_with_commas_in_city_names_like(self):
+        ref_to_original = CityIDRegistry._get_lines
+        CityIDRegistry._get_lines = self._mock_test_file_contents_with_commas_in_names
+
+        result = self._instance.locations_for("Pitcai", matching="like")
+        self._assertLocationsEqual(result[0], Location('Pitcairn', -79.778099, 40.403118, 5206361, 'PA'))
+        self._assertLocationsEqual(result[1], Location('Pitcairn, Henderson, Ducie and Oeno Islands', -130.100006, -25.066669, 4030699, 'PN'))
+
+        CityIDRegistry._get_lines = ref_to_original
+
+    def test_locations_for_with_commas_in_city_names_like_country(self):
+        ref_to_original = CityIDRegistry._get_lines
+        CityIDRegistry._get_lines = self._mock_test_file_contents_with_commas_in_names
+
+        result = self._instance.locations_for("Pitca", country='PA', matching="like")
+        self._assertLocationsEqual(result[0], Location('Pitcairn', -79.778099, 40.403118, 5206361, 'PA'))
+
+        result = self._instance.locations_for("Ducie", country='PN', matching="like")
+        self._assertLocationsEqual(result[1], Location('Pitcairn, Henderson, Ducie and Oeno Islands', -130.100006, -25.066669, 4030699, 'PN'))
+
+        CityIDRegistry._get_lines = ref_to_original
+
+    def test_locations_for_with_commas_in_city_names_startswith(self):
+        ref_to_original = CityIDRegistry._get_lines
+        CityIDRegistry._get_lines = self._mock_test_file_contents_with_commas_in_names
+
+        result = self._instance.locations_for("Pitcai", matching="startswith")
+        self.assertEqual(2, len(result))
+        self._assertLocationsEqual(result[0], Location('Pitcairn', -79.778099, 40.403118, 5206361, 'PA'))
+        self._assertLocationsEqual(result[1], Location('Pitcairn, Henderson, Ducie and Oeno Islands', -130.100006, -25.066669, 4030699, 'PN'))
+
+        result = self._instance.locations_for("vil", matching="startswith")
+        self.assertEqual(0, len(result))
+
+        CityIDRegistry._get_lines = ref_to_original
+
+    def test_locations_for_with_commas_in_city_names_startswith_country(self):
+        ref_to_original = CityIDRegistry._get_lines
+        CityIDRegistry._get_lines = self._mock_test_file_contents_with_commas_in_names
+
+        result = self._instance.locations_for("Pit", country='PA', matching="startswith")
+        self._assertLocationsEqual(result[0], Location('Pitcairn', -79.778099, 40.403118, 5206361, 'PA'))
+
+        result = self._instance.locations_for("Ducie", country='PN', matching="startswith")
+        self.assertEqual(0, len(result))
+
         CityIDRegistry._get_lines = ref_to_original
 
     def test_geopoints_for(self):
diff --git a/tests/unit/geocodingapi10/__init__.py b/tests/unit/geocodingapi10/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/unit/geocodingapi10/geocoding_manager.py b/tests/unit/geocodingapi10/geocoding_manager.py
new file mode 100644
index 00000000..1bde58a9
--- /dev/null
+++ b/tests/unit/geocodingapi10/geocoding_manager.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import json
+import unittest
+from pyowm.commons.http_client import HttpClient
+from pyowm.config import DEFAULT_CONFIG
+from pyowm.constants import GEOCODING_API_VERSION
+from pyowm.geocodingapi10.geocoding_manager import GeocodingManager
+from pyowm.weatherapi25.location import Location
+
+
+class TestGeocodingManager(unittest.TestCase):
+
+    __test_instance = GeocodingManager('fakeapikey', DEFAULT_CONFIG)
+
+    DIRECT_GEOCODING_JSON = '''[{"name":"London","local_names":{"af":"Londen","ar":"لندن","ascii":"London","az":"London","bg":"Лондон","ca":"Londres","da":"London","de":"London","el":"Λονδίνο","en":"London","eu":"Londres","fa":"لندن","feature_name":"London","fi":"Lontoo","fr":"Londres","gl":"Londres","he":"לונדון","hi":"लंदन","hr":"London","hu":"London","id":"London","it":"Londra","ja":"ロンドン","la":"Londinium","lt":"Londonas","mk":"Лондон","nl":"Londen","no":"London","pl":"Londyn","pt":"Londres","ro":"Londra","ru":"Лондон","sk":"Londýn","sl":"London","sr":"Лондон","th":"ลอนดอน","tr":"Londra","vi":"Luân Đôn","zu":"ILondon"},"lat":51.5085,"lon":-0.1257,"country":"GB"},{"name":"London","local_names":{"ar":"لندن","ascii":"London","bg":"Лондон","de":"London","en":"London","fa":"لندن، انتاریو","feature_name":"London","fi":"London","fr":"London","he":"לונדון","ja":"ロンドン","lt":"Londonas","nl":"London","pl":"London","pt":"London","ru":"Лондон","sr":"Лондон"},"lat":42.9834,"lon":-81.233,"country":"CA"},{"name":"London","local_names":{"ar":"لندن","ascii":"London","en":"London","fa":"لندن، اوهایو","feature_name":"London","sr":"Ландон"},"lat":39.8865,"lon":-83.4483,"country":"US","state":"OH"},{"name":"London","local_names":{"ar":"لندن","ascii":"London","en":"London","fa":"لندن، کنتاکی","feature_name":"London","sr":"Ландон"},"lat":37.129,"lon":-84.0833,"country":"US","state":"KY"},{"name":"London","local_names":{"ascii":"London","ca":"Londres","en":"London","feature_name":"London"},"lat":36.4761,"lon":-119.4432,"country":"US","state":"CA"}]'''
+    REVERSE_GEOCODING_JSON = '''[{"name":"London","local_names":{"af":"Londen","ar":"لندن","ascii":"London","az":"London","bg":"Лондон","ca":"Londres","da":"London","de":"London","el":"Λονδίνο","en":"London","eu":"Londres","fa":"لندن","feature_name":"London","fi":"Lontoo","fr":"Londres","gl":"Londres","he":"לונדון","hi":"लंदन","hr":"London","hu":"London","id":"London","it":"Londra","ja":"ロンドン","la":"Londinium","lt":"Londonas","mk":"Лондон","nl":"Londen","no":"London","pl":"Londyn","pt":"Londres","ro":"Londra","ru":"Лондон","sk":"Londýn","sl":"London","sr":"Лондон","th":"ลอนดอน","tr":"Londra","vi":"Luân Đôn","zu":"ILondon"},"lat":51.5085,"lon":-0.1257,"country":"GB"},{"name":"City of Westminster","local_names":{"ascii":"City of Westminster","feature_name":"City of Westminster"},"lat":51.5,"lon":-0.1167,"country":"GB"},{"name":"Lambeth","local_names":{"ascii":"Lambeth","en":"Lambeth","feature_name":"Lambeth"},"lat":51.4963,"lon":-0.1115,"country":"GB"},{"name":"Clerkenwell","local_names":{"ascii":"Clerkenwell","feature_name":"Clerkenwell","hi":"क्लर्कनवेल","ru":"Кларкенуэлл"},"lat":51.5244,"lon":-0.1102,"country":"GB"},{"name":"City of London","local_names":{"ar":"مدينة لندن","ascii":"City of London","bg":"Сити","ca":"La City","de":"London City","el":"Σίτι του Λονδίνου","en":"City of London","fa":"سیتی لندن","feature_name":"City of London","fi":"Lontoon City","fr":"Cité de Londres","gl":"Cidade de Londres","he":"הסיטי של לונדון","hi":"सिटी ऑफ़ लंदन","id":"Kota London","it":"Londra","ja":"シティ・オブ・ロンドン","la":"Civitas Londinium","lt":"Londono Sitis","pt":"Cidade de Londres","ru":"Сити","sr":"Сити","th":"นครลอนดอน","tr":"Londra Şehri","vi":"Thành phố Luân Đôn","zu":"Idolobha weLondon"},"lat":51.5128,"lon":-0.0918,"country":"GB"}]'''
+    MALFORMED_JSON = '{"a":"2016-10-01T13:07:01Z","b":[]}'
+
+    def mock_get_json_for_direct_geocoding(self, URI, params):
+        return 200, json.loads(self.DIRECT_GEOCODING_JSON)
+
+    def mock_get_json_returning_malformed_json(self, URI, params):
+        return 200, json.loads(self.MALFORMED_JSON)
+
+    def mock_get_json_for_reverse_geocoding(self, URI, params):
+        return 200, json.loads(self.REVERSE_GEOCODING_JSON)
+
+    def test_geocoding_api_version(self):
+        result = self.__test_instance.geocoding_api_version()
+        self.assertIsInstance(result, tuple)
+        self.assertEqual(result, GEOCODING_API_VERSION)
+
+    def test_instantiation_with_wrong_params(self):
+        self.assertRaises(AssertionError, GeocodingManager, None, dict())
+        self.assertRaises(AssertionError, GeocodingManager, 'apikey', None)
+
+    def test_geocode_with_wrong_params(self):
+        self.assertRaises(AssertionError,
+                          GeocodingManager.geocode, self.__test_instance,
+                          None)
+        self.assertRaises(ValueError,
+                          GeocodingManager.geocode, self.__test_instance,
+                          'London', 'tooooomany')
+        self.assertRaises(ValueError,
+                          GeocodingManager.geocode, self.__test_instance,
+                          'London', 'GB', 'tooooomany')
+        self.assertRaises(AssertionError,
+                          GeocodingManager.geocode, self.__test_instance,
+                          'London', 'OH', 'US', 'notastring')
+        self.assertRaises(AssertionError,
+                          GeocodingManager.geocode, self.__test_instance,
+                          'London', 'OH', 'US', -6)
+
+    def test_geocode_fails(self):
+        ref_to_original = HttpClient.get_json
+        HttpClient.get_json = self.mock_get_json_returning_malformed_json
+        self.assertRaises(Exception,
+                          GeocodingManager.geocode, self.__test_instance,
+                          'London', 'GB')
+        HttpClient.get_json = ref_to_original
+
+    def test_geocode(self):
+        ref_to_original = HttpClient.get_json
+        HttpClient.get_json = self.mock_get_json_for_direct_geocoding
+        locations = self.__test_instance.geocode('London', 'GB')
+        self.assertTrue(isinstance(locations, list))
+        self.assertTrue(all([isinstance(l, Location) for l in locations]))
+        self.assertTrue(all([l.name == 'London' for l in locations]))
+        HttpClient.get_json = ref_to_original
+
+    def test_reverse_geocode_with_wrong_params(self):
+        self.assertRaises(AssertionError,
+                          GeocodingManager.reverse_geocode, self.__test_instance,
+                          None, None)
+        self.assertRaises(AssertionError,
+                          GeocodingManager.reverse_geocode, self.__test_instance,
+                          None, -0.15)
+        self.assertRaises(AssertionError,
+                          GeocodingManager.reverse_geocode, self.__test_instance,
+                          42, None)
+        self.assertRaises(ValueError,
+                          GeocodingManager.reverse_geocode, self.__test_instance,
+                          167, 15)
+        self.assertRaises(ValueError,
+                          GeocodingManager.reverse_geocode, self.__test_instance,
+                          15, 234)
+        self.assertRaises(AssertionError,
+                          GeocodingManager.reverse_geocode, self.__test_instance,
+                          42, 16, 'notanint')
+        self.assertRaises(AssertionError,
+                          GeocodingManager.reverse_geocode, self.__test_instance,
+                          42, 16, -4)
+
+    def test_reverse_geocode_fails(self):
+        ref_to_original = HttpClient.get_json
+        HttpClient.get_json = self.mock_get_json_returning_malformed_json
+        self.assertRaises(Exception,
+                          GeocodingManager.reverse_geocode, self.__test_instance,
+                          51.5098, -0.1180)
+        HttpClient.get_json = ref_to_original
+
+    def test_reverse_geocode(self):
+        lat = 51.5098
+        lon = -0.1180
+        ref_to_original = HttpClient.get_json
+        HttpClient.get_json = self.mock_get_json_for_reverse_geocoding
+        locations = self.__test_instance.reverse_geocode(lat, lon)
+        self.assertTrue(isinstance(locations, list))
+        self.assertTrue(all([isinstance(l, Location) for l in locations]))
+        HttpClient.get_json = ref_to_original
diff --git a/tests/unit/test_owm.py b/tests/unit/test_owm.py
index 5ee9ca13..0603e95a 100644
--- a/tests/unit/test_owm.py
+++ b/tests/unit/test_owm.py
@@ -7,6 +7,7 @@
 from pyowm.airpollutionapi30.airpollution_manager import AirPollutionManager
 from pyowm.alertapi30.alert_manager import AlertManager
 from pyowm.commons.cityidregistry import CityIDRegistry
+from pyowm.geocodingapi10.geocoding_manager import GeocodingManager
 from pyowm.stationsapi30.stations_manager import StationsManager
 from pyowm.tiles.tile_manager import TileManager
 from pyowm.uvindexapi30.uvindex_manager import UVIndexManager
@@ -80,3 +81,8 @@ def test_tile_manager(self):
         result = self.__test_instance.tile_manager('test')
         self.assertIsNotNone(result)
         self.assertIsInstance(result, TileManager)
+
+    def test_geocoding_manager(self):
+        result = self.__test_instance.geocoding_manager()
+        self.assertIsNotNone(result)
+        self.assertIsInstance(result, GeocodingManager)
diff --git a/tests/unit/utils/test_measurables.py b/tests/unit/utils/test_measurables.py
index 25399896..f9196072 100644
--- a/tests/unit/utils/test_measurables.py
+++ b/tests/unit/utils/test_measurables.py
@@ -132,3 +132,23 @@ def test_metric_wind_dict_to_beaufort(self):
         expected = {'speed': 8, 'gust': 2, 'deg': 7.89}
         result = measurables.metric_wind_dict_to_beaufort(input)
         self.assertEqual(expected, result)
+
+    def test_metric_pressure_dict_to_inhg(self):
+        input = {'press': 1000, 'sea_level': 1, 'grnd_level': None}
+        expected = {'press': 29.53, 'sea_level': .03}
+        result = measurables.metric_pressure_dict_to_inhg(input)
+        print(result)
+        self.assertEqual(expected, result)
+
+    def test_visibility_distance_to(self):
+        distances = (100, 200, None)
+        cmp_kms = (.1, .2, None)
+        cmp_miles = (.06, .12, None)
+        case_one, case_two = list(), list()
+        for distance in distances:
+            case_one.append(measurables.visibility_distance_to(distance))
+            case_two.append(measurables.visibility_distance_to(distance, 'miles'))
+        self.assertTrue(tuple(case_one) == cmp_kms and tuple(case_two) == cmp_miles)
+
+    def test_visibility_distance_to_fails_with_invalid_unit(self):
+        self.assertRaises(ValueError, measurables.visibility_distance_to, 10, 'xyz')
\ No newline at end of file
diff --git a/tests/unit/weatherapi25/test_forecast.py b/tests/unit/weatherapi25/test_forecast.py
index b7978eb3..44b5d46d 100644
--- a/tests/unit/weatherapi25/test_forecast.py
+++ b/tests/unit/weatherapi25/test_forecast.py
@@ -21,14 +21,16 @@ class TestForecast(unittest.TestCase):
                                {"temp": 294.199, "temp_kf": -1.899, "temp_max": 296.098,
                                 "temp_min": 294.199
                                 },
-                               "Clouds", "Overcast clouds", 804, "04d", 1000, 300.0, 298.0, 296.0),
+                               "Clouds", "Overcast clouds", 804, "04d", 1000, 300.0, 298.0, 296.0,
+                               precipitation_probability=0.25),
                        Weather(1378459690, 1378496480, 1378449510, 23, {"all": 10},
                                {"all": 0}, {"deg": 103.4, "speed": 4.2}, 12,
                                {"press": 1070.119, "sea_level": 1078.589},
                                {"temp": 297.199, "temp_kf": -1.899, "temp_max": 299.0,
                                 "temp_min": 295.6
                                 },
-                               "Clear", "Sky is clear", 804, "02d", 1000, 300.0, 298.0, 296.0)
+                               "Clear", "Sky is clear", 804, "02d", 1000, 300.0, 298.0, 296.0,
+                               precipitation_probability=0.0)
                        ]
     __test_n_weathers = len(__test_weathers)
     __test_instance = Forecast("daily", __test_reception_time, __test_location,
@@ -54,7 +56,8 @@ class TestForecast(unittest.TestCase):
                          '"sunset_time": 1378496400, "pressure": {"press": 1030.119,' \
                          ' "sea_level": 1038.589}, "sunrise_time": 1378449600, ' \
                          '"heat_index": 296.0, "weather_icon_name": "04d", "wind": ' \
-                         '{"speed": 1.1, "deg": 252.002}, "utc_offset": null, "uvi": null}, {"status": "Clear", ' \
+                         '{"speed": 1.1, "deg": 252.002}, "utc_offset": null, "uvi": null, ' \
+                         '"precipitation_probability": 0.25}, {"status": "Clear", ' \
                          '"visibility_distance": 1000, "humidity": 12, ' \
                          '"clouds": 23, "temperature": {"temp_kf": -1.899, ' \
                          '"temp_max": 299.0, "temp": 297.199, "temp_min": 295.6}, ' \
@@ -65,7 +68,7 @@ class TestForecast(unittest.TestCase):
                          '{"press": 1070.119, "sea_level": 1078.589}, ' \
                          '"sunrise_time": 1378449510, "heat_index": 296.0, ' \
                          '"weather_icon_name": "02d", "wind": {"speed": 4.2, ' \
-                         '"deg": 103.4}, "utc_offset": null, "uvi": null}]}'
+                         '"deg": 103.4}, "utc_offset": null, "uvi": null, "precipitation_probability": 0.0}]}'
 
     def test_actualize(self):
         weathers = [Weather(1378459200, 1378496400, 1378449600, 67,
diff --git a/tests/unit/weatherapi25/test_observation.py b/tests/unit/weatherapi25/test_observation.py
index 8c8a111b..961d00dd 100644
--- a/tests/unit/weatherapi25/test_observation.py
+++ b/tests/unit/weatherapi25/test_observation.py
@@ -52,7 +52,7 @@ class TestObservation(unittest.TestCase):
                             '"sunrise_time": 1378449600, "heat_index": 296.0, ' \
                             '"weather_icon_name": "04d", "wind": ' \
                             '{"speed": 1.1, "deg": 252.002}, "utc_offset": null, ' \
-                            '"uvi": null}}'
+                            '"uvi": null, "precipitation_probability": null}}'
 
     def test_init_fails_when_reception_time_is_negative(self):
         self.assertRaises(ValueError, Observation, -1234567, \
diff --git a/tests/unit/weatherapi25/test_weather.py b/tests/unit/weatherapi25/test_weather.py
index ed03b594..f3479f56 100644
--- a/tests/unit/weatherapi25/test_weather.py
+++ b/tests/unit/weatherapi25/test_weather.py
@@ -34,6 +34,7 @@ class TestWeather(unittest.TestCase):
     __test_kmh_wind = {'deg': 252.002, 'speed': 3.9600000000000004, 'gust': 7.524}
     __test_humidity = 57
     __test_pressure = {"press": 1030.119, "sea_level": 1038.589, "grnd_level": 1038.773}
+    __test_inhg_pressure = {'press': 30.42, 'sea_level': 30.67, 'grnd_level': 30.67}
     __test_temperature = {"temp": 294.199, "temp_kf": -1.899,
                           "temp_max": 296.098, "temp_min": 294.199,
                           "feels_like": 298.0}
@@ -48,9 +49,12 @@ class TestWeather(unittest.TestCase):
     __test_weather_code = 804
     __test_weather_icon_name = "04d"
     __test_visibility_distance = 1000
+    __test_visibility_in_kms = 1
+    __test_visibility_in_miles = .62
     __test_dewpoint = 300.0
     __test_humidex = 298.0
     __test_heat_index = 40.0
+    __test_precipitation_probability = 0.5
 
     __test_instance = Weather(__test_reference_time, __test_sunset_time,
                               __test_sunrise_time, __test_clouds, __test_rain,
@@ -59,7 +63,8 @@ class TestWeather(unittest.TestCase):
                               __test_status, __test_detailed_status,
                               __test_weather_code, __test_weather_icon_name,
                               __test_visibility_distance, __test_dewpoint,
-                              __test_humidex, __test_heat_index)
+                              __test_humidex, __test_heat_index,
+                              precipitation_probability=__test_precipitation_probability)
 
     __bad_json = '{"a": "test", "b": 1.234, "c": [ "hello", "world"] }'
     __bad_json_2 = '{"list": [{"test":"fake"}] }'
@@ -75,7 +80,8 @@ class TestWeather(unittest.TestCase):
                         '{"press": 1030.119, "sea_level": 1038.589, "grnd_level": 1038.773}, ' \
                         '"sunrise_time": 1378449600, "heat_index": 40.0, ' \
                         '"weather_icon_name": "04d", "humidity": 57, "wind": ' \
-                        '{"speed": 1.1, "deg": 252.002, "gust": 2.09}, "utc_offset": null, "uvi": null}'
+                        '{"speed": 1.1, "deg": 252.002, "gust": 2.09}, "utc_offset": null, "uvi": null, ' \
+                        '"precipitation_probability": 0.5}'
 
     def test_init_fails_when_wrong_data_provided(self):
         self.assertRaises(ValueError, Weather, -9876543210,
@@ -138,6 +144,26 @@ def test_init_fails_when_wrong_data_provided(self):
                           self.__test_weather_code, self.__test_weather_icon_name,
                           self.__test_visibility_distance, self.__test_dewpoint,
                           self.__test_humidex, self.__test_heat_index, uvi=-1)
+        self.assertRaises(ValueError, Weather, self.__test_reference_time,
+                          self.__test_sunset_time, self.__test_sunrise_time,
+                          self.__test_clouds, self.__test_rain, self.__test_snow,
+                          self.__test_wind, self.__test_humidity,
+                          self.__test_pressure, self.__test_temperature,
+                          self.__test_status, self.__test_detailed_status,
+                          self.__test_weather_code, self.__test_weather_icon_name,
+                          self.__test_visibility_distance, self.__test_dewpoint,
+                          self.__test_humidex, self.__test_heat_index,
+                          precipitation_probability=-1.0)
+        self.assertRaises(ValueError, Weather, self.__test_reference_time,
+                          self.__test_sunset_time, self.__test_sunrise_time,
+                          self.__test_clouds, self.__test_rain, self.__test_snow,
+                          self.__test_wind, self.__test_humidity,
+                          self.__test_pressure, self.__test_temperature,
+                          self.__test_status, self.__test_detailed_status,
+                          self.__test_weather_code, self.__test_weather_icon_name,
+                          self.__test_visibility_distance, self.__test_dewpoint,
+                          self.__test_humidex, self.__test_heat_index,
+                          precipitation_probability=2.0)
 
     def test_init_when_wind_is_none(self):
         instance = Weather(self.__test_reference_time,
@@ -530,6 +556,29 @@ def test_returning_different_units_for_wind_values(self):
     def test_get_wind_fails_with_unknown_units(self):
         self.assertRaises(ValueError, Weather.wind, self.__test_instance, 'xyz')
 
+    def test_barometric_pressure_returning_different_units_for_pressure_values(self):
+        result_imperial_inhg = self.__test_instance.barometric_pressure(unit='inHg')
+        result_metric_hpa = self.__test_instance.barometric_pressure(unit='hPa')
+        result_unspecified = self.__test_instance.barometric_pressure()
+        a = result_metric_hpa == result_unspecified
+        b = result_imperial_inhg == self.__test_inhg_pressure
+        self.assertTrue(a and b)
+
+    def test_barometric_pressure_fails_with_unknown_units(self):
+        self.assertRaises(ValueError, Weather.barometric_pressure, self.__test_instance, 'xyz')
+
+    def test_returning_different_units_for_visibility(self):
+        result_metric_kms = self.__test_instance.visibility(unit='kilometers')
+        result_imperial_miles = self.__test_instance.visibility(unit='miles')
+        result_unspecified = self.__test_instance.visibility()
+        a = self.__test_visibility_distance == result_unspecified
+        b = self.__test_visibility_in_kms == result_metric_kms
+        c = self.__test_visibility_in_miles == result_imperial_miles
+        self.assertTrue(a and b and c)
+
+    def test_visibility_fails_with_unknown_units(self):
+        self.assertRaises(ValueError, Weather.visibility, self.__test_instance, 'xyz')
+
     def test_weather_icon_url(self):
         expected_unspecified = ICONS_BASE_URI % (self.__test_instance.weather_icon_name, "")
         expected_2x = ICONS_BASE_URI % (self.__test_instance.weather_icon_name, "@2x")