@@ -47,17 +47,26 @@ class PVLive:
4747 proxies : Optional[Dict]
4848 Optionally specify a Dict of proxies for http and https requests in the format:
4949 {"http": "<address>", "https": "<address>"}
50+ ssl_verify : Bool
51+ Set to False to disable SSL cert checks when querying the API. Useful if e.g. your company
52+ network/VPN has issues propagating SSL certs. Defaults to True. Not recommended to set to
53+ False!
54+ domain_url : Literal["api.pvlive.uk", "api.solar.sheffield.ac.uk", "api0.solar.sheffield.ac.uk"]
55+ Used to switch between the production PV_Live API on GCP and the test/FOF server on-prem.
56+ gsp_boundaries_version : str
57+ Some capacity datasets (by GSP) are encoded with the version of the GSP boundaries used.
5058 """
5159 def __init__ (
5260 self ,
5361 retries : int = 3 ,
5462 proxies : Optional [Dict ] = None ,
5563 ssl_verify : bool = True ,
5664 domain_url : Literal [
57- "api0.solar.sheffield.ac .uk" ,
65+ "api.pvlive .uk" ,
5866 "api.solar.sheffield.ac.uk" ,
59- "api.pvlive.uk"
60- ] = "api.solar.sheffield.ac.uk"
67+ "api0.solar.sheffield.ac.uk" ,
68+ ] = "api.pvlive.uk" ,
69+ gsp_boundaries_version : str = "20250109" ,
6170 ):
6271 valid_domain_urls = [
6372 "api0.solar.sheffield.ac.uk" ,
@@ -79,7 +88,9 @@ def __init__(
7988 self .pes_list = self ._get_pes_list ()
8089 self .gsp_ids = self .gsp_list .gsp_id .dropna ().astype (int64 ).unique ()
8190 self .pes_ids = self .pes_list .pes_id .dropna ().astype (int64 ).unique ()
91+ self .deployment_datasets = None
8292 self .deployment_releases = None
93+ self .gsp_boundaries_version = gsp_boundaries_version
8394
8495 def _get_gsp_list (self ):
8596 """Fetch the GSP list from the API and convert to Pandas DataFrame."""
@@ -94,15 +105,15 @@ def _get_pes_list(self):
94105 return pd .DataFrame (response ["data" ], columns = response ["meta" ])
95106
96107 def _get_deployment_releases (self ):
97- """Get a list of deployment releases as datestamps (YYYYMMDD)."""
98- if self .deployment_releases is None :
108+ """
109+ Get the available deployment releases as a list of datestamps (YYYYMMDD) alongside a dict of
110+ files available in each release.
111+ """
112+ if self .deployment_datasets is None :
99113 url = f"{ self .domain_url } /capacity/"
100- response = self ._fetch_url (url , parse_json = False )
101- soup = BeautifulSoup (response .content , "html.parser" )
102- releases = [r ["href" ].strip ("/" ) for r in soup .find_all ("a" , href = True )
103- if re .match (r"[0-9]{8}/" , r ["href" ])]
104- self .deployment_releases = sorted (releases , reverse = True )
105- return self .deployment_releases
114+ self .deployment_datasets = self ._fetch_url (url , parse_json = True )
115+ self .deployment_releases = sorted (list (self .deployment_datasets .keys ()), reverse = True )
116+ return self .deployment_datasets , self .deployment_releases
106117
107118 def _get_deployment_filenames (self , release ):
108119 """Get a list of filenames for a given release."""
@@ -115,7 +126,7 @@ def _get_deployment_filenames(self, release):
115126
116127 def _validate_deployment_inputs (self , region , include_history , by_system_size , release ):
117128 """Validate input parameters to `deployment()`."""
118- releases = self ._get_deployment_releases ()
129+ deployment_datasets , releases = self ._get_deployment_releases ()
119130 if not isinstance (region , str ):
120131 raise TypeError ("`region` must be a string." )
121132 supported_regions = ["gsp" , "llsoa" ]
@@ -165,22 +176,23 @@ def deployment(self,
165176 GSPs/llsoa and dc_capacity_mwp.
166177 """
167178 self ._validate_deployment_inputs (region , include_history , by_system_size , release )
168- releases = self ._get_deployment_releases ()
179+ deployment_datasets , releases = self ._get_deployment_releases ()
169180 release = releases [release ] if isinstance (release , int ) else release
170- filenames = self . _get_deployment_filenames ( release )
171- region_ = "20220314_GSP " if region == "gsp" else region
181+ filenames = list ( deployment_datasets [ release ]. keys () )
182+ region_ = f" { self . gsp_boundaries_version } _GSP " if region == "gsp" else region
172183 history_ = "_and_month" if include_history else ""
173184 system_size_ = "_and_system_size" if by_system_size else ""
174185 filename_ending = f"_capacity_by_{ region_ } { history_ } { system_size_ } .csv.gz"
175186 filename = [f for f in filenames if f .endswith (filename_ending )][0 ]
176- url = f" { self . domain_url } /capacity/ { release } / { filename } "
187+ url = deployment_datasets [ release ][ filename ]
177188 kwargs = dict (parse_dates = ["install_month" ]) if include_history else {}
178189 response = self ._fetch_url (url , parse_json = False )
179190 mock_file = BytesIO (response .content )
180191 deployment_data = pd .read_csv (mock_file , compression = {"method" : "gzip" }, ** kwargs )
181192 deployment_data .insert (0 , "release" , release )
182193 deployment_data .rename (columns = {"dc_capacity_MWp" : "dc_capacity_mwp" }, inplace = True )
183194 deployment_data .system_count = deployment_data .system_count .astype ("Int64" )
195+ deployment_data .dropna (how = "any" , inplace = True )
184196 return deployment_data
185197
186198 def latest (self ,
@@ -532,7 +544,7 @@ def _validate_inputs(self, entity_type="gsp", entity_id=0, extra_fields="", peri
532544 elif entity_type == "gsp" :
533545 if entity_id not in self .gsp_ids :
534546 raise PVLiveException (f"The gsp_id { entity_id } was not found." )
535- periods = [5 , 30 ]
547+ periods = [30 ]
536548 if period not in periods :
537549 raise ValueError ("The period parameter must be one of: "
538550 f"{ ', ' .join (map (str , periods ))} ." )
0 commit comments