11from enum import Enum
2- from json import JSONDecodeError
32
43import requests as req
54
65from .__version__ import __version__
7- from .exceptions import APIException , GatewayTimeoutException
6+ from .exceptions import APIException
87
98
109def _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