Skip to content

Commit f2cb247

Browse files
authored
Release 2.3.2 (#115)
* Set a test API key (the one in the API call examples from OWM API website) * Try to fix Travis Virtualenv support for Py32 * Fix again * Bump to version 2.3.1 * Don't commit coverage file * Dockerfile (#106) * First attempt * Guide * Fix Dockerfile and docs * Update docs * Change URL of documentation to comply with new readthedocs domain * Backporting from master branch * Refactor integration tests to use the API key specified in api_key.py * Fix ValueError then sunrise_time & sunset time is less than zero when there is a polar day * Fix ValueError then sunrise_time & sunset time is less than zero when there is a polar day * Fix ValueError then sunrise_time & sunset time is less than zero when there is a polar day * Fix ValueError then sunrise_time & sunset time is less than zero when there is a polar day * Fix ValueError then sunrise_time & sunset time is less than zero when there is a polar day * Messed up with Git... sunrise/sunset fix from #109 is OK now * Sometimes Wind data is None Hello ! I'm useing Homeassistant, and sometimes I got the following error: ``` Traceback (most recent call last): File "/home/titilambert/gits/domo/homeassitant/env/lib/python3.4/site-packages/homeassistant/helpers/entity_component.py", line 98, in _setup_platform discovery_info) File "/home/titilambert/gits/domo/homeassitant/env/lib/python3.4/site-packages/homeassistant/components/sensor/openweathermap.py", line 70, in setup_platform dev.append(OpenWeatherMapSensor(data, variable, unit)) File "/home/titilambert/gits/domo/homeassitant/env/lib/python3.4/site-packages/homeassistant/components/sensor/openweathermap.py", line 94, in __init__ self.update() File "/home/titilambert/gits/domo/homeassitant/env/lib/python3.4/site-packages/homeassistant/components/sensor/openweathermap.py", line 114, in update self.owa_client.update() File "/home/titilambert/gits/domo/homeassitant/env/lib/python3.4/site-packages/homeassistant/util/__init__.py", line 289, in wrapper result = method(*args, **kwargs) File "/home/titilambert/gits/domo/homeassitant/env/lib/python3.4/site-packages/homeassistant/components/sensor/openweathermap.py", line 179, in update self.longitude) File "/home/titilambert/gits/domo/homeassitant/config/deps/pyowm/webapi25/owm25.py", line 432, in three_hours_forecast_at_coords forecast = self._parsers['forecast'].parse_JSON(json_data) File "/home/titilambert/gits/domo/homeassitant/config/deps/pyowm/webapi25/forecastparser.py", line 66, in parse_JSON for item in d['list']] File "/home/titilambert/gits/domo/homeassitant/config/deps/pyowm/webapi25/forecastparser.py", line 66, in <listcomp> for item in d['list']] File "/home/titilambert/gits/domo/homeassitant/config/deps/pyowm/webapi25/weather.py", line 462, in weather_from_dictionary wind = d['wind'].copy() AttributeError: 'NoneType' object has no attribute 'copy' ``` This patch fixes this error * Regression test for #110 * update with new contributor * Fixes for #110 and #114 * Forgot... * Were missing * Bump to version 2.3.2
1 parent 12ae3b8 commit f2cb247

19 files changed

+233
-47
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ sphinx/_build/*
1010
.pydevproject
1111
.idea/*
1212
.tox/*
13+
.coverage

CONTRIBUTORS.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Code
88
* [liato] (https://github.com/liato)
99
* [Noid] (https://github.com/n0id)
1010
* [dphildebrandt] (https://github.com/dphildebrandt)
11+
* [titilambert] (https://github.com/titilambert)
1112

1213
Testing
1314
-------

Dockerfile

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
FROM ubuntu:15.10
2+
MAINTAINER Claudio Sparpaglione <[email protected]>
3+
4+
RUN apt-get update && \
5+
apt-get upgrade -y && \
6+
apt-get -y install software-properties-common && \
7+
RUN echo -ne '\n' | apt-add-repository ppa:fkrull/deadsnakes
8+
RUN apt-get update && \
9+
apt-get upgrade -y && \
10+
apt-get install python2.7 python3.2 python3.3 python-pip -y
11+
12+
ADD . /pyowm
13+
WORKDIR /pyowm
14+
15+
RUN pip install -r /pyowm/dev-requirements.txt
16+
17+
CMD tail -f /dev/null

docs/build.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ PyOWM release checklist
1212
* close milestone on github
1313
* tag release on github
1414
* generate and upload release on pypi
15+
* update docker image on DockerHub
1516

1617

1718
Filling in of main setup.py file

docs/docker.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Playing with Docker image locally
2+
=================================
3+
4+
Build the image
5+
---------------
6+
```
7+
cd <pyowm-root-dir>
8+
build -t pyowm:latest .
9+
```
10+
11+
12+
Start container
13+
---------------
14+
```
15+
docker run -d --name pyowm pyowm
16+
```
17+
18+
Run tests on Tox
19+
---------------
20+
```
21+
docker exec -ti pyowm tox
22+
```
23+
24+
25+
Releasing on DockerHub
26+
======================
27+
28+
Eg: for tagged version 2.3.1
29+
30+
```
31+
VERSION="2.3.1"
32+
33+
# Build and tag
34+
docker build -t csparpa/pyowm:${VERSION} .
35+
docker tag csparpa/pyowm:${VERSION} csparpa/pyowm:latest
36+
37+
# Push to DockerHub
38+
docker push csparpa/pyowm:${VERSION}
39+
docker push csparpa/pyowm:latest
40+
```

pyowm/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
from pyowm.utils import timeutils # Convenience import
1515

1616

17-
def OWM(API_key=None, version=constants.LATEST_OWM_API_VERSION,
17+
def OWM(API_key=constants.DEFAULT_API_KEY, version=constants.LATEST_OWM_API_VERSION,
1818
config_module=None, language=None, subscription_type=None):
1919
"""
2020
A parametrized factory method returning a global OWM instance that
2121
represents the desired OWM web API version (or the currently supported one
2222
if no version number is specified)
2323
24-
:param API_key: the OWM web API key (``None`` by default)
24+
:param API_key: the OWM web API key (defaults to a test value)
2525
:type API_key: str
2626
:param version: the OWM web API version. Defaults to ``None``, which means
2727
use the latest web API version

pyowm/constants.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
Constants for the PyOWM library
33
"""
44

5-
PYOWM_VERSION = '2.3.0'
5+
PYOWM_VERSION = '2.3.2'
66
LATEST_OWM_API_VERSION = '2.5'
7+
DEFAULT_API_KEY = 'b1b15e88fa797225412429c1c50c122a'

pyowm/webapi25/weather.py

+30-15
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ class Weather(object):
1717
1818
:param reference_time: GMT UNIX time of weather measurement
1919
:type reference_time: int
20-
:param sunset_time: GMT UNIX time of sunset
21-
:type sunset_time: int
22-
:param sunrise_time: GMT UNIX time of sunrise
23-
:type sunrise_time: int
20+
:param sunset_time: GMT UNIX time of sunset or None on polar days
21+
:type sunset_time: int or None
22+
:param sunrise_time: GMT UNIX time of sunrise or None on polar nights
23+
:type sunrise_time: int or None
2424
:param clouds: cloud coverage percentage
2525
:type clouds: int
2626
:param rain: precipitation info
@@ -64,10 +64,10 @@ def __init__(self, reference_time, sunset_time, sunrise_time, clouds, rain,
6464
raise ValueError("'reference_time' must be greater than 0")
6565
self._reference_time = reference_time
6666
if sunset_time < 0:
67-
raise ValueError("'sunset_time' must be greatear than 0")
67+
sunset_time = None
6868
self._sunset_time = sunset_time
6969
if sunrise_time < 0:
70-
raise ValueError("'sunrise_time' must be greatear than 0")
70+
sunrise_time = None
7171
self._sunrise_time = sunrise_time
7272
if clouds < 0:
7373
raise ValueError("'clouds' must be greater than 0")
@@ -115,10 +115,12 @@ def get_sunset_time(self, timeformat='unix'):
115115
'*unix*' (default) for UNIX time or '*iso*' for ISO8601-formatted
116116
string in the format ``YYYY-MM-DD HH:MM:SS+00``
117117
:type timeformat: str
118-
:returns: an int or a str
118+
:returns: an int or a str or None
119119
:raises: ValueError
120120
121121
"""
122+
if self._sunset_time is None:
123+
return None
122124
return timeformatutils.timeformat(self._sunset_time, timeformat)
123125

124126
def get_sunrise_time(self, timeformat='unix'):
@@ -128,10 +130,12 @@ def get_sunrise_time(self, timeformat='unix'):
128130
'*unix*' (default) for UNIX time or '*iso*' for ISO8601-formatted
129131
string in the format ``YYYY-MM-DD HH:MM:SS+00``
130132
:type timeformat: str
131-
:returns: an int or a str
133+
:returns: an int or a str or None
132134
:raises: ValueError
133135
134136
"""
137+
if self._sunrise_time is None:
138+
return None
135139
return timeformatutils.timeformat(self._sunrise_time, timeformat)
136140

137141
def get_clouds(self):
@@ -335,7 +339,7 @@ def _to_DOM(self):
335339
xmlutils.create_DOM_node_from_dict(self._pressure, "pressure",
336340
root_node)
337341
node_sunrise_time = ET.SubElement(root_node, "sunrise_time")
338-
node_sunrise_time.text = str(self._sunrise_time)
342+
node_sunrise_time.text = str(self._sunrise_time) if self._sunrise_time is not None else 'null'
339343
weather_icon_name_node = ET.SubElement(root_node, "weather_icon_name")
340344
weather_icon_name_node.text = self._weather_icon_name
341345
clouds_node = ET.SubElement(root_node, "clouds")
@@ -347,7 +351,7 @@ def _to_DOM(self):
347351
reference_time_node = ET.SubElement(root_node, "reference_time")
348352
reference_time_node.text = str(self._reference_time)
349353
sunset_time_node = ET.SubElement(root_node, "sunset_time")
350-
sunset_time_node.text = str(self._sunset_time)
354+
sunset_time_node.text = str(self._sunset_time) if self._sunset_time is not None else 'null'
351355
humidity_node = ET.SubElement(root_node, "humidity")
352356
humidity_node.text = str(self._humidity)
353357
xmlutils.create_DOM_node_from_dict(self._wind, "wind", root_node)
@@ -454,15 +458,20 @@ def weather_from_dictionary(d):
454458
if isinstance(d['rain'], int) or isinstance(d['rain'], float):
455459
rain = {'all': d['rain']}
456460
else:
457-
rain = d['rain'].copy()
461+
if d['rain'] is not None:
462+
rain = d['rain'].copy()
463+
else:
464+
rain = dict()
458465
else:
459466
rain = dict()
460467
# -- wind
461-
if 'wind' in d:
468+
if 'wind' in d and d['wind'] is not None:
462469
wind = d['wind'].copy()
463470
elif 'last' in d:
464-
if 'wind' in d['last']:
471+
if 'wind' in d['last'] and d['last']['wind'] is not None:
465472
wind = d['last']['wind'].copy()
473+
else:
474+
wind = dict()
466475
elif 'speed' in d:
467476
wind = dict(speed=d['speed'])
468477
else:
@@ -481,7 +490,10 @@ def weather_from_dictionary(d):
481490
if isinstance(d['snow'], int) or isinstance(d['snow'], float):
482491
snow = {'all': d['snow']}
483492
else:
484-
snow = d['snow'].copy()
493+
if d['snow'] is not None:
494+
snow = d['snow'].copy()
495+
else:
496+
snow = dict()
485497
else:
486498
snow = dict()
487499
# -- pressure
@@ -501,7 +513,10 @@ def weather_from_dictionary(d):
501513
pressure = {'press': atm_press, 'sea_level': sea_level_press}
502514
# -- temperature
503515
if 'temp' in d:
504-
temperature = d['temp'].copy()
516+
if d['temp'] is not None:
517+
temperature = d['temp'].copy()
518+
else:
519+
temperature = dict()
505520
elif 'main' in d and 'temp' in d['main']:
506521
temp = d['main']['temp']
507522
if 'temp_kf' in d['main']:
62.9 KB
Binary file not shown.

sphinx/doctrees/pyowm.caches.doctree

39.1 KB
Binary file not shown.

sphinx/doctrees/pyowm.commons.doctree

64.4 KB
Binary file not shown.
732 KB
Binary file not shown.

tests/functional/webapi25/api_key.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Put here your valid API key in order to run integration tests
2+
API_KEY = ''

tests/functional/webapi25/test_cache_webapi25.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pyowm.webapi25.owm25 import OWM25
99
from pyowm.caches.lrucache import LRUCache
1010
from pyowm.abstractions.owmcache import OWMCache
11+
from api_key import API_KEY
1112

1213

1314
class CacheWrapper(OWMCache):
@@ -43,7 +44,7 @@ class CacheTestWebAPI25(unittest.TestCase):
4344
def test_caching_prevents_API_calls(self):
4445
cache = LRUCache(20, 1000 * 60 * 60)
4546
wrapped_cache = CacheWrapper(cache)
46-
owm = OWM25(parsers, '5746e1a976021a0', wrapped_cache)
47+
owm = OWM25(parsers, API_KEY, wrapped_cache)
4748
self.assertFalse(wrapped_cache.last_request_was_hit())
4849
self.assertEqual(0, wrapped_cache.api_calls())
4950
owm.weather_at_place('London,uk') # Comes from OWM web API
@@ -72,7 +73,7 @@ def test_cache_limits(self):
7273
"""
7374
cache = LRUCache(3, 1000 * 60 * 60) # Only three cacheable elements!
7475
wrapped_cache = CacheWrapper(cache)
75-
owm = OWM25(parsers, '5746e1a976021a0', wrapped_cache)
76+
owm = OWM25(parsers, API_KEY, wrapped_cache)
7677
owm.weather_at_place('London,uk') # Comes from OWM web API
7778
owm.weather_at_place('Kiev') # Comes from OWM web API
7879
owm.weather_at_place('Madrid') # Comes from OWM web API
@@ -92,7 +93,7 @@ def test_caching_times(self):
9293
non-null cache.
9394
"""
9495
cache = LRUCache(20, 1000 * 60 * 60)
95-
owm = OWM25(parsers, '5746e1a976021a0', cache)
96+
owm = OWM25(parsers, API_KEY, cache)
9697
before_request = time()
9798
o1 = owm.weather_at_place('London,uk') # Comes from OWM web API
9899
after_request = time()
@@ -122,3 +123,7 @@ def test_caching_times(self):
122123
self.assertTrue(cache_hit_2_delay < req_delay)
123124
self.assertTrue(cache_hit_1_delay / req_delay < 1)
124125
self.assertTrue(cache_hit_2_delay / req_delay < 1)
126+
127+
128+
if __name__ == "__main__":
129+
unittest.main()

tests/functional/webapi25/test_cityidregistry_reads_fs.py

+3
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ def test_location_for(self):
6363
def test_location_for_fails_with_malformed_inputs(self):
6464
self.assertRaises(ValueError, CityIDRegistry.location_for,
6565
self._instance, '123abc')
66+
67+
if __name__ == "__main__":
68+
unittest.main()

tests/functional/webapi25/test_configuration_injection_webapi25.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'''
77
import unittest
88
import pyowm
9+
from api_key import API_KEY
910

1011

1112
class ConfigurationInjectionTestsWebAPI25(unittest.TestCase):
@@ -14,7 +15,7 @@ class ConfigurationInjectionTestsWebAPI25(unittest.TestCase):
1415
_non_existent_config_module_name = 'this_will_never_be_a_config_module'
1516

1617
def test(self):
17-
pyowm.OWM('�b02f5370d�76021a0', '2.5', self._config_module_name)
18+
pyowm.OWM(API_KEY, '2.5', self._config_module_name)
1819

1920
def test_library_is_instantiated_with_wrong_API_version(self):
2021
self.assertRaises(ValueError, pyowm.OWM, 'abcd', '0.0')
@@ -25,7 +26,7 @@ def test_library_is_instantiated_with_external_config(self):
2526
configuration
2627
"""
2728
try:
28-
pyowm.OWM('�b02f5370d�76021a0', '2.5', self._config_module_name)
29+
pyowm.OWM(API_KEY, '2.5', self._config_module_name)
2930
except Exception:
3031
self.fail("Error raised during library instantiation")
3132

@@ -34,7 +35,7 @@ def test_error_raised_when_providing_non_existent_external_config(self):
3435
Test that library instantiation raises an error when trying to inject
3536
a non-existent external configuration module
3637
"""
37-
self.assertRaises(Exception, pyowm.OWM, '�b02f5370d�76021a0', '2.5',
38+
self.assertRaises(Exception, pyowm.OWM, API_KEY, '2.5',
3839
self._non_existent_config_module_name)
3940

4041
def test_library_performs_API_calls_with_external_config(self):
@@ -45,8 +46,12 @@ def test_library_performs_API_calls_with_external_config(self):
4546
"""
4647
try:
4748
instance = \
48-
pyowm.OWM('�b02f5370d�76021a0', '2.5',
49+
pyowm.OWM(API_KEY, '2.5',
4950
self._config_module_name)
5051
except:
5152
self.fail("Error raised during library instantiation")
5253
self.assertRaises(Exception, instance.weather_at_place, 'London,uk')
54+
55+
56+
if __name__ == "__main__":
57+
unittest.main()

tests/functional/webapi25/test_integration_webapi25.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
from datetime import datetime
1010
from pyowm.webapi25.configuration25 import parsers
1111
from pyowm.webapi25.owm25 import OWM25
12+
from pyowm import constants
13+
from api_key import API_KEY
1214

1315

1416
class IntegrationTestsWebAPI25(unittest.TestCase):
1517

16-
__owm = OWM25(parsers, '�b02f5370d�76021a0')
18+
__owm = OWM25(parsers, API_KEY)
1719

1820
def test_is_API_online(self):
1921
self.assertTrue(self.__owm.is_API_online())
@@ -24,7 +26,7 @@ def test_weather_at_place(self):
2426
"""
2527
o1 = self.__owm.weather_at_place('London,uk')
2628
o2 = self.__owm.weather_at_place('Kiev')
27-
o3 = self.__owm.weather_at_place('QmFoPIlbf') # Shall be None
29+
o3 = self.__owm.weather_at_place('234g08n34gQmFoPIlbf') # Shall be None
2830
self.assertTrue(o1 is not None)
2931
self.assertTrue(o1.get_reception_time() is not None)
3032
loc = o1.get_location()
@@ -165,7 +167,7 @@ def test_three_hours_forecast(self):
165167
"""
166168
fc1 = self.__owm.three_hours_forecast("London,uk")
167169
fc2 = self.__owm.three_hours_forecast('Kiev')
168-
fc3 = self.__owm.three_hours_forecast('QmFoPIlbf') # Shall be None
170+
fc3 = self.__owm.three_hours_forecast('wg8934gnk3QmFoPIlbf') # Shall be None
169171
self.assertTrue(fc1)
170172
f1 = fc1.get_forecast()
171173
self.assertTrue(f1 is not None)
@@ -251,7 +253,7 @@ def test_daily_forecast(self):
251253
"""
252254
fc1 = self.__owm.daily_forecast("London,uk")
253255
fc2 = self.__owm.daily_forecast('Kiev')
254-
fc3 = self.__owm.daily_forecast('QmFoPIlbf') # Shall be None
256+
fc3 = self.__owm.daily_forecast('34hg08n34knQmFoPIlbf') # Shall be None
255257
self.assertTrue(fc1)
256258
f1 = fc1.get_forecast()
257259
self.assertTrue(f1 is not None)

0 commit comments

Comments
 (0)