Skip to content

Commit

Permalink
Merge branch 'master' into update_versioneer
Browse files Browse the repository at this point in the history
  • Loading branch information
EJEP committed Feb 9, 2024
2 parents 5e30a57 + 305446f commit c07b274
Show file tree
Hide file tree
Showing 23 changed files with 187 additions and 184 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.md
Expand Up @@ -7,11 +7,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+ Update versioneer
+ Add pythons 3.9, 3.10, 3.11, 3.12 to tests and setup.py.
+ Remove support for python < 3.8
+ Remove deprecated `new_old` and `future_old` functions.
+ Add `__str__` functions to `Timestep`, `Element`, `Day`, `Site`
+ Add element to `Forecast` to track if forecast is daily or 3 hourly
+ Change `id` variable in `Forecast`, `Observation`, `Site` to `location_id`.
+ Change `id` variable in `Element` to `field_code`.

## [0.9.8] - 2020-07-03

+ Remove f-string in test

## [0.9.7] - 2020-07-03

+ Bugfix for `get_observation_sites`
Expand Down Expand Up @@ -141,4 +146,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [0.1] - 2014-07-16

+ Initial commit and license.
+ Initial commit and license.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -46,7 +46,7 @@ conn = datapoint.connection(api_key="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee")
site = conn.get_nearest_forecast_site(51.500728, -0.124626)

# Get a forecast for my nearest site with 3 hourly timesteps
forecast = conn.get_forecast_for_site(site.id, "3hourly")
forecast = conn.get_forecast_for_site(site.location_id, "3hourly")

# Get the current timestep from the forecast
current_timestep = forecast.now()
Expand Down
21 changes: 18 additions & 3 deletions datapoint/Day.py
@@ -1,6 +1,21 @@
class Day():
def __init__(self, api_key=""):
self.api_key = api_key

def __init__(self):
self.date = None
self.timesteps = []

def __str__(self):
day_str = ''

date_part = 'Date: ' + str(self.date) + '\n\n'
day_str += date_part

day_str += 'Timesteps: \n\n'
try:
for timestep in self.timesteps:
day_str += str(timestep)
day_str += '\n'

except TypeError:
day_str += 'No timesteps'

return day_str
7 changes: 5 additions & 2 deletions datapoint/Element.py
@@ -1,9 +1,12 @@
class Element():
def __init__(self, id=None, value=None, units=None):
def __init__(self, field_code=None, value=None, units=None):

self.id = id
self.field_code = field_code
self.value = value
self.units = units

# For elements which can also have a text value
self.text = None

def __str__(self):
return str(self.value) + ' ' + str(self.units)
119 changes: 9 additions & 110 deletions datapoint/Forecast.py
@@ -1,17 +1,17 @@
import datetime
from datapoint.exceptions import APIException

class Forecast():
def __init__(self, api_key=""):
self.api_key = api_key

class Forecast():
def __init__(self, frequency=""):
self.frequency = frequency
self.data_date = None
self.continent = None
self.country = None
self.name = None
self.longitude = None
self.latitude = None
self.id = None
self.location_id = None
self.elevation = None
self.days = []

Expand Down Expand Up @@ -67,19 +67,20 @@ def at_datetime(self, target):

for day in self.days:
for timestep in day.timesteps:
# Calculate the difference between the target time and the timestep.
# Calculate the difference between the target time and the
# timestep.
td = target - timestep.date

# Find the timestep which is further from the target than the
# previous one. Return the previous timestep
if abs(td.total_seconds()) > abs(prev_td.total_seconds()):
# We are further from the target
return prev_ts
elif abs(td.total_seconds()) < 5400 and num_timesteps == 8:
if abs(td.total_seconds()) < 5400 and num_timesteps == 8:
# if we are past the final timestep, and it is a 3 hourly
# forecast, check that we are within 90 minutes of it
return timestep
elif abs(td.total_seconds()) < 21600 and num_timesteps == 2:
if abs(td.total_seconds()) < 21600 and num_timesteps == 2:
# if we are past the final timestep, and it is a daily
# forecast, check that we are within 6 hours of it
return timestep
Expand All @@ -94,113 +95,11 @@ def now(self):
d = datetime.datetime.now(tz=self.days[0].date.tzinfo)
return self.at_datetime(d)

def now_old(self):
"""
Function to return just the current timestep from this forecast
"""

# From the comments in issue 19: forecast.days[0] is dated for the
# previous day shortly after midnight

now = None
# Set the time now to be in the same time zone as the first timestep in
# the forecast. This shouldn't cause problems with daylight savings as
# the change is far enough after midnight.
d = datetime.datetime.now(tz=self.days[0].date.tzinfo)
# d is something like datetime.datetime(2019, 1, 19, 17, 5, 28, 337439)
# d.replace(...) is datetime.datetime(2019, 1, 19, 0, 0)
# for_total_seconds is then: datetime.timedelta(seconds=61528,
# microseconds=337439)
# In this example, this is (17*60*60) + (5*60) + 28 = 61528
# this is the number of seconds through the day
for_total_seconds = d - \
d.replace(hour=0, minute=0, second=0, microsecond=0)

# In the example time,
# for_total_seconds.total_seconds() = 61528 + 0.337439
# This is the number of seconds after midnight
# msm is then the number of minutes after midnight
msm = for_total_seconds.total_seconds() / 60

# If the date now and the date in the forecast are the same, proceed
if self.days[0].date.strftime("%Y-%m-%dZ") == d.strftime("%Y-%m-%dZ"):
# We have determined that the date in the forecast and the date now
# are the same.
#
# Now, test if timestep.name is larger than the number of minutes
# since midnight for each timestep.
# The timestep we keep is the one with the largest timestep.name
# which is less than the number of minutes since midnight
for timestep in self.days[0].timesteps:
if timestep.name > msm:

# break here stops the for loop
break
# now is assigned to the last timestep that did not break the
# loop
now = timestep
return now
# Bodge to get around problems near midnight:
# Previous method does not account for the end of the month. The test
# trying to be evaluated is that the absolute difference between the
# last timestep of the first day and the current time is less than 4
# hours. 4 hours is because the final timestep of the previous day is
# for 21:00
elif abs(self.days[0].timesteps[-1].date - d).total_seconds() < 14400:
# This is verbose to check that the returned data makes sense
timestep_to_return = self.days[0].timesteps[-1]

return timestep_to_return
else:
return False


def future(self,in_days=0,in_hours=0,in_minutes=0,in_seconds=0):
def future(self, in_days=0, in_hours=0, in_minutes=0, in_seconds=0):
"""Return the closest timestep to a date in a given amount of time"""

d = datetime.datetime.now(tz=self.days[0].date.tzinfo)
target = d + datetime.timedelta(days=in_days, hours=in_hours,
minutes=in_minutes, seconds=in_seconds)

return self.at_datetime(target)

def future_old(self,in_days=None,in_hours=None,in_minutes=None,in_seconds=None):
"""
Function to return a future timestep
"""
future = None

# Initialize variables to 0
dd, hh, mm, ss = [0 for i in range(4)]
if (in_days != None):
dd = dd + in_days
if (in_hours != None):
hh = hh + in_hours
if (in_minutes != None):
mm = mm + in_minutes
if (in_seconds != None):
ss = ss + in_seconds

# Set the hours, minutes and seconds from now (minus the days)
dnow = datetime.datetime.utcnow() # Now
d = dnow + \
datetime.timedelta(hours=hh, minutes=mm, seconds = ss)
# Time from midnight
for_total_seconds = d - \
d.replace(hour=0, minute=0, second=0, microsecond=0)

# Convert into minutes since midnight
try:
msm = for_total_seconds.total_seconds()/60.
except:
# For versions before 2.7
msm = self.timedelta_total_seconds(for_total_seconds)/60.

if (dd<len(self.days)):
for timestep in self.days[dd].timesteps:
if timestep.name >= msm:
future = timestep
return future
else:
print('ERROR: requested date is outside the forecast range selected,' + str(len(self.days)))
return False
19 changes: 9 additions & 10 deletions datapoint/Manager.py
Expand Up @@ -247,7 +247,7 @@ def get_forecast_sites(self):
for jsoned in data['Locations']['Location']:
site = Site()
site.name = jsoned['name']
site.id = jsoned['id']
site.location_id = jsoned['id']
site.latitude = jsoned['latitude']
site.longitude = jsoned['longitude']

Expand Down Expand Up @@ -304,7 +304,7 @@ def get_nearest_forecast_site(self, latitude, longitude):
float(longitude),
float(latitude))

if ((distance == None) or (new_distance < distance)):
if ((distance is None) or (new_distance < distance)):
distance = new_distance
nearest = site

Expand All @@ -325,15 +325,15 @@ def get_forecast_for_site(self, site_id, frequency="daily"):
A frequency of "3hourly" will return 8 timesteps:
0, 180, 360 ... 1260 (minutes since midnight UTC)
"""
data = self.__call_api(site_id, {"res":frequency})
data = self.__call_api(site_id, {"res": frequency})
params = data['SiteRep']['Wx']['Param']
forecast = Forecast()
forecast = Forecast(frequency=frequency)

# If the 'Location' key is missing, there is no data for the site,
# raise an error.
if 'Location' not in data['SiteRep']['DV']:
err_string = ('DataPoint has not returned any data for the'
'requested site.' )
'requested site.')
raise APIException(err_string)

# Check if the other keys we need are in the data returned from the
Expand All @@ -359,7 +359,7 @@ def get_forecast_for_site(self, site_id, frequency="daily"):
forecast.latitude = data['SiteRep']['DV']['Location']['lat']

if 'i' in data['SiteRep']['DV']['Location']:
forecast.id = data['SiteRep']['DV']['Location']['i']
forecast.location_id = data['SiteRep']['DV']['Location']['i']

if 'elevation' in data['SiteRep']['DV']['Location']:
forecast.elevation = data['SiteRep']['DV']['Location']['elevation']
Expand Down Expand Up @@ -456,7 +456,6 @@ def get_forecast_for_site(self, site_id, frequency="daily"):

return forecast


def get_observation_sites(self):
"""
This function returns a list of Site objects for which observations are available.
Expand All @@ -468,7 +467,7 @@ def get_observation_sites(self):
for jsoned in data['Locations']['Location']:
site = Site()
site.name = jsoned['name']
site.id = jsoned['id']
site.location_id = jsoned['id']
site.latitude = jsoned['latitude']
site.longitude = jsoned['longitude']

Expand Down Expand Up @@ -528,7 +527,7 @@ def get_observations_for_site(self, site_id, frequency='hourly'):
Returns hourly observations for the previous 24 hours
"""

data = self.__call_api(site_id,{"res":frequency}, OBSERVATION_URL)
data = self.__call_api(site_id,{"res": frequency}, OBSERVATION_URL)

params = data['SiteRep']['Wx']['Param']
observation = Observation()
Expand All @@ -554,7 +553,7 @@ def get_observations_for_site(self, site_id, frequency='hourly'):
observation.latitude = data['SiteRep']['DV']['Location']['lat']

if 'i' in data['SiteRep']['DV']['Location']:
observation.id = data['SiteRep']['DV']['Location']['i']
observation.location_id = data['SiteRep']['DV']['Location']['i']

if 'elevation' in data['SiteRep']['DV']['Location']:
observation.elevation = data['SiteRep']['DV']['Location']['elevation']
Expand Down
11 changes: 4 additions & 7 deletions datapoint/Observation.py
@@ -1,23 +1,20 @@
import datetime

class Observation():
def __init__(self, api_key=""):
self.api_key = api_key

def __init__(self):
self.data_date = None
self.continent = None
self.country = None
self.name = None
self.longitude = None
self.latitude = None
self.id = None
self.location_id = None
self.elevation = None
# Stores a list of observations in days
self.days = []

def now(self):
"""
Return the final timestep available. This is the most recent observation.
Return the final timestep available. This is the most recent
observation.
"""

return self.days[-1].timesteps[-1]
14 changes: 10 additions & 4 deletions datapoint/Site.py
@@ -1,12 +1,18 @@
class Site():
def __init__(self, api_key=""):
self.api_key = api_key

def __init__(self):
self.name = None
self.id = None
self.location_id = None
self.elevation = None
self.latitude = None
self.longitude = None
self.nationalPark = None
self.region = None
self.unitaryAuthArea = None

def __str__(self):
site_string = ''
for attr, value in self.__dict__.items():
to_append = attr + ': ' + str(value) + '\n'
site_string += to_append

return site_string
11 changes: 8 additions & 3 deletions datapoint/Timestep.py
@@ -1,9 +1,7 @@
from .Element import Element

class Timestep():
def __init__(self, api_key=""):
self.api_key = api_key

def __init__(self):
self.name = None
self.date = None
self.weather = None
Expand All @@ -29,3 +27,10 @@ def elements(self):
elements = [el[1] for el in self.__dict__.items() if isinstance(el[1], Element)]

return elements

def __str__(self):
timestep_string = ''
for attr, value in self.__dict__.items():
to_append = attr + ': ' + str(value) + '\n'
timestep_string += to_append
return timestep_string
2 changes: 1 addition & 1 deletion datapoint/regions/RegionManager.py
Expand Up @@ -50,7 +50,7 @@ def get_all_regions(self):
regions = []
for location in response['Locations']['Location']:
region = Site()
region.id = location['@id']
region.location_id = location['@id']
region.region = location['@name']
region.name = REGION_NAMES[location['@name']]
regions.append(region)
Expand Down

0 comments on commit c07b274

Please sign in to comment.