1
1
from enum import Enum
2
- from json import JSONDecodeError
3
2
4
3
import requests as req
5
4
6
5
from .__version__ import __version__
7
- from .exceptions import APIException , GatewayTimeoutException
6
+ from .exceptions import APIException
8
7
9
8
10
9
def _handle_error (error_res ):
@@ -56,19 +55,16 @@ class SingularityAPI(object):
56
55
57
56
BASE_URL = 'https://api.singularity.energy'
58
57
59
-
60
58
def __init__ (self , api_key ):
61
59
self .api_key = api_key
62
60
63
-
64
61
def _get_headers (self ):
65
62
return {
66
63
'X-Api-Key' : self .api_key ,
67
64
'User-Agent' : 'Python/Singularity SDK v{}' .format (__version__ )
68
65
}
69
66
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 ):
72
68
if region is None and postal_code is not None :
73
69
url = '{}/v1/region_events/search?postal_code={}&start={}&end={}&event_type={}' \
74
70
.format (
@@ -78,9 +74,6 @@ def _format_search_url(self, region, event_type, start, end, postal_code, filter
78
74
end ,
79
75
event_type
80
76
)
81
- if filter_ is not None :
82
- url += '&filter={}' .format (filter_ )
83
- return url
84
77
else :
85
78
url = '{}/v1/region_events/search?region={}&start={}&end={}&event_type={}' \
86
79
.format (
@@ -90,18 +83,20 @@ def _format_search_url(self, region, event_type, start, end, postal_code, filter
90
83
end ,
91
84
event_type
92
85
)
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
97
93
98
94
def _format_find_url (self , dedup_key ):
99
95
return '{}/v1/region_events/{}' .format (
100
96
self .BASE_URL ,
101
97
dedup_key
102
98
)
103
99
104
-
105
100
def find_region_event (self , dedup_key ):
106
101
"""Find an event by its dedup key.
107
102
@@ -113,7 +108,7 @@ def find_region_event(self, dedup_key):
113
108
singularity = SingularityAPI('API_KEY')
114
109
nowstring = dt.utcnow().isoformat() + 'Z'
115
110
before = (dt.utcnow() - td(minutes=10)).isoformat() + 'Z'
116
- events = singularity.search_region_events(
111
+ events, pagination = singularity.search_region_events(
117
112
Regions.ISONE,
118
113
'generated_fuel_mix',
119
114
before,
@@ -134,7 +129,6 @@ def find_region_event(self, dedup_key):
134
129
else :
135
130
_handle_error (res )
136
131
137
-
138
132
def find_all_region_events (self , dedup_keys ):
139
133
"""Find all events with the given dedup keys.
140
134
@@ -150,8 +144,7 @@ def find_all_region_events(self, dedup_keys):
150
144
else :
151
145
_handle_error (res )
152
146
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 ):
155
148
"""Search for region events by a postal code instead of a region.
156
149
157
150
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
162
155
:param start: an iso8601 datetime string with a timezone
163
156
:param end: an iso8601 datetime string with a timezone
164
157
: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
166
162
:raises: APIException if there is a bad response code
167
163
"""
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 )
169
165
res = req .get (url , headers = self ._get_headers ())
170
166
if res .status_code == 200 :
171
- return res .json ()['data' ]
167
+ return res .json ()['data' ], res . json ()[ 'meta' ][ 'pagination' ]
172
168
else :
173
169
_handle_error (res )
174
170
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 ):
177
172
"""Search for region events over a period of time.
178
173
179
174
Currently, the supported regions are:
@@ -196,27 +191,29 @@ def search_region_events(self, region, event_type, start, end, filter_=None):
196
191
s = SingularityAPI('API_KEY')
197
192
region_string = 'PJM'
198
193
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')
200
195
201
196
202
197
:param region: a region from the Regions enum
203
198
:param event_type: an event type to search for
204
199
:param start: an iso8601 datetime string with a timezone
205
200
: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.
207
202
a filter either looks in `meta` or `data` objects. Filters must be
208
203
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
210
208
:raises: APIException if there is a bad response code
211
209
"""
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 )
213
211
res = req .get (url , headers = self ._get_headers ())
214
212
if res .status_code == 200 :
215
- return res .json ()['data' ]
213
+ return res .json ()['data' ], res . json ()[ 'meta' ][ 'pagination' ]
216
214
else :
217
215
_handle_error (res )
218
216
219
-
220
217
def get_all_emission_factors (self ):
221
218
"""Fetch all the emission factors available in the emissions API.
222
219
@@ -229,8 +226,7 @@ def get_all_emission_factors(self):
229
226
else :
230
227
_handle_error (res )
231
228
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' ):
234
230
"""Calculate the intensity for a given genfuelmix.
235
231
236
232
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
240
236
241
237
:param genfuelmix: the `data` part of a `generated_fuel_mix` event
242
238
: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
244
240
for the emission factors.
245
241
:returns: the rate of carbon emissions for a genfuelmix in lbs/MWh
246
242
:raises: an APIException if a bad response code is returned
@@ -257,13 +253,12 @@ def calculate_generated_carbon_intensity(self, genfuelmix, region, source='EGRID
257
253
else :
258
254
_handle_error (res )
259
255
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' ):
262
257
"""Calculate the intensity for a given fuelmix percentage
263
258
264
259
:param fuelmix_percents: the `data` part of a `marginal_fuel_mix` event
265
260
: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
267
262
for the emission factors
268
263
:returns: the rate of carbon emissions for a fuelmix in lbs/MWh
269
264
:raises: an APIException if a bad response code is returned
@@ -280,19 +275,22 @@ def calculate_marginal_carbon_intensity(self, fuelmix_percents, region, source='
280
275
else :
281
276
_handle_error (res )
282
277
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 ):
285
279
"""Get the latest region events for a region or postal code.
286
280
287
281
:param region_or_postal_code: the region or postal code to query. Either a region from
288
282
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
290
284
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
291
287
:returns: a dict of the latest event and forecasts for the event
292
288
:raises: an APIException if a bad response code is returned
293
289
"""
294
290
295
291
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 )
296
294
res = req .get (url , headers = self ._get_headers ())
297
295
if res .status_code == 200 :
298
296
return res .json ()['data' ]
0 commit comments