Skip to content

Commit

Permalink
Release 2.3.2 (#115)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
csparpa authored Jul 6, 2016
1 parent 12ae3b8 commit f2cb247
Show file tree
Hide file tree
Showing 19 changed files with 233 additions and 47 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ sphinx/_build/*
.pydevproject
.idea/*
.tox/*
.coverage
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Code
* [liato] (https://github.com/liato)
* [Noid] (https://github.com/n0id)
* [dphildebrandt] (https://github.com/dphildebrandt)
* [titilambert] (https://github.com/titilambert)

Testing
-------
Expand Down
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM ubuntu:15.10
MAINTAINER Claudio Sparpaglione <[email protected]>

RUN apt-get update && \
apt-get upgrade -y && \
apt-get -y install software-properties-common && \
RUN echo -ne '\n' | apt-add-repository ppa:fkrull/deadsnakes
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install python2.7 python3.2 python3.3 python-pip -y

ADD . /pyowm
WORKDIR /pyowm

RUN pip install -r /pyowm/dev-requirements.txt

CMD tail -f /dev/null
1 change: 1 addition & 0 deletions docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ PyOWM release checklist
* close milestone on github
* tag release on github
* generate and upload release on pypi
* update docker image on DockerHub


Filling in of main setup.py file
Expand Down
40 changes: 40 additions & 0 deletions docs/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Playing with Docker image locally
=================================

Build the image
---------------
```
cd <pyowm-root-dir>
build -t pyowm:latest .
```


Start container
---------------
```
docker run -d --name pyowm pyowm
```

Run tests on Tox
---------------
```
docker exec -ti pyowm tox
```


Releasing on DockerHub
======================

Eg: for tagged version 2.3.1

```
VERSION="2.3.1"
# Build and tag
docker build -t csparpa/pyowm:${VERSION} .
docker tag csparpa/pyowm:${VERSION} csparpa/pyowm:latest
# Push to DockerHub
docker push csparpa/pyowm:${VERSION}
docker push csparpa/pyowm:latest
```
4 changes: 2 additions & 2 deletions pyowm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
from pyowm.utils import timeutils # Convenience import


def OWM(API_key=None, version=constants.LATEST_OWM_API_VERSION,
def OWM(API_key=constants.DEFAULT_API_KEY, version=constants.LATEST_OWM_API_VERSION,
config_module=None, language=None, subscription_type=None):
"""
A parametrized factory method returning a global OWM instance that
represents the desired OWM web API version (or the currently supported one
if no version number is specified)
:param API_key: the OWM web API key (``None`` by default)
:param API_key: the OWM web API key (defaults to a test value)
:type API_key: str
:param version: the OWM web API version. Defaults to ``None``, which means
use the latest web API version
Expand Down
3 changes: 2 additions & 1 deletion pyowm/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
Constants for the PyOWM library
"""

PYOWM_VERSION = '2.3.0'
PYOWM_VERSION = '2.3.2'
LATEST_OWM_API_VERSION = '2.5'
DEFAULT_API_KEY = 'b1b15e88fa797225412429c1c50c122a'
45 changes: 30 additions & 15 deletions pyowm/webapi25/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ class Weather(object):
:param reference_time: GMT UNIX time of weather measurement
:type reference_time: int
:param sunset_time: GMT UNIX time of sunset
:type sunset_time: int
:param sunrise_time: GMT UNIX time of sunrise
:type sunrise_time: int
:param sunset_time: GMT UNIX time of sunset or None on polar days
:type sunset_time: int or None
:param sunrise_time: GMT UNIX time of sunrise or None on polar nights
:type sunrise_time: int or None
:param clouds: cloud coverage percentage
:type clouds: int
:param rain: precipitation info
Expand Down Expand Up @@ -64,10 +64,10 @@ def __init__(self, reference_time, sunset_time, sunrise_time, clouds, rain,
raise ValueError("'reference_time' must be greater than 0")
self._reference_time = reference_time
if sunset_time < 0:
raise ValueError("'sunset_time' must be greatear than 0")
sunset_time = None
self._sunset_time = sunset_time
if sunrise_time < 0:
raise ValueError("'sunrise_time' must be greatear than 0")
sunrise_time = None
self._sunrise_time = sunrise_time
if clouds < 0:
raise ValueError("'clouds' must be greater than 0")
Expand Down Expand Up @@ -115,10 +115,12 @@ def get_sunset_time(self, timeformat='unix'):
'*unix*' (default) for UNIX time or '*iso*' for ISO8601-formatted
string in the format ``YYYY-MM-DD HH:MM:SS+00``
:type timeformat: str
:returns: an int or a str
:returns: an int or a str or None
:raises: ValueError
"""
if self._sunset_time is None:
return None
return timeformatutils.timeformat(self._sunset_time, timeformat)

def get_sunrise_time(self, timeformat='unix'):
Expand All @@ -128,10 +130,12 @@ def get_sunrise_time(self, timeformat='unix'):
'*unix*' (default) for UNIX time or '*iso*' for ISO8601-formatted
string in the format ``YYYY-MM-DD HH:MM:SS+00``
:type timeformat: str
:returns: an int or a str
:returns: an int or a str or None
:raises: ValueError
"""
if self._sunrise_time is None:
return None
return timeformatutils.timeformat(self._sunrise_time, timeformat)

def get_clouds(self):
Expand Down Expand Up @@ -335,7 +339,7 @@ def _to_DOM(self):
xmlutils.create_DOM_node_from_dict(self._pressure, "pressure",
root_node)
node_sunrise_time = ET.SubElement(root_node, "sunrise_time")
node_sunrise_time.text = str(self._sunrise_time)
node_sunrise_time.text = str(self._sunrise_time) if self._sunrise_time is not None else 'null'
weather_icon_name_node = ET.SubElement(root_node, "weather_icon_name")
weather_icon_name_node.text = self._weather_icon_name
clouds_node = ET.SubElement(root_node, "clouds")
Expand All @@ -347,7 +351,7 @@ def _to_DOM(self):
reference_time_node = ET.SubElement(root_node, "reference_time")
reference_time_node.text = str(self._reference_time)
sunset_time_node = ET.SubElement(root_node, "sunset_time")
sunset_time_node.text = str(self._sunset_time)
sunset_time_node.text = str(self._sunset_time) if self._sunset_time is not None else 'null'
humidity_node = ET.SubElement(root_node, "humidity")
humidity_node.text = str(self._humidity)
xmlutils.create_DOM_node_from_dict(self._wind, "wind", root_node)
Expand Down Expand Up @@ -454,15 +458,20 @@ def weather_from_dictionary(d):
if isinstance(d['rain'], int) or isinstance(d['rain'], float):
rain = {'all': d['rain']}
else:
rain = d['rain'].copy()
if d['rain'] is not None:
rain = d['rain'].copy()
else:
rain = dict()
else:
rain = dict()
# -- wind
if 'wind' in d:
if 'wind' in d and d['wind'] is not None:
wind = d['wind'].copy()
elif 'last' in d:
if 'wind' in d['last']:
if 'wind' in d['last'] and d['last']['wind'] is not None:
wind = d['last']['wind'].copy()
else:
wind = dict()
elif 'speed' in d:
wind = dict(speed=d['speed'])
else:
Expand All @@ -481,7 +490,10 @@ def weather_from_dictionary(d):
if isinstance(d['snow'], int) or isinstance(d['snow'], float):
snow = {'all': d['snow']}
else:
snow = d['snow'].copy()
if d['snow'] is not None:
snow = d['snow'].copy()
else:
snow = dict()
else:
snow = dict()
# -- pressure
Expand All @@ -501,7 +513,10 @@ def weather_from_dictionary(d):
pressure = {'press': atm_press, 'sea_level': sea_level_press}
# -- temperature
if 'temp' in d:
temperature = d['temp'].copy()
if d['temp'] is not None:
temperature = d['temp'].copy()
else:
temperature = dict()
elif 'main' in d and 'temp' in d['main']:
temp = d['main']['temp']
if 'temp_kf' in d['main']:
Expand Down
Binary file added sphinx/doctrees/pyowm.abstractions.doctree
Binary file not shown.
Binary file added sphinx/doctrees/pyowm.caches.doctree
Binary file not shown.
Binary file added sphinx/doctrees/pyowm.commons.doctree
Binary file not shown.
Binary file added sphinx/doctrees/pyowm.webapi25.doctree
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/functional/webapi25/api_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Put here your valid API key in order to run integration tests
API_KEY = ''
11 changes: 8 additions & 3 deletions tests/functional/webapi25/test_cache_webapi25.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pyowm.webapi25.owm25 import OWM25
from pyowm.caches.lrucache import LRUCache
from pyowm.abstractions.owmcache import OWMCache
from api_key import API_KEY


class CacheWrapper(OWMCache):
Expand Down Expand Up @@ -43,7 +44,7 @@ class CacheTestWebAPI25(unittest.TestCase):
def test_caching_prevents_API_calls(self):
cache = LRUCache(20, 1000 * 60 * 60)
wrapped_cache = CacheWrapper(cache)
owm = OWM25(parsers, '5746e1a976021a0', wrapped_cache)
owm = OWM25(parsers, API_KEY, wrapped_cache)
self.assertFalse(wrapped_cache.last_request_was_hit())
self.assertEqual(0, wrapped_cache.api_calls())
owm.weather_at_place('London,uk') # Comes from OWM web API
Expand Down Expand Up @@ -72,7 +73,7 @@ def test_cache_limits(self):
"""
cache = LRUCache(3, 1000 * 60 * 60) # Only three cacheable elements!
wrapped_cache = CacheWrapper(cache)
owm = OWM25(parsers, '5746e1a976021a0', wrapped_cache)
owm = OWM25(parsers, API_KEY, wrapped_cache)
owm.weather_at_place('London,uk') # Comes from OWM web API
owm.weather_at_place('Kiev') # Comes from OWM web API
owm.weather_at_place('Madrid') # Comes from OWM web API
Expand All @@ -92,7 +93,7 @@ def test_caching_times(self):
non-null cache.
"""
cache = LRUCache(20, 1000 * 60 * 60)
owm = OWM25(parsers, '5746e1a976021a0', cache)
owm = OWM25(parsers, API_KEY, cache)
before_request = time()
o1 = owm.weather_at_place('London,uk') # Comes from OWM web API
after_request = time()
Expand Down Expand Up @@ -122,3 +123,7 @@ def test_caching_times(self):
self.assertTrue(cache_hit_2_delay < req_delay)
self.assertTrue(cache_hit_1_delay / req_delay < 1)
self.assertTrue(cache_hit_2_delay / req_delay < 1)


if __name__ == "__main__":
unittest.main()
3 changes: 3 additions & 0 deletions tests/functional/webapi25/test_cityidregistry_reads_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ def test_location_for(self):
def test_location_for_fails_with_malformed_inputs(self):
self.assertRaises(ValueError, CityIDRegistry.location_for,
self._instance, '123abc')

if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
'''
import unittest
import pyowm
from api_key import API_KEY


class ConfigurationInjectionTestsWebAPI25(unittest.TestCase):
Expand All @@ -14,7 +15,7 @@ class ConfigurationInjectionTestsWebAPI25(unittest.TestCase):
_non_existent_config_module_name = 'this_will_never_be_a_config_module'

def test(self):
pyowm.OWM('�b02f5370d�76021a0', '2.5', self._config_module_name)
pyowm.OWM(API_KEY, '2.5', self._config_module_name)

def test_library_is_instantiated_with_wrong_API_version(self):
self.assertRaises(ValueError, pyowm.OWM, 'abcd', '0.0')
Expand All @@ -25,7 +26,7 @@ def test_library_is_instantiated_with_external_config(self):
configuration
"""
try:
pyowm.OWM('�b02f5370d�76021a0', '2.5', self._config_module_name)
pyowm.OWM(API_KEY, '2.5', self._config_module_name)
except Exception:
self.fail("Error raised during library instantiation")

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

def test_library_performs_API_calls_with_external_config(self):
Expand All @@ -45,8 +46,12 @@ def test_library_performs_API_calls_with_external_config(self):
"""
try:
instance = \
pyowm.OWM('�b02f5370d�76021a0', '2.5',
pyowm.OWM(API_KEY, '2.5',
self._config_module_name)
except:
self.fail("Error raised during library instantiation")
self.assertRaises(Exception, instance.weather_at_place, 'London,uk')


if __name__ == "__main__":
unittest.main()
10 changes: 6 additions & 4 deletions tests/functional/webapi25/test_integration_webapi25.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
from datetime import datetime
from pyowm.webapi25.configuration25 import parsers
from pyowm.webapi25.owm25 import OWM25
from pyowm import constants
from api_key import API_KEY


class IntegrationTestsWebAPI25(unittest.TestCase):

__owm = OWM25(parsers, '�b02f5370d�76021a0')
__owm = OWM25(parsers, API_KEY)

def test_is_API_online(self):
self.assertTrue(self.__owm.is_API_online())
Expand All @@ -24,7 +26,7 @@ def test_weather_at_place(self):
"""
o1 = self.__owm.weather_at_place('London,uk')
o2 = self.__owm.weather_at_place('Kiev')
o3 = self.__owm.weather_at_place('QmFoPIlbf') # Shall be None
o3 = self.__owm.weather_at_place('234g08n34gQmFoPIlbf') # Shall be None
self.assertTrue(o1 is not None)
self.assertTrue(o1.get_reception_time() is not None)
loc = o1.get_location()
Expand Down Expand Up @@ -165,7 +167,7 @@ def test_three_hours_forecast(self):
"""
fc1 = self.__owm.three_hours_forecast("London,uk")
fc2 = self.__owm.three_hours_forecast('Kiev')
fc3 = self.__owm.three_hours_forecast('QmFoPIlbf') # Shall be None
fc3 = self.__owm.three_hours_forecast('wg8934gnk3QmFoPIlbf') # Shall be None
self.assertTrue(fc1)
f1 = fc1.get_forecast()
self.assertTrue(f1 is not None)
Expand Down Expand Up @@ -251,7 +253,7 @@ def test_daily_forecast(self):
"""
fc1 = self.__owm.daily_forecast("London,uk")
fc2 = self.__owm.daily_forecast('Kiev')
fc3 = self.__owm.daily_forecast('QmFoPIlbf') # Shall be None
fc3 = self.__owm.daily_forecast('34hg08n34knQmFoPIlbf') # Shall be None
self.assertTrue(fc1)
f1 = fc1.get_forecast()
self.assertTrue(f1 is not None)
Expand Down
Loading

0 comments on commit f2cb247

Please sign in to comment.