Skip to content

Commit 9e47629

Browse files
committed
ruff and uv
1 parent ac18e56 commit 9e47629

File tree

14 files changed

+563
-540
lines changed

14 files changed

+563
-540
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,4 @@ run_act_test.bat
136136
*.py~
137137

138138
# Streamlit secrets
139-
.streamlit/secrets.toml
139+
app/.streamlit/secrets.toml

README.md

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,53 @@
11
# NREL-PSM3-2-EPW
2-
A script that writes out epw files from NREL Physical Solar Model (PSM) v3.2.2
32

4-
# Demo
3+
A script and Streamlit app that writes out EPW files from NREL Physical Solar Model (PSM) v3.2.2 (and v4.0.0 API).
54

6-
- [Link to demo](https://nrel-psm3-2-epw.streamlit.app/)
5+
## Features
76

8-
# How to use locally
7+
- **EPW Conversion**: Converts NREL solar data to EnergyPlus Weather format.
8+
- **Streamlit App**: User-friendly interface for downloading data.
9+
- **Secure**: API keys are managed via Streamlit secrets and verified with hash checks.
10+
- **Modern Stack**: Built with `uv` for dependency management and `ruff` for code quality.
911

10-
- Create a virtual environment and install dependencies with uv:
11-
- `uv venv .venv`
12-
- `uv sync --extra dev`
13-
- Place your [API key](https://developer.nrel.gov/signup/) in a file called `api_key` in the tests directory or export it as `APIKEY`.
14-
- Make adjustments to `tests/test_download.py` and run the file or run tests with `uv run pytest`.
15-
- For GOES TMY data, use `names` like `tmy`, `tmy-2024`, `tgy-2024`, or `tdy-2024`. Numeric years use the GOES aggregated dataset.
12+
## Project Structure
13+
14+
- `app/`: Contains the Streamlit application code.
15+
- `streamlit_app.py`: Main entry point.
16+
- `.streamlit/secrets.toml`: (Not committed) Stores your API key.
17+
- `nrel_psm3_2_epw/`: Core transformation logic.
18+
- `tests/`: Unit tests (100% coverage).
19+
20+
## How to Run Locally
21+
22+
1. **Install dependencies with uv**:
23+
```bash
24+
uv sync --extra dev
25+
```
26+
27+
2. **Configure API Key**:
28+
- Create `app/.streamlit/secrets.toml`:
29+
```toml
30+
APIKEY = "YOUR_NREL_API_KEY"
31+
```
32+
- *Note: The app verifies the default API key integrity.*
33+
34+
3. **Run the App**:
35+
```bash
36+
uv run streamlit run app/streamlit_app.py
37+
```
38+
*(Windows users can use `run_streamlit.bat`)*
39+
40+
4. **Run Tests**:
41+
```bash
42+
uv run pytest
43+
```
44+
45+
5. **Lint & Format**:
46+
```bash
47+
uv run ruff check --fix .
48+
uv run ruff format .
49+
```
50+
51+
## Demo
52+
53+
- [Link to demo](https://nrel-psm3-2-epw.streamlit.app/)
Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,27 @@
66
import os
77
import hashlib
88
from datetime import datetime
9-
from typing import Optional, Union, Any
9+
from typing import Optional, Any
1010

1111
import streamlit as st
1212
import pandas as pd
1313

1414
from nrel_psm3_2_epw.assets import download_epw
1515

1616
# --- CONSTANTS ---
17-
ATTRIBUTES = ('air_temperature,clearsky_dhi,clearsky_dni,clearsky_ghi,cloud_type,dew_point,dhi,dni,fill_flag,'
18-
'ghi,relative_humidity,solar_zenith_angle,surface_albedo,surface_pressure,total_precipitable_water,'
19-
'wind_direction,wind_speed')
20-
INTERVAL = '60'
21-
UTC = 'false'
17+
ATTRIBUTES = (
18+
"air_temperature,clearsky_dhi,clearsky_dni,clearsky_ghi,cloud_type,dew_point,dhi,dni,fill_flag,"
19+
"ghi,relative_humidity,solar_zenith_angle,surface_albedo,surface_pressure,total_precipitable_water,"
20+
"wind_direction,wind_speed"
21+
)
22+
INTERVAL = "60"
23+
UTC = "false"
2224
YOUR_NAME = "John+Doe"
23-
REASON_FOR_USE = 'beta+testing'
24-
YOUR_AFFILIATION = 'aaa'
25+
REASON_FOR_USE = "beta+testing"
26+
YOUR_AFFILIATION = "aaa"
2527
YOUR_EMAIL = "[email protected]"
26-
MAILING_LIST = 'false'
27-
LEAP_YEAR = 'false'
28+
MAILING_LIST = "false"
29+
LEAP_YEAR = "false"
2830
MIN_YEAR = 1998
2931
VALID_API_KEY_HASH = "1c2c12cf359f7aba48e0aaf39ac031d98fee2418f3c4e482ec1044904032fefe"
3032

@@ -47,12 +49,10 @@ def _load_api_key() -> Optional[str]:
4749
# 2. Environment Variable
4850
api_key = os.environ.get("APIKEY")
4951
if api_key:
50-
print("DEBUG: Found APIKEY in os.environ")
5152
return api_key
5253

5354
cwd = os.getcwd()
54-
print(f"DEBUG: CWD is {cwd}")
55-
55+
5656
# 3. api_key file
5757
api_key_path = os.path.join(cwd, "api_key")
5858
if os.path.isfile(api_key_path):
@@ -61,28 +61,21 @@ def _load_api_key() -> Optional[str]:
6161

6262
# 4. .env file
6363
env_path = os.path.join(cwd, ".env")
64-
print(f"DEBUG: Checking .env at {env_path}")
6564
if os.path.isfile(env_path):
66-
print("DEBUG: .env file exists")
6765
with open(env_path, "r") as f:
6866
for line in f:
6967
stripped = line.strip()
7068
if not stripped or stripped.startswith("#") or "=" not in stripped:
7169
continue
7270
key, value = stripped.split("=", 1)
73-
# print(f"DEBUG: Seen key: {key}")
7471
if key.strip() == "APIKEY" and value.strip():
75-
print("DEBUG: Found APIKEY in .env")
7672
return value.strip()
77-
else:
78-
print("DEBUG: .env file NOT found")
7973
return None
8074

8175

82-
def download_button(object_to_download: Any,
83-
download_filename: str,
84-
button_text: str,
85-
pickle_it: bool = False) -> Optional[str]:
76+
def download_button(
77+
object_to_download: Any, download_filename: str, button_text: str, pickle_it: bool = False
78+
) -> Optional[str]:
8679
"""
8780
Generates a link to download the given object_to_download.
8881
"""
@@ -110,8 +103,8 @@ def download_button(object_to_download: Any,
110103
st.error(f"Encoding error: {e}")
111104
return None
112105

113-
button_uuid = str(uuid.uuid4()).replace('-', '')
114-
button_id = re.sub(r'\d+', '', button_uuid)
106+
button_uuid = str(uuid.uuid4()).replace("-", "")
107+
button_id = re.sub(r"\d+", "", button_uuid)
115108

116109
custom_css = f"""
117110
<style>
@@ -138,7 +131,10 @@ def download_button(object_to_download: Any,
138131
}}
139132
</style> """
140133

141-
dl_link = custom_css + f'<a download="{download_filename}" id="{button_id}" href="data:file/txt;base64,{b64}">{button_text}</a><br><br>'
134+
dl_link = (
135+
custom_css + f'<a download="{download_filename}" id="{button_id}" '
136+
f'href="data:file/txt;base64,{b64}">{button_text}</a><br><br>'
137+
)
142138
return dl_link
143139

144140

@@ -157,18 +153,18 @@ def main():
157153
value="",
158154
type="password",
159155
)
160-
156+
161157
api_key = ""
162158
api_key_source = "none"
163-
159+
164160
if api_key_override:
165161
api_key = api_key_override
166162
api_key_source = "user"
167163
st.success("User API key loaded.")
168164
elif default_api_key:
169165
api_key = default_api_key
170166
api_key_source = "default"
171-
167+
172168
# Verify Hash
173169
key_hash = hashlib.sha256(api_key.strip().encode()).hexdigest()
174170
if key_hash == VALID_API_KEY_HASH:
@@ -180,29 +176,33 @@ def main():
180176

181177
st.markdown("Please provide _Lat_, _Lon_, _Location_, and _Year_.")
182178

183-
lat = st.text_input('Lat:', value=33.770)
184-
lon = st.text_input('Lon:', value=-84.3824)
185-
location = st.text_input('Location (just used to name the file):', value="Atlanta")
186-
year = st.text_input('Year (e.g., 2012, tmy, tmy-2024):', value="tmy")
179+
lat = st.text_input("Lat:", value=33.770)
180+
lon = st.text_input("Lon:", value=-84.3824)
181+
location = st.text_input("Location (just used to name the file):", value="Atlanta")
182+
year = st.text_input("Year (e.g., 2012, tmy, tmy-2024):", value="tmy")
187183

188-
if st.button('Request from NREL'):
184+
if st.button("Request from NREL"):
189185
if not api_key:
190186
st.error("Please provide a valid API key.")
191187
st.stop()
192188

193189
current_year = datetime.now().year
194190
year_str = str(year).strip()
195-
191+
196192
# Basic Year Validation
197193
if year_str.isdigit():
198194
year_int = int(year_str)
199195
if year_int in (current_year, current_year - 1):
200-
st.warning(f"NREL does not provide data for the current year {year}. "
201-
f"It is also unlikely that there is data availability for {year_int - 1}.")
196+
st.warning(
197+
f"NREL does not provide data for the current year {year}. "
198+
f"It is also unlikely that there is data availability for {year_int - 1}."
199+
)
202200
st.stop()
203201
elif year_int < MIN_YEAR:
204-
st.warning(f"NREL does not provide data for the year {year}. "
205-
f"The earliest year data is available for is {MIN_YEAR}.")
202+
st.warning(
203+
f"NREL does not provide data for the year {year}. "
204+
f"The earliest year data is available for is {MIN_YEAR}."
205+
)
206206
st.stop()
207207
else:
208208
if not year_str.lower().startswith(("tmy", "tgy", "tdy")):
@@ -212,18 +212,32 @@ def main():
212212
st.info("Requesting data from NREL...")
213213

214214
try:
215-
file_name = download_epw(lon, lat, year, location, ATTRIBUTES, INTERVAL, UTC, YOUR_NAME,
216-
api_key, REASON_FOR_USE, YOUR_AFFILIATION, YOUR_EMAIL, MAILING_LIST, LEAP_YEAR)
215+
file_name = download_epw(
216+
lon,
217+
lat,
218+
year,
219+
location,
220+
ATTRIBUTES,
221+
INTERVAL,
222+
UTC,
223+
YOUR_NAME,
224+
api_key,
225+
REASON_FOR_USE,
226+
YOUR_AFFILIATION,
227+
YOUR_EMAIL,
228+
MAILING_LIST,
229+
LEAP_YEAR,
230+
)
217231
except Exception as exc:
218232
st.error(f"Request failed: {exc}")
219233
if api_key_source == "default":
220234
st.info("If this failure is related to the default API key, enter your own key above and retry.")
221235
st.stop()
222236

223237
if os.path.exists(file_name):
224-
with open(file_name, 'rb') as f:
238+
with open(file_name, "rb") as f:
225239
s = f.read()
226-
download_button_str = download_button(s, file_name, 'Download EPW')
240+
download_button_str = download_button(s, file_name, "Download EPW")
227241
if download_button_str:
228242
st.markdown(download_button_str, unsafe_allow_html=True)
229243
else:

debug_api_key.py

Lines changed: 0 additions & 60 deletions
This file was deleted.

get_key_hash.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)