Skip to content

Commit 5cd3471

Browse files
committed
Add pagination support, change default emission factor to 2019
1 parent 45da9fe commit 5cd3471

File tree

5 files changed

+43
-45
lines changed

5 files changed

+43
-45
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ singularity = SingularityAPI('API_KEY')
1919
2020
end = datetime.utcnow()
2121
start = end - timedelta(hours=4)
22-
events = singularity.search_region_events(
22+
events, pagination = singularity.search_region_events(
2323
Regions.ISONE,
2424
'carbon_intensity',
2525
start.isoformat() + 'Z',

docs/source/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
# -- Project information -----------------------------------------------------
1919

2020
project = 'Singularity Python SDK'
21-
copyright = '2020, Ryan Baker'
22-
author = 'Ryan Baker'
21+
copyright = '2020, Singularity Energy'
22+
author = 'Jeff Burka'
2323

2424
# The full version, including alpha/beta/rc tags
25-
release = '0.0.7'
25+
release = '0.0.9'
2626

2727

2828
# -- General configuration ---------------------------------------------------

docs/source/getting_started.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Getting Started
33

44
Before you can start using Singularity's API, you need to have an API key.
55

6-
To get an API key, you can reach out to Ryan Baker (ryan.baker@singularity.energy) and ask for one.
6+
To get an API key, you can reach out to Jeff Burka (jeffrey.burka@singularity.energy) and ask for one.
77

88
Once you have an API key, you can use the python SDK::
99

singularity/__version__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
__title__ = 'singularity_energy'
22
__description__ = 'Python SDK for api.singularity.energy'
33
__url__ = 'https://www.singularity.energy'
4-
__version__ = '0.0.8'
5-
__author__ = 'Ryan Baker <ryan.baker@singularity.energy>'
4+
__version__ = '0.0.9'
5+
__author__ = 'Jeff Burka <jeffrey.burka@singularity.energy>'
66
__license__ = 'Apache 2.0'
77
__copyright__ = 'Copyright 2020 Singularity Energy'

singularity/api.py

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
from enum import Enum
2-
from json import JSONDecodeError
32

43
import requests as req
54

65
from .__version__ import __version__
7-
from .exceptions import APIException, GatewayTimeoutException
6+
from .exceptions import APIException
87

98

109
def _handle_error(error_res):
@@ -56,19 +55,16 @@ class SingularityAPI(object):
5655

5756
BASE_URL = 'https://api.singularity.energy'
5857

59-
6058
def __init__(self, api_key):
6159
self.api_key = api_key
6260

63-
6461
def _get_headers(self):
6562
return {
6663
'X-Api-Key': self.api_key,
6764
'User-Agent': 'Python/Singularity SDK v{}'.format(__version__)
6865
}
6966

70-
71-
def _format_search_url(self, region, event_type, start, end, postal_code, filter_):
67+
def _format_search_url(self, region, event_type, start, end, postal_code, filter_, page, per_page):
7268
if region is None and postal_code is not None:
7369
url = '{}/v1/region_events/search?postal_code={}&start={}&end={}&event_type={}'\
7470
.format(
@@ -78,9 +74,6 @@ def _format_search_url(self, region, event_type, start, end, postal_code, filter
7874
end,
7975
event_type
8076
)
81-
if filter_ is not None:
82-
url += '&filter={}'.format(filter_)
83-
return url
8477
else:
8578
url = '{}/v1/region_events/search?region={}&start={}&end={}&event_type={}'\
8679
.format(
@@ -90,18 +83,20 @@ def _format_search_url(self, region, event_type, start, end, postal_code, filter
9083
end,
9184
event_type
9285
)
93-
if filter_ is not None:
94-
url += '&filter={}'.format(filter_)
95-
return url
96-
86+
if filter_ is not None:
87+
url += '&filter={}'.format(filter_)
88+
if page is not None:
89+
url += '&page={}'.format(page)
90+
if per_page is not None:
91+
url += '&per_page={}'.format(page)
92+
return url
9793

9894
def _format_find_url(self, dedup_key):
9995
return '{}/v1/region_events/{}'.format(
10096
self.BASE_URL,
10197
dedup_key
10298
)
10399

104-
105100
def find_region_event(self, dedup_key):
106101
"""Find an event by its dedup key.
107102
@@ -113,7 +108,7 @@ def find_region_event(self, dedup_key):
113108
singularity = SingularityAPI('API_KEY')
114109
nowstring = dt.utcnow().isoformat() + 'Z'
115110
before = (dt.utcnow() - td(minutes=10)).isoformat() + 'Z'
116-
events = singularity.search_region_events(
111+
events, pagination = singularity.search_region_events(
117112
Regions.ISONE,
118113
'generated_fuel_mix',
119114
before,
@@ -134,7 +129,6 @@ def find_region_event(self, dedup_key):
134129
else:
135130
_handle_error(res)
136131

137-
138132
def find_all_region_events(self, dedup_keys):
139133
"""Find all events with the given dedup keys.
140134
@@ -150,8 +144,7 @@ def find_all_region_events(self, dedup_keys):
150144
else:
151145
_handle_error(res)
152146

153-
154-
def search_region_events_for_postal_code(self, postal_code, event_type, start, end, filter_=None):
147+
def search_region_events_for_postal_code(self, postal_code, event_type, start, end, filter_=None, page=None, per_page=None):
155148
"""Search for region events by a postal code instead of a region.
156149
157150
Currently only ISONE, NYISO, and PJM have mappings from postal code <> ISO region. Any postal codes
@@ -162,18 +155,20 @@ def search_region_events_for_postal_code(self, postal_code, event_type, start, e
162155
:param start: an iso8601 datetime string with a timezone
163156
:param end: an iso8601 datetime string with a timezone
164157
:param filter_: (optional) a string in the format key:value to filter the events for
165-
:returns: an array of data region events
158+
:param page: (optional) a number to indicate which page of results to return
159+
:param per_page: (optional) the number of events to return per page.
160+
default is 300, maximum is 1,000
161+
:returns: an array of region events, and a dict with pagination information
166162
:raises: APIException if there is a bad response code
167163
"""
168-
url = self._format_search_url(None, event_type, start, end, postal_code=postal_code, filter_=filter_)
164+
url = self._format_search_url(None, event_type, start, end, postal_code, filter_, page, per_page)
169165
res = req.get(url, headers=self._get_headers())
170166
if res.status_code == 200:
171-
return res.json()['data']
167+
return res.json()['data'], res.json()['meta']['pagination']
172168
else:
173169
_handle_error(res)
174170

175-
176-
def search_region_events(self, region, event_type, start, end, filter_=None):
171+
def search_region_events(self, region, event_type, start, end, filter_=None, page=None, per_page=None):
177172
"""Search for region events over a period of time.
178173
179174
Currently, the supported regions are:
@@ -196,27 +191,29 @@ def search_region_events(self, region, event_type, start, end, filter_=None):
196191
s = SingularityAPI('API_KEY')
197192
region_string = 'PJM'
198193
region = Regions(region_string) # or you can use Regions.PJM
199-
s.search_region_events(region, 'carbon_intensity', '2020-01-20T00:00:00Z', '2020-01-21T00:00:00Z')
194+
events, pagination = s.search_region_events(region, 'carbon_intensity', '2020-01-20T00:00:00Z', '2020-01-21T00:00:00Z')
200195
201196
202197
:param region: a region from the Regions enum
203198
:param event_type: an event type to search for
204199
:param start: an iso8601 datetime string with a timezone
205200
:param end: an iso8601 datetime string with a timezone
206-
:param filter: a string in the format key:value to filter the events for.
201+
:param filter_: a string in the format key:value to filter the events for.
207202
a filter either looks in `meta` or `data` objects. Filters must be
208203
in the format of `data.some_key:some_value`
209-
:returns: an array of region events
204+
:param page: (optional) a number to indicate which page of results to return
205+
:param per_page: (optional) the number of events to return per page.
206+
default is 300, maximum is 1,000
207+
:returns: an array of region events, and a dict with pagination information
210208
:raises: APIException if there is a bad response code
211209
"""
212-
url = self._format_search_url(region, event_type, start, end, None, filter_)
210+
url = self._format_search_url(region, event_type, start, end, None, filter_, page, per_page)
213211
res = req.get(url, headers=self._get_headers())
214212
if res.status_code == 200:
215-
return res.json()['data']
213+
return res.json()['data'], res.json()['meta']['pagination']
216214
else:
217215
_handle_error(res)
218216

219-
220217
def get_all_emission_factors(self):
221218
"""Fetch all the emission factors available in the emissions API.
222219
@@ -229,8 +226,7 @@ def get_all_emission_factors(self):
229226
else:
230227
_handle_error(res)
231228

232-
233-
def calculate_generated_carbon_intensity(self, genfuelmix, region, source='EGRID_2018'):
229+
def calculate_generated_carbon_intensity(self, genfuelmix, region, source='EGRID_2019'):
234230
"""Calculate the intensity for a given genfuelmix.
235231
236232
The generated rate is calculated by multiplying the generated MW for each fuel type
@@ -240,7 +236,7 @@ def calculate_generated_carbon_intensity(self, genfuelmix, region, source='EGRID
240236
241237
:param genfuelmix: the `data` part of a `generated_fuel_mix` event
242238
:param region: a region from the Regions enum
243-
:param source: (default: EGRID_2018) a string representing the source data to use
239+
:param source: (default: EGRID_2019) a string representing the source data to use
244240
for the emission factors.
245241
:returns: the rate of carbon emissions for a genfuelmix in lbs/MWh
246242
:raises: an APIException if a bad response code is returned
@@ -257,13 +253,12 @@ def calculate_generated_carbon_intensity(self, genfuelmix, region, source='EGRID
257253
else:
258254
_handle_error(res)
259255

260-
261-
def calculate_marginal_carbon_intensity(self, fuelmix_percents, region, source='EGRID_2018'):
256+
def calculate_marginal_carbon_intensity(self, fuelmix_percents, region, source='EGRID_2019'):
262257
"""Calculate the intensity for a given fuelmix percentage
263258
264259
:param fuelmix_percents: the `data` part of a `marginal_fuel_mix` event
265260
:param region: a region from the Regions enum
266-
:param source: (default: EGRID_2018) a string representing the source data to use
261+
:param source: (default: EGRID_2019) a string representing the source data to use
267262
for the emission factors
268263
:returns: the rate of carbon emissions for a fuelmix in lbs/MWh
269264
:raises: an APIException if a bad response code is returned
@@ -280,19 +275,22 @@ def calculate_marginal_carbon_intensity(self, fuelmix_percents, region, source='
280275
else:
281276
_handle_error(res)
282277

283-
284-
def latest_region_events(self, region_or_postal_code, event_type='carbon_intensity'):
278+
def latest_region_events(self, region_or_postal_code, event_type='carbon_intensity', emission_factor=None):
285279
"""Get the latest region events for a region or postal code.
286280
287281
:param region_or_postal_code: the region or postal code to query. Either a region from
288282
the Regions enum or a string of a postal code
289-
:param event_type: (default: carbon_intensity) the event type to query for
283+
:param event_type: (default: carbon_intensity) the forecast event type to query for
290284
currently only carbon_intensity and generated_fuel_mix are supported
285+
:param emission_factor: (default: EGRID_u2018) he emission factor used to calculate carbon intensity
286+
possible values: EGRID_u2018, EGRID_2018, EGRID_u2019, EGRID_2019
291287
:returns: a dict of the latest event and forecasts for the event
292288
:raises: an APIException if a bad response code is returned
293289
"""
294290

295291
url = self.BASE_URL + '/v1/region_events/{}/latest?event_type={}'.format(region_or_postal_code, event_type)
292+
if emission_factor is not None:
293+
url += '&emission_factor={}'.format(emission_factor)
296294
res = req.get(url, headers=self._get_headers())
297295
if res.status_code == 200:
298296
return res.json()['data']

0 commit comments

Comments
 (0)