-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix silently ignoring file path in pvsystem.retrieve_sam
when name
is provided
#2020
Changes from 5 commits
16c0ee1
4ea8fb0
a2b446f
405de69
5b4aa4b
bce44f4
215c038
a8d1d15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,7 @@ | |
import functools | ||
import io | ||
import itertools | ||
import os | ||
from pathlib import Path | ||
import inspect | ||
from urllib.request import urlopen | ||
import numpy as np | ||
|
@@ -1958,9 +1958,9 @@ def calcparams_pvsyst(effective_irradiance, temp_cell, | |
|
||
|
||
def retrieve_sam(name=None, path=None): | ||
''' | ||
Retrieve latest module and inverter info from a local file or the | ||
SAM website. | ||
""" | ||
Retrieve latest module and inverter info from a file bundled with pvlib, | ||
a path or an URL (like SAM's website). | ||
|
||
This function will retrieve either: | ||
|
||
|
@@ -1971,10 +1971,14 @@ def retrieve_sam(name=None, path=None): | |
|
||
and return it as a pandas DataFrame. | ||
|
||
.. note:: | ||
Only provide one of ``name`` or ``path``. | ||
|
||
Parameters | ||
---------- | ||
name : string, optional | ||
Name can be one of: | ||
Use one of the following strings to retrieve a database bundled with | ||
pvlib: | ||
|
||
* 'CECMod' - returns the CEC module database | ||
* 'CECInverter' - returns the CEC Inverter database | ||
|
@@ -1985,7 +1989,7 @@ def retrieve_sam(name=None, path=None): | |
* 'ADRInverter' - returns the ADR Inverter database | ||
|
||
path : string, optional | ||
Path to the SAM file. May also be a URL. | ||
Path to a CSV file or a URL. | ||
|
||
Returns | ||
------- | ||
|
@@ -1997,7 +2001,13 @@ def retrieve_sam(name=None, path=None): | |
Raises | ||
------ | ||
ValueError | ||
If no name or path is provided. | ||
If no ``name`` or ``path`` is provided. | ||
ValueError | ||
If both ``name`` and ``path`` are provided. | ||
ValueError | ||
If the provided ``name`` is not valid. | ||
ValueError | ||
If the provided ``path`` does not exist. | ||
|
||
Notes | ||
----- | ||
|
@@ -2030,38 +2040,40 @@ def retrieve_sam(name=None, path=None): | |
CEC_Date NaN | ||
CEC_Type Utility Interactive | ||
Name: AE_Solar_Energy__AE6_0__277V_, dtype: object | ||
''' | ||
|
||
if name is not None: | ||
name = name.lower() | ||
data_path = os.path.join( | ||
os.path.dirname(os.path.abspath(__file__)), 'data') | ||
if name == 'cecmod': | ||
csvdata = os.path.join( | ||
data_path, 'sam-library-cec-modules-2019-03-05.csv') | ||
elif name == 'sandiamod': | ||
csvdata = os.path.join( | ||
data_path, 'sam-library-sandia-modules-2015-6-30.csv') | ||
elif name == 'adrinverter': | ||
csvdata = os.path.join( | ||
data_path, 'adr-library-cec-inverters-2019-03-05.csv') | ||
elif name in ['cecinverter', 'sandiainverter']: | ||
# Allowing either, to provide for old code, | ||
# while aligning with current expectations | ||
csvdata = os.path.join( | ||
data_path, 'sam-library-cec-inverters-2019-03-05.csv') | ||
""" | ||
# error: path was previously silently ignored if name was given GH#2018 | ||
if name is not None and path is not None: | ||
raise ValueError("Please provide either 'name' or 'path', not both.") | ||
elif name is None and path is None: | ||
raise ValueError("Please provide either 'name' or 'path'.") | ||
elif name is not None: | ||
internal_dbs = { | ||
"cecmod": "sam-library-cec-modules-2019-03-05.csv", | ||
"sandiamod": "sam-library-sandia-modules-2015-6-30.csv", | ||
"adrinverter": "adr-library-cec-inverters-2019-03-05.csv", | ||
# Both 'cecinverter' and 'sandiainverter', point to same database | ||
# to provide for old code, while aligning with current expectations | ||
"cecinverter": "sam-library-cec-inverters-2019-03-05.csv", | ||
"sandiainverter": "sam-library-cec-inverters-2019-03-05.csv", | ||
} | ||
name_lwr = name.lower() | ||
if name_lwr in internal_dbs.keys(): # input is a database name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this extra call is unnecessary, just use a try except or better yet let it raise key error: try:
cvsdata = internal_dbs[name_lwr]
except KeyError as exc:
raise exc # or do what you want
else: # only executes if no exception
csvdata_path = Path(__file__).parent.joinpath(
"data", cvsdata) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good idea. You've inspired me to avoid checking the existence of the file too. |
||
csvdata_path = Path(__file__).parent.joinpath( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general beware of placing too many executions in try-except or else isolating exceptions can be more difficult. Best to limit one operation with known exceptions |
||
"data", internal_dbs[name_lwr] | ||
) | ||
else: | ||
raise ValueError(f'invalid name {name}') | ||
elif path is not None: | ||
if path.startswith('http'): | ||
response = urlopen(path) | ||
csvdata = io.StringIO(response.read().decode(errors='ignore')) | ||
raise ValueError( | ||
f"Invalid name {name}. Provide one of {internal_dbs.keys()}." | ||
echedey-ls marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
else: # path is not None | ||
if path.lower().startswith("http"): # URL check is not case-sensitive | ||
response = urlopen(path) # URL is case-sensitive | ||
csvdata_path = io.StringIO(response.read().decode(errors="ignore")) | ||
elif Path(path).exists(): | ||
csvdata_path = path | ||
else: | ||
csvdata = path | ||
elif name is None and path is None: | ||
raise ValueError("A name or path must be provided!") | ||
|
||
return _parse_raw_sam_df(csvdata) | ||
raise ValueError(f"Invalid path {path}: does not exist.") | ||
return _parse_raw_sam_df(csvdata_path) | ||
|
||
|
||
def _normalize_sam_product_names(names): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a blocker, but anyone consider using a constant
INTERNAL_DBS
at the top of the module instead of defining it on every call?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like something only used in a function is better placed near it, and I doubt the extra computation time does save significant time when there will be two calls at most in a script.