Degree Days
Weather Data for Energy Saving
The Python client library is stable and well tested. It is the recommended way to access the API from Python.
The client library is available from PyPI at https://pypi.org/project/degreedays/ and Anaconda at https://anaconda.org/bizee/degreedays. From the command line you can install it via pip
with:
pip install degreedays
Or via conda
with:
conda install -c bizee degreedays
Command-line installation is the recommended approach, but you can also download the source from PyPI and unpack it yourself.
The Python client library is currently at version 1.4. For changes since earlier versions, please see the release history. If you have an earlier version installed already, you can upgrade it from the command line via pip
with:
pip install degreedays --upgrade
or via conda
with:
conda update -c bizee degreedays
You'll need:
degreedays
Python client library mentioned above.Here's a simple example showing how to fetch the latest 12 months of 65F-base-temperature heating degree days from an automatically-selected weather station near Times Square, New York (US zip code 10036). The HDD figures are output to the command line:
from degreedays.api import DegreeDaysApi, AccountKey, SecurityKey from degreedays.api.data import DataSpec, Calculation, Temperature, \ DatedBreakdown, Period, Location, DataSpecs, LocationDataRequest api = DegreeDaysApi.fromKeys( AccountKey(yourStringAccountKey), SecurityKey(yourStringSecurityKey)) hddSpec = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.fahrenheit(65)), DatedBreakdown.monthly(Period.latestValues(12))) request = LocationDataRequest( Location.postalCode('10036', 'US'), DataSpecs(hddSpec)) response = api.dataApi.getLocationData(request) hddData = response.dataSets[hddSpec] for v in hddData.values: print('%s: %g' % (v.firstDay, v.value))
Just swap in your API access keys (account key and security key) and the example code above should work right away.
But bear in mind that this example is just a starting point...
LocationDataRequest
is highly configurable:There are multiple ways to specify the various components of the LocationDataRequest
:
# some examples below need extra imports (as well as those in the main code sample above) from degreedays.geo import LongLat from degreedays.time import DayRange, DayRanges, DayOfWeek, StartOfMonth, StartOfYear from datetime import date from degreedays.api.data import AverageBreakdown, TemperatureUnit # The Location can be a station ID, or a "geographic location" for which the API # will select the best weather station to use automatically: Location.stationId('EGLL') Location.longLat(LongLat(-135.23127, 43.92135)) # needs extra import at top Location.postalCode('10036', 'US') # Calculation: Calculation.heatingDegreeDays(Temperature.fahrenheit(65)) Calculation.coolingDegreeDays(Temperature.celsius(21.5)) # A TimeSeriesCalculation is for time-series data like hourly temperature data: TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.CELSIUS) # needs extra import at top # Period of coverage: Period.latestValues(12) Period.dayRange(DayRange(date(2018, 1, 1), date(2018, 12, 31))) # needs extra imports at top # By default you may get back less data than you requested if there aren't # enough records to generate a full set for your specified location. But you # can specify a minimum if you would rather have a failure than too little data. # For example, if you want 60 values but will accept 36+: Period.latestValues(60, minimumNumberOfValuesOrNone=36) # Or if you want 10 specific years, and would rather a failure than anything less: Period.dayRange(DayRange(date(2010, 1, 1), date(2019, 12, 31)), # needs extra imports at top minimumDayRangeOrNone=DayRange(date(2010, 1, 1), date(2019, 12, 31))) # DatedBreakdown (using a period like those specified above): DatedBreakdown.daily(period) DatedBreakdown.weekly(period, firstDayOfWeek=DayOfWeek.MONDAY) # needs extra import at top DatedBreakdown.monthly(period) DatedBreakdown.monthly(period, startOfMonth=StartOfMonth(5)) # needs extra import at top DatedBreakdown.yearly(period) DatedBreakdown.yearly(period, startOfYear=StartOfYear(6, 22)) # needs extra import at top DatedBreakdown.custom(DayRanges( # needs extra import at top DayRange(date(2019, 12, 15), date(2020, 1, 12)), DayRange(date(2020, 1, 13), date(2020, 2, 17)), DayRange(date(2020, 2, 24), date(2020, 3, 18)))) # All the DatedBreakdown types let you specify allowPartialLatest=True, so you # can fetch time-series data that includes the current day so far. For example: DatedBreakdown.daily(period, allowPartialLatest=True) # AverageBreakdown has just one type at present, which specifies an average of # the data for the full calendar years specified by the period: AverageBreakdown.fullYears(period) # needs extra import at top # A DataSpec is a specification for a set of data, made up of # Calculation/Period/Breakdown objects like those specified above: DataSpec.dated(calculation, datedBreakdown) # for HDD or CDD DataSpec.average(calculation, averageBreakdown) # for average HDD or CDD DataSpec.timeSeries(timeSeriesCalculation, datedBreakdown) # for e.g. hourly temperature data # Putting all this together, here are a few example DataSpec items: hddSpec = DataSpec.dated( Calculation.heatingDegreeDays(Temperature.fahrenheit(60)), DatedBreakdown.monthly(Period.latestValues(12))) cddSpec = DataSpec.dated( Calculation.coolingDegreeDays(Temperature.celsius(21)), DatedBreakdown.daily(Period.dayRange( DayRange(date(2019, 1, 1), date(2019, 12, 31))))) averageHddSpec = DataSpec.average( Calculation.heatingDegreeDays(Temperature.celsius(15.5)), AverageBreakdown.fullYears(Period.latestValues(5))) hourlyTemperaturesSpec = DataSpec.timeSeries( TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.FAHRENHEIT), DatedBreakdown.daily(Period.latestValues(30))) hourlyTemperaturesIncludingTodaySpec = DataSpec.timeSeries( TimeSeriesCalculation.hourlyTemperature(TemperatureUnit.CELSIUS), DatedBreakdown.daily(Period.latestValues(31), allowPartialLatest=True)) # DataSpec objects go into a DataSpecs object: DataSpecs(hddSpec) # HDD only (with only one base temperature and breakdown) DataSpecs(hddSpec, cddSpec) # HDD and CDD DataSpecs(hddSpec, cddSpec, hourlyTemperaturesSpec) # HDD, CDD, and hourly temperature data DataSpecs(listOfUpTo120DataSpecObjects) # e.g. HDD & CDD with a range of base temperatures or breakdowns
Note above how you can specify multiple sets of data (e.g. HDD, CDD, hourly temperature data) to be fetched in a single request. This is faster and uses less request units than making multiple requests for data from the same location.
LocationDataResponse
contains more than just data:It also contains information about the weather station(s) used to generate the returned data. For example, if you request data from a geographic location initially, you might want to use the station ID to fetch updates later:
print(response.stationId)
LocationInfoRequest
and two-stage data fetching:Except in name, LocationInfoRequest
looks exactly the same as LocationDataRequest
. Using it is almost identical too:
from degreedays.api.data import LocationInfoRequest # Assuming location, dataSpecs, and api are already defined (see examples above) locationInfoResponse = api.dataApi.getLocationInfo(LocationInfoRequest(location, dataSpecs)) print(locationInfoResponse.stationId)
Each API request you make uses request units that count against your hourly rate limit. A big LocationDataRequest
can use a lot of request units, but a LocationInfoRequest
will only ever use one. See the sign-up page for more on request units and rate limits.
But LocationInfoResponse
does not contain any data (it has no dataSets
property)... It's typically used for two-stage data fetching, which can be useful if you are dealing with geographic locations (postal/zip codes, or longitude/latitude positions), but storing data by station ID (returned in every successful response). For this use-case, two-stage data fetching can help you save request units (see right) and improve the efficiency of your system by avoiding re-fetching data that you already have stored.
When you want to add a new location into your system (e.g. if a new user signs up with a new address), you can do the following:
LocationInfoRequest
with the geographic location and the specification of the data that you want. This will only take one request unit. You won't get any data back, but you should get the station ID that the system would use for an equivalent LocationDataRequest
. If you already have data stored for that station ID, use it; if not, progress to stage 2.LocationDataRequest
with the station ID from stage 1. This will take more request units (more the more data you fetch), but using the station ID will save a request unit, such that your two-stage fetch will use the same number of request units in total as you would have used if you had made a LocationDataRequest
with the geographic location in the first place.Two-stage fetching will only improve efficiency and save request units if/when you have enough geographic locations in your system that some of them end up sharing weather stations. But, if that is the case, two-stage fetching can really help your system to scale well as more and more geographic locations are added in.
RegressionRequest
for advanced regression functionality:With RegressionRequest
you can send energy data to the API so it can test thousands of regressions and find the HDD and/or CDD base temperatures that give the best statistical fit. We cover this fully in our docs on the API's regression functionality.
Error handling would be important for production code:
The Python client library tries its best to fail fast on invalid input. We'd rather give you a ValueError
immediately than use up your rate limit with invalid API requests that are destined to fail.
This is mainly relevant if you are dealing with user input, particularly for:
[a-zA-Z0-9_-]{1,60}
);[- 0-9a-zA-Z]{1,16}
) and ISO country codes ([A-Z]{2}
); andAccountKey
and SecurityKey
objects do a kind of tolerant checking which is suitable for first-pass validation of user input.All the relevant methods will throw a ValueError
(or subclass) if they are passed an ID, code, or key that is clearly invalid. If you are dealing with user input, you might want to catch those exceptions explicitly as a means of validation.
DegreeDaysApiError
)All the exceptions that can arise from a remote call to the API servers extend from degreedays.api.DegreeDaysApiError
.
The methods that make a remote call to the API servers are accessible through degreedays.api.DegreeDaysApi
. At present the only such methods are DataApi.getLocationData(LocationDataRequest)
and DataApi.getLocationInfo(LocationInfoRequest)
. For example:
api = DegreeDaysApi.fromKeys( AccountKey(yourStringAccountKey), SecurityKey(yourStringSecurityKey)) response = api.dataApi.getLocationData(yourLocationDataRequest)
getLocationData
and getLocationInfo
can throw a range of subclasses of DegreeDaysApiError
:
degreedays.api.data.LocationError
– if the request fails because of problems relating to the specified Location
;degreedays.api.ServiceError
– if the request fails because of a problem with the API service (sorry!);degreedays.api.RateLimitError
– if you hit the rate limit for your account's plan, and need to wait a little while before it's reset (you can check .metadata.rateLimit.minutesToReset
on any successful response, or .responseMetadata.rateLimit.minutesToReset
on any RateLimitError
to see how long it will be until the next reset);degreedays.api.InvalidRequestError
– if the request that is sent to the API servers is invalid (e.g. if it is authenticated with invalid access keys);degreedays.api.TransportError
– if there's a problem sending the request to the API servers, or a problem getting the API's response back;degreedays.api.DegreeDaysApiError
– the superclass of all the errors listed above.There is also degreedays.api.data.SourceDataError
(another subclass of DegreeDaysApiError
), which can be thrown when you try to retrieve a DataSet
from the DataSets
object in the response. For example:
from degreedays.api.data import SourceDataError try: hddSet = response.dataSets[hddSpec] except SourceDataError: # hddSpec couldn't be fulfilled as there wasn't enough good temperature data # to calculate degree days covering the specified period.
Which, if any, of these exceptions you'll want to handle explicitly will depend on the nature of your application:
LocationError
explicitly so you can tell if a user entered an unrecognized or unsupported station ID or postal/zip code. But anything else might just be DegreeDaysApiError
as far as you are concerned – the request for data failed, and that might be all that matters.InvalidRequestError
explicitly so you can prompt the user to check their API access keys for typos.This isn't an error as such, but it's important to realize that, in certain circumstances, the API can return less data than you requested. For example, you might request 10 years of data for a certain weather station, but get only 5 years back if that's all the usable temperature data that the weather station has. Or you might request data up to and including yesterday, but get only data to the day before yesterday. (Note that you should never be able to get the data for yesterday until that day has finished in the location's local time zone, and it's best not to expect it until at least a couple of hours after that. More on update schedules here.)
There are clear rules about how and when the API can deliver less data than requested, and you can control this behaviour as well. See the Javadoc for DataApi.getLocationData(LocationDataRequest)
to find out more. (The notes further below explain how the Javadoc are a good source of detailed documentation for the Python library, as the Java and Python libraries mirror each other very closely.)
We've built the API and the Python client library for robustness and predictability:
None
have "OrNone" in their names.None
as an argument. Passing None
where it is not allowed will usually result in an immediate exception (fail fast).nan
, inf
, 0, −1, 9999 etc.The documentation for the Python client library is currently limited to this page and the source code (a documentation of sorts). But the Python library is very similar to the Java library, which has comprehensive documentation in its Javadoc – a good place to look if you want more detail on anything. All the public Python classes have an equivalent Java class, and the Java packages are mirrored by the Python packages/modules as follows:
net.degreedays.api
in Java is equivalent to degreedays.api
in Python;net.degreedays.api.data
in Java is equivalent to degreedays.api.data
in Python;net.degreedays.api.regression
in Java is equivalent to degreedays.api.regression
in Python;net.degreedays.geo
in Java is equivalent to degreedays.geo
in Python;net.degreedays.time
in Java is equivalent to degreedays.time
in Python.Virtually all the classes have the same names in Python as they do in Java... The main exception is the exceptions (no pun intended)... The Python exceptions mirror the Java exceptions, but their names end with Error
instead of Exception
. So:
DegreeDaysApiException
in Java is DegreeDaysApiError
in Python;TransportException
in Java is TransportError
in Python;LocationException
in Java is LocationError
in Python;The Javadoc has detailed notes about the different request options, the types of response data, and the exceptions, and the vast majority of the notes are equally applicable to Python as to Java.
There are separate docs covering the API's regression functionality.
Hopefully this page, the Python source, and the detailed Javadoc will be enough to get you going, but please do let us know if you get stuck – we'll be happy to help.
It is also worth reading the higher-level integration guide for tips on the various approaches to integrating with the API. We have helped a lot of businesses integrate their software with our API so we are very familiar with the patterns that work well for common use cases.
© 2008–2024 BizEE Software – About | Contact | Privacy | Free Website | API | Integration Guide | API FAQ | API Sign-Up