Repository: csparpa/pyowm

Issue:
[Feature suggestion] Change wind speed unit
While `get_temperature() `supports unit parameter, `get_wind()` does not support it. OWM has wind speed unit available as Imperial (miles/hour) or Metric (meter/sec) unit. 

Suggested implementation:
`w.get_wind(unit = 'Imperial')` or  
`w.get_wind(unit = 'miles/hour')` or  
`w.get_wind('Imperial')`

References:
[https://openweathermap.org/weather-data](url)

Discussion:

Patch:
diff --git a/pyowm/utils/temputils.py b/pyowm/utils/temputils.py
index 58c4ea9..8bc29c3 100644
--- a/pyowm/utils/temputils.py
+++ b/pyowm/utils/temputils.py
@@ -1,11 +1,15 @@
 """
-Module containing utility functions for temperature units conversion
+Module containing utility functions for temperature and wind units conversion
 """
 
+# Temperature coneversion constants
 KELVIN_OFFSET = 273.15
 FAHRENHEIT_OFFSET = 32.0
 FAHRENHEIT_DEGREE_SCALE = 1.8
 
+# Wind speed conversion constants
+MILES_PER_HOUR_FOR_ONE_METER_PER_SEC = 2.23694
+
 
 def kelvin_dict_to(d, target_temperature_unit):
     """
@@ -66,3 +70,24 @@ def kelvin_to_fahrenheit(kelvintemp):
     fahrenheittemp = (kelvintemp - KELVIN_OFFSET) * \
         FAHRENHEIT_DEGREE_SCALE + FAHRENHEIT_OFFSET
     return float("{0:.2f}".format(fahrenheittemp))
+
+
+def metric_wind_dict_to_imperial(d):
+    """
+    Converts all the wind values in a dict from meters/sec (metric measurement 
+    system) to miles/hour (imperial measurement system)
+    .
+
+    :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 miles/hour
+
+    """
+    result = dict()
+    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
+        else:
+            result[key] = value
+    return result
diff --git a/pyowm/webapi25/weather.py b/pyowm/webapi25/weather.py
index ddd9c39..cfd3962 100644
--- a/pyowm/webapi25/weather.py
+++ b/pyowm/webapi25/weather.py
@@ -163,13 +163,22 @@ class Weather(object):
         """
         return self._snow
 
-    def get_wind(self):
+    def get_wind(self, unit='meters_sec'):
         """Returns a dict containing wind info
-
+        
+        :param unit: the unit of measure for the wind values. May be:
+            '*meters_sec*' (default) or '*miles_hour*'
+        :type unit: str
         :returns: a dict containing wind info
 
         """
-        return self._wind
+        if unit == 'meters_sec':
+            return self._wind
+        elif unit == 'miles_hour':
+            wind_dict = {k: self._wind[k] for k in self._wind if self._wind[k] is not None}
+            return temputils.metric_wind_dict_to_imperial(wind_dict)
+        else:
+            raise ValueError("Invalid value for target wind conversion unit")
 
     def get_humidity(self):
         """Returns the atmospheric humidity as an int


Test patch:
diff --git a/tests/unit/utils/test_temputils.py b/tests/unit/utils/test_temputils.py
index e5bd43c..92a2aa9 100644
--- a/tests/unit/utils/test_temputils.py
+++ b/tests/unit/utils/test_temputils.py
@@ -48,3 +48,17 @@ class TestTempUtils(unittest.TestCase):
 
     def test_kelvin_to_fahrenheit_fails_with_negative_values(self):
         self.assertRaises(ValueError, temputils.kelvin_to_fahrenheit, -137.0)
+
+    def test_metric_wind_dict_to_imperial(self):
+        input = {
+            'speed': 2,
+            'gust': 3,
+            'deg': 7.89
+        }
+        expected = {
+            'speed': 4.47388,
+            'gust': 6.71082,
+            'deg': 7.89
+        }
+        result = temputils.metric_wind_dict_to_imperial(input)
+        self.assertEqual(expected, result)
diff --git a/tests/unit/webapi25/json_test_dumps.py b/tests/unit/webapi25/json_test_dumps.py
index df2d2cb..448f7e7 100644
--- a/tests/unit/webapi25/json_test_dumps.py
+++ b/tests/unit/webapi25/json_test_dumps.py
@@ -15,7 +15,7 @@ WEATHER_JSON_DUMP = '{"status": "Clouds", "visibility_distance": 1000, ' \
                     '{"press": 1030.119, "sea_level": 1038.589}, ' \
                     '"sunrise_time": 1378449600, "heat_index": 40.0, ' \
                     '"weather_icon_name": "04d", "humidity": 57, "wind": ' \
-                    '{"speed": 1.1, "deg": 252.002}}'
+                    '{"speed": 1.1, "deg": 252.002, "gust": 2.09}}'
 
 OBSERVATION_JSON_DUMP = '{"reception_time": 1234567, "Location": ' \
                         '{"country": "UK", "name": "test", "coordinates": ' \
diff --git a/tests/unit/webapi25/test_weather.py b/tests/unit/webapi25/test_weather.py
index a3ffa7c..9bb6319 100644
--- a/tests/unit/webapi25/test_weather.py
+++ b/tests/unit/webapi25/test_weather.py
@@ -6,6 +6,7 @@ import unittest
 from pyowm.webapi25.weather import Weather, weather_from_dictionary
 from pyowm.utils.timeformatutils import UTC
 from tests.unit.webapi25.json_test_dumps import WEATHER_JSON_DUMP
+from tests.unit.webapi25.xml_test_dumps import WEATHER_XML_DUMP
 from datetime import datetime
 
 
@@ -22,7 +23,8 @@ class TestWeather(unittest.TestCase):
     __test_clouds = 67
     __test_rain = {"all": 20}
     __test_snow = {"all": 0}
-    __test_wind = {"deg": 252.002, "speed": 1.100}
+    __test_wind = {"deg": 252.002, "speed": 1.100, "gust": 2.09}
+    __test_imperial_wind = {"deg": 252.002, "speed": 2.460634, "gust": 4.6752046}
     __test_humidity = 57
     __test_pressure = {"press": 1030.119, "sea_level": 1038.589}
     __test_temperature = {"temp": 294.199, "temp_kf": -1.899,
@@ -380,6 +382,21 @@ class TestWeather(unittest.TestCase):
         self.assertRaises(ValueError, Weather.get_temperature,
                           self.__test_instance, 'xyz')
 
+    def test_returning_different_units_for_wind_values(self):
+        result_imperial = self.__test_instance.get_wind(unit='miles_hour')
+        result_metric = self.__test_instance.get_wind(unit='meters_sec')
+        result_unspecified = self.__test_instance.get_wind()
+        self.assertEqual(result_unspecified, result_metric)
+        for item in self.__test_wind:
+            self.assertEqual(result_metric[item],
+                             self.__test_wind[item])
+            self.assertEqual(result_imperial[item],
+                             self.__test_imperial_wind[item])
+
+    def test_get_wind_fails_with_unknown_units(self):
+        self.assertRaises(ValueError, Weather.get_wind,
+                          self.__test_instance, 'xyz')
+
     # Test JSON and XML comparisons by ordering strings (this overcomes
     # interpeter-dependant serialization of XML/JSON objects)
 
diff --git a/tests/unit/webapi25/xml_test_dumps.py b/tests/unit/webapi25/xml_test_dumps.py
index c393de8..08088b7 100644
--- a/tests/unit/webapi25/xml_test_dumps.py
+++ b/tests/unit/webapi25/xml_test_dumps.py
@@ -6,7 +6,7 @@ LOCATION_XML_DUMP = """<?xml version='1.0' encoding='utf8'?>
 <location xmlns:l="http://github.com/csparpa/pyowm/tree/master/pyowm/webapi25/xsd/location.xsd"><l:name>London</l:name><l:coordinates><l:lon>12.3</l:lon><l:lat>43.7</l:lat></l:coordinates><l:ID>1234</l:ID><l:country>UK</l:country></location>"""
 
 WEATHER_XML_DUMP = """<?xml version='1.0' encoding='utf8'?>
-<weather xmlns:w="http://github.com/csparpa/pyowm/tree/master/pyowm/webapi25/xsd/weather.xsd"><w:status>Clouds</w:status><w:weather_code>804</w:weather_code><w:rain><w:all>20</w:all></w:rain><w:snow><w:all>0</w:all></w:snow><w:pressure><w:press>1030.119</w:press><w:sea_level>1038.589</w:sea_level></w:pressure><w:sunrise_time>1378449600</w:sunrise_time><w:weather_icon_name>04d</w:weather_icon_name><w:clouds>67</w:clouds><w:temperature><w:temp_kf>-1.899</w:temp_kf><w:temp_min>294.199</w:temp_min><w:temp>294.199</w:temp><w:temp_max>296.098</w:temp_max></w:temperature><w:detailed_status>Overcast clouds</w:detailed_status><w:reference_time>1378459200</w:reference_time><w:sunset_time>1378496400</w:sunset_time><w:humidity>57</w:humidity><w:wind><w:speed>1.1</w:speed><w:deg>252.002</w:deg></w:wind><w:visibility_distance>1000</w:visibility_distance><w:dewpoint>300.0</w:dewpoint><w:humidex>298.0</w:humidex><w:heat_index>40.0</w:heat_index></weather>"""
+<weather xmlns:w="http://github.com/csparpa/pyowm/tree/master/pyowm/webapi25/xsd/weather.xsd"><w:status>Clouds</w:status><w:weather_code>804</w:weather_code><w:rain><w:all>20</w:all></w:rain><w:snow><w:all>0</w:all></w:snow><w:pressure><w:press>1030.119</w:press><w:sea_level>1038.589</w:sea_level></w:pressure><w:sunrise_time>1378449600</w:sunrise_time><w:weather_icon_name>04d</w:weather_icon_name><w:clouds>67</w:clouds><w:temperature><w:temp_kf>-1.899</w:temp_kf><w:temp_min>294.199</w:temp_min><w:temp>294.199</w:temp><w:temp_max>296.098</w:temp_max></w:temperature><w:detailed_status>Overcast clouds</w:detailed_status><w:reference_time>1378459200</w:reference_time><w:sunset_time>1378496400</w:sunset_time><w:humidity>57</w:humidity><w:wind><w:speed>1.1</w:speed><w:deg>252.002</w:deg><w:gust>2.09</w:gust></w:wind><w:visibility_distance>1000</w:visibility_distance><w:dewpoint>300.0</w:dewpoint><w:humidex>298.0</w:humidex><w:heat_index>40.0</w:heat_index></weather>"""
 
 OBSERVATION_XML_DUMP = """<?xml version='1.0' encoding='utf8'?>
 <observation xmlns:o="http://github.com/csparpa/pyowm/tree/master/pyowm/webapi25/xsd/observation.xsd"><o:reception_time>1234567</o:reception_time><o:location><o:name>test</o:name><o:coordinates><o:lon>12.3</o:lon><o:lat>43.7</o:lat></o:coordinates><o:ID>987</o:ID><o:country>UK</o:country></o:location><o:weather><o:status>Clouds</o:status><o:weather_code>804</o:weather_code><o:rain><o:all>20</o:all></o:rain><o:snow><o:all>0</o:all></o:snow><o:pressure><o:press>1030.119</o:press><o:sea_level>1038.589</o:sea_level></o:pressure><o:sunrise_time>1378449600</o:sunrise_time><o:weather_icon_name>04d</o:weather_icon_name><o:clouds>67</o:clouds><o:temperature><o:temp_kf>-1.899</o:temp_kf><o:temp_min>294.199</o:temp_min><o:temp>294.199</o:temp><o:temp_max>296.098</o:temp_max></o:temperature><o:detailed_status>Overcast clouds</o:detailed_status><o:reference_time>1378459200</o:reference_time><o:sunset_time>1378496400</o:sunset_time><o:humidity>57</o:humidity><o:wind><o:speed>1.1</o:speed><o:deg>252.002</o:deg></o:wind><o:visibility_distance>1000</o:visibility_distance><o:dewpoint>300.0</o:dewpoint><o:humidex>298.0</o:humidex><o:heat_index>296.0</o:heat_index></o:weather></observation>"""