Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ repos:
rev: v0.10.1
hooks:
- id: validate-pyproject
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.5.0
# This rev must match the exact ruff pin in pyproject.toml, which is what CI's
# `make lint` resolves through uv.lock.
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.0
hooks:
- id: ruff
types_or: [ python, jupyter ]
Expand Down
24 changes: 14 additions & 10 deletions Examples/4. CAISO April Net Load.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -5381,7 +5381,7 @@
" y=year_data[\"net_load\"] / 1000,\n",
" mode=\"lines\",\n",
" name=str(year),\n",
" line=dict(color=color, width=3),\n",
" line={\"color\": color, \"width\": 3},\n",
" opacity=min(0.6 + (year - 2017) * 0.075, 1),\n",
" )\n",
" )\n",
Expand All @@ -5393,7 +5393,7 @@
" y=min_net_load_day_2023[\"net_load\"] / 1000,\n",
" mode=\"lines\",\n",
" name=\"2023 Minimum Day\",\n",
" line=dict(color=\"#4a69bb\", width=4, dash=\"dash\"),\n",
" line={\"color\": \"#4a69bb\", \"width\": 4, \"dash\": \"dash\"},\n",
" opacity=1,\n",
" )\n",
")\n",
Expand All @@ -5404,13 +5404,17 @@
"\n",
"# Update layout with title, subtitle, and custom axis tick labels\n",
"fig.update_layout(\n",
" title=\"Average Net Load in April in California<br><span style='font-size:0.8em;color:gray'>Net Load regularly below 0 for first time in April 2023 </span>\", # noqa\n",
" xaxis=dict(\n",
" tickmode=\"array\",\n",
" tickvals=x_tick_values,\n",
" ticktext=[\"12am\", \"6am\", \"12pm\", \"6pm\", \"12am\"],\n",
" ),\n",
" yaxis=dict(tickmode=\"array\", tickvals=y_tick_values, ticktext=[0, 10, 20, \"25 GW\"]),\n",
" title=\"Average Net Load in April in California<br><span style='font-size:0.8em;color:gray'>Net Load regularly below 0 for first time in April 2023 </span>\",\n",
" xaxis={\n",
" \"tickmode\": \"array\",\n",
" \"tickvals\": x_tick_values,\n",
" \"ticktext\": [\"12am\", \"6am\", \"12pm\", \"6pm\", \"12am\"],\n",
" },\n",
" yaxis={\n",
" \"tickmode\": \"array\",\n",
" \"tickvals\": y_tick_values,\n",
" \"ticktext\": [0, 10, 20, \"25 GW\"],\n",
" },\n",
" template=\"plotly_dark\",\n",
")\n",
"\n",
Expand Down Expand Up @@ -5438,7 +5442,7 @@
" text=annotation[\"text\"],\n",
" showarrow=False,\n",
" yshift=annotation[\"y_shift\"],\n",
" font=dict(color=\"white\", size=18),\n",
" font={\"color\": \"white\", \"size\": 18},\n",
" )\n",
"\n",
"# Diplay the figure as an interactive plot\n",
Expand Down
22 changes: 11 additions & 11 deletions Examples/5. Stacked Net Load Visualization.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@
" name=\"Net Load\",\n",
" stackgroup=\"one\",\n",
" fill=\"none\",\n",
" line=dict(color=gray, width=5),\n",
" line={\"color\": gray, \"width\": 5},\n",
" )\n",
")\n",
"\n",
Expand Down Expand Up @@ -562,7 +562,7 @@
" y=df[\"load.load\"],\n",
" name=\"Load\",\n",
" mode=\"lines\",\n",
" line=dict(color=red, width=5),\n",
" line={\"color\": red, \"width\": 5},\n",
" )\n",
")\n",
"\n",
Expand All @@ -571,15 +571,15 @@
" template=\"plotly_dark\",\n",
" title=f\"<b>Net Load Visualization - {ISO.upper()} - {START} to {END} </b>\",\n",
" # bold title\n",
" title_font=dict(size=20),\n",
" font=dict(size=16),\n",
" legend=dict(\n",
" orientation=\"h\",\n",
" yanchor=\"bottom\",\n",
" y=1.02,\n",
" xanchor=\"right\",\n",
" x=1,\n",
" ),\n",
" title_font={\"size\": 20},\n",
" font={\"size\": 16},\n",
" legend={\n",
" \"orientation\": \"h\",\n",
" \"yanchor\": \"bottom\",\n",
" \"y\": 1.02,\n",
" \"xanchor\": \"right\",\n",
" \"x\": 1,\n",
" },\n",
")\n",
"\n",
"fig.show(\"png\", width=2000, height=800)"
Expand Down
4 changes: 2 additions & 2 deletions Examples/6. Resampling Data.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,7 @@
" y=df_hourly_max[\"load\"],\n",
" name=\"Hourly Max\",\n",
" mode=\"lines\",\n",
" line=dict(color=\"rgb(0,100,80)\"),\n",
" line={\"color\": \"rgb(0,100,80)\"},\n",
" )\n",
")\n",
"\n",
Expand All @@ -1043,7 +1043,7 @@
" y=df_hourly_min[\"load\"],\n",
" name=\"Hourly Min\",\n",
" mode=\"lines\",\n",
" line=dict(color=\"rgb(0,100,80)\"),\n",
" line={\"color\": \"rgb(0,100,80)\"},\n",
" fill=\"tonexty\",\n",
" fillcolor=\"rgba(0,100,80,0.2)\",\n",
" )\n",
Expand Down
7 changes: 5 additions & 2 deletions gridstatusio/gs_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ def _retry_delay_and_log(reason: str):
except RETRIABLE_EXCEPTIONS as e:
if retries >= self.max_retries:
raise Exception(
f"Network error: {str(e)}. Exceeded maximum number of retries",
f"Network error: {e!s}. Exceeded maximum number of retries",
)
_retry_delay_and_log(f"Network error ({type(e).__name__})")
retries += 1
Expand Down Expand Up @@ -1009,7 +1009,10 @@ def get_daily_peak_report(
dict: The daily peak report as a dict.
"""
if market_date is None:
market_date = datetime.today()
# Local, not UTC: the documented default is the caller's current date,
# and only the calendar date survives the strftime below. Using UTC
# would shift the default market day for callers west of UTC.
market_date = datetime.today() # noqa: DTZ002

if isinstance(market_date, datetime):
market_date = market_date.strftime("%Y-%m-%d")
Expand Down
32 changes: 18 additions & 14 deletions gridstatusio/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ def get_unique_values(data: Any, column: str) -> list:

def get_min(data: Any, column: str) -> Any:
"""Get min value for a column, regardless of format."""
if isinstance(data, pd.DataFrame):
return data[column].min()
elif isinstance(data, pl.DataFrame):
if isinstance(data, (pd.DataFrame, pl.DataFrame)):
return data[column].min()
elif isinstance(data, list):
values = [v for v in get_column_values(data, column) if v is not None]
Expand All @@ -120,9 +118,7 @@ def get_min(data: Any, column: str) -> Any:

def get_max(data: Any, column: str) -> Any:
"""Get max value for a column, regardless of format."""
if isinstance(data, pd.DataFrame):
return data[column].max()
elif isinstance(data, pl.DataFrame):
if isinstance(data, (pd.DataFrame, pl.DataFrame)):
return data[column].max()
elif isinstance(data, list):
values = [v for v in get_column_values(data, column) if v is not None]
Expand Down Expand Up @@ -312,9 +308,7 @@ def check_data(

def data_equals(data1: Any, data2: Any, return_format: str) -> bool:
"""Check if two datasets are equal."""
if return_format == ReturnFormat.PANDAS:
return data1.equals(data2)
elif return_format == ReturnFormat.POLARS:
if return_format == ReturnFormat.PANDAS or return_format == ReturnFormat.POLARS:
return data1.equals(data2)
elif return_format == ReturnFormat.PYTHON:
return data1 == data2
Expand Down Expand Up @@ -441,7 +435,9 @@ def test_get_dataset_metadata_csv_request_format():

def test_get_dataset_metadata_invalid_dataset(pandas_client):
"""Test that an invalid dataset id raises an error."""
with pytest.raises(Exception):
# The client raises bare Exception for API errors; narrowing this needs a custom
# exception hierarchy in the library (see the TRY002 note in pyproject.toml).
with pytest.raises(Exception): # noqa: B017
pandas_client.get_dataset_metadata("not_a_real_dataset")


Expand Down Expand Up @@ -917,22 +913,28 @@ def test_pagination(client, return_format):
assert get_length(data) == 25

# Test too large page size errors
with pytest.raises(Exception):
# The client raises bare Exception for API errors; narrowing this needs a custom
# exception hierarchy in the library (see the TRY002 note in pyproject.toml).
with pytest.raises(Exception): # noqa: B017
client.get_dataset(dataset=dataset, page_size=10**10)


# Query yesterday rather than today so the daily peak report always has settled
# data. Requesting the current date fails when the market day has not produced any
# intervals yet (the API returns "max() iterable argument is empty").
yesterday = datetime.now() - timedelta(days=1)
# Local, not UTC: this feeds market_date, which the client reduces to a calendar
# date, and the market day tracks the local date rather than a UTC instant.
yesterday = datetime.now() - timedelta(days=1) # noqa: DTZ005


@pytest.mark.parametrize(
"iso,market_date,expected_date",
[
("ERCOT", yesterday, yesterday.strftime("%Y-%m-%d")),
("CAISO", "2024-07-01", "2024-07-01"),
("spp", datetime(2024, 7, 10), "2024-07-10"),
# Naive by design: market_date is a calendar date, so a tzinfo would be
# discarded by the client's strftime anyway.
("spp", datetime(2024, 7, 10), "2024-07-10"), # noqa: DTZ001
],
)
def test_reports_api(client, return_format, iso, market_date, expected_date):
Expand All @@ -945,7 +947,9 @@ def test_reports_api(client, return_format, iso, market_date, expected_date):

def test_invalid_resampling_frequency(client, return_format):
"""Test that invalid resampling frequency raises error."""
with pytest.raises(Exception):
# The client raises bare Exception for API errors; narrowing this needs a custom
# exception hierarchy in the library (see the TRY002 note in pyproject.toml).
with pytest.raises(Exception): # noqa: B017
client.get_dataset(
"pjm_load",
resample="1 hour market",
Expand Down
6 changes: 3 additions & 3 deletions gridstatusio/tests/test_compression.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ def test_brotli_decoder_is_installed() -> None:
importlib.util.find_spec("brotli") is not None
or importlib.util.find_spec("brotlicffi") is not None
)
assert (
has_brotli_decoder
), "a brotli decoder (brotli or brotlicffi) must be installed"
assert has_brotli_decoder, (
"a brotli decoder (brotli or brotlicffi) must be installed"
)


def test_default_accept_encoding_advertises_brotli() -> None:
Expand Down
4 changes: 2 additions & 2 deletions gridstatusio/tests/test_retries.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def test_rate_limit_hit_backoff(mock_get_request, caplog):
)

log_messages = [record.message for record in caplog.records]
for i in range(0, client.max_retries):
for i in range(client.max_retries):
expected_text = (
f"Too Many Requests. Limit: 6 per 1 second. "
f"Retrying in {1 * 2**i} seconds. "
Expand All @@ -58,7 +58,7 @@ def test_connection_error_backoff(mock_get_request, caplog):
)

log_messages = [record.message for record in caplog.records]
for i in range(0, client.max_retries):
for i in range(client.max_retries):
expected_text = (
f"Network error (ConnectionError). "
f"Retrying in {1 * 2**i} seconds. "
Expand Down
4 changes: 3 additions & 1 deletion gridstatusio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def handle_date(
# For non-pandas mode, just validate and return the string/datetime
if not use_pandas:
if date == "today":
return datetime.now().strftime("%Y-%m-%d")
# Local, not UTC: the caller asked for "today", which means their
# calendar date, and only the date survives the strftime.
return datetime.now().strftime("%Y-%m-%d") # noqa: DTZ005
if isinstance(date, datetime):
return date.isoformat()
# Assume string is already in valid format
Expand Down
4 changes: 2 additions & 2 deletions gridstatusio/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
def get_latest_version() -> str:
"""Get the latest version of gridstatusio from PyPI"""

response = requests.get("https://pypi.org/pypi/gridstatusio/json") # noqa: E501
response = requests.get("https://pypi.org/pypi/gridstatusio/json")
latest_version = response.json()["info"]["version"]
return latest_version

Expand Down Expand Up @@ -49,7 +49,7 @@ def check_for_update() -> None:
)
print(
colored(
"\nSee the changelog here: https://github.com/gridstatus/gridstatusio/blob/main/CHANGELOG.md", # noqa: E501
"\nSee the changelog here: https://github.com/gridstatus/gridstatusio/blob/main/CHANGELOG.md",
"red",
),
)
25 changes: 19 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ dev-dependencies = [
"pre-commit>=4.5.1",
"kaleido==0.2.1",
"nbformat>=5.10.4",
"ruff>=0.5.0,<0.6",
"ruff==0.16.0",
"pyright>=1.1.407,<2",
# Include optional dependencies for testing
"polars>=1.36.1",
Expand All @@ -110,17 +110,30 @@ markers = [

[tool.ruff]
line-length = 88
lint.ignore = []
lint.select = [
# Pyflakes
# ruff >=0.16 ships a broad curated default rule set
# (https://docs.astral.sh/ruff/default-rules/). We inherit it and use
# extend-select for the families enforced here that are not part of it, rather
# than pinning our own select and missing future default additions.
lint.extend-select = [
# Pyflakes in full (the default set carries only a subset)
"F",
# Pycodestyle
# Pycodestyle in full (the default set carries only E722/E902)
"E",
"W",
# flake8-quotes
"Q",
# isort
"I001"
"I001",
]
lint.ignore = [
# Class-level mutable defaults here are declarative config, not the shared-state
# bug this targets.
"RUF012",
# Nested `with` blocks read better than one merged statement for these.
"SIM117",
# Introducing a custom exception hierarchy is a separate change from adopting
# the ruleset.
"TRY002",
]
src = ["gridstatusio"]

Expand Down
Loading
Loading