From a110a695d9d294c4532dda4470df47b885ea657d Mon Sep 17 00:00:00 2001 From: Jeremy Lucas Date: Wed, 10 Sep 2025 13:20:08 -0700 Subject: [PATCH 1/5] Allows reading TMY data from a Path or file-like object --- pvlib/iotools/tmy.py | 8 +++++--- pvlib/tools.py | 4 ++-- tests/iotools/test_tmy.py | 4 ++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pvlib/iotools/tmy.py b/pvlib/iotools/tmy.py index 4b6edaeea4..01b0d70668 100644 --- a/pvlib/iotools/tmy.py +++ b/pvlib/iotools/tmy.py @@ -4,6 +4,8 @@ import re import pandas as pd +from pvlib.tools import _file_context_manager + # Dictionary mapping TMY3 names to pvlib names VARIABLE_MAP = { 'GHI (W/m^2)': 'ghi', @@ -22,7 +24,7 @@ } -def read_tmy3(filename, coerce_year=None, map_variables=True, encoding=None): +def read_tmy3(filename_or_obj, coerce_year=None, map_variables=True, encoding=None): """Read a TMY3 file into a pandas dataframe. Note that values contained in the metadata dictionary are unchanged @@ -35,7 +37,7 @@ def read_tmy3(filename, coerce_year=None, map_variables=True, encoding=None): Parameters ---------- - filename : str + filename_or_obj : str, Path, or file-like object A relative file path or absolute file path. coerce_year : int, optional If supplied, the year of the index will be set to ``coerce_year``, except @@ -186,7 +188,7 @@ def read_tmy3(filename, coerce_year=None, map_variables=True, encoding=None): """ # noqa: E501 head = ['USAF', 'Name', 'State', 'TZ', 'latitude', 'longitude', 'altitude'] - with open(str(filename), 'r', encoding=encoding) as fbuf: + with _file_context_manager(filename_or_obj, mode="r", encoding=encoding) as fbuf: # header information on the 1st line (0 indexing) firstline = fbuf.readline() # use pandas to read the csv file buffer diff --git a/pvlib/tools.py b/pvlib/tools.py index 63406f4e0d..142e89869b 100644 --- a/pvlib/tools.py +++ b/pvlib/tools.py @@ -562,7 +562,7 @@ def normalize_max2one(a): return res -def _file_context_manager(filename_or_object, mode='r'): +def _file_context_manager(filename_or_object, mode='r', encoding=None): """ Open a filename/path for reading, or pass a file-like object through unchanged. @@ -584,5 +584,5 @@ def _file_context_manager(filename_or_object, mode='r'): context = contextlib.nullcontext(filename_or_object) else: # otherwise, assume a filename or path - context = open(str(filename_or_object), mode=mode) + context = open(str(filename_or_object), mode=mode, encoding=encoding) return context diff --git a/tests/iotools/test_tmy.py b/tests/iotools/test_tmy.py index 63d37ac830..90658d4e25 100644 --- a/tests/iotools/test_tmy.py +++ b/tests/iotools/test_tmy.py @@ -18,6 +18,10 @@ def test_read_tmy3(): tmy.read_tmy3(TMY3_TESTFILE, map_variables=False) +def test_read_tmy3_buffer(): + with open(TMY3_TESTFILE) as f: + tmy.read_tmy3(f, map_variables=False) + def test_read_tmy3_norecolumn(): data, _ = tmy.read_tmy3(TMY3_TESTFILE, map_variables=False) From 6561ccbf3def2863e72b159d866cc3100da1b701 Mon Sep 17 00:00:00 2001 From: Jeremy Lucas Date: Thu, 11 Sep 2025 10:54:07 -0700 Subject: [PATCH 2/5] Reverts "filename" parameter rename for consistency --- pvlib/iotools/tmy.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/iotools/tmy.py b/pvlib/iotools/tmy.py index 01b0d70668..28d20ffc2d 100644 --- a/pvlib/iotools/tmy.py +++ b/pvlib/iotools/tmy.py @@ -24,7 +24,7 @@ } -def read_tmy3(filename_or_obj, coerce_year=None, map_variables=True, encoding=None): +def read_tmy3(filename, coerce_year=None, map_variables=True, encoding=None): """Read a TMY3 file into a pandas dataframe. Note that values contained in the metadata dictionary are unchanged @@ -37,7 +37,7 @@ def read_tmy3(filename_or_obj, coerce_year=None, map_variables=True, encoding=No Parameters ---------- - filename_or_obj : str, Path, or file-like object + filename : str, Path, or file-like object A relative file path or absolute file path. coerce_year : int, optional If supplied, the year of the index will be set to ``coerce_year``, except @@ -188,7 +188,7 @@ def read_tmy3(filename_or_obj, coerce_year=None, map_variables=True, encoding=No """ # noqa: E501 head = ['USAF', 'Name', 'State', 'TZ', 'latitude', 'longitude', 'altitude'] - with _file_context_manager(filename_or_obj, mode="r", encoding=encoding) as fbuf: + with _file_context_manager(filename, mode="r", encoding=encoding) as fbuf: # header information on the 1st line (0 indexing) firstline = fbuf.readline() # use pandas to read the csv file buffer From be82a62d7c2dc569e0ff2f187d5fd557ee661757 Mon Sep 17 00:00:00 2001 From: Jeremy Lucas Date: Thu, 11 Sep 2025 10:54:21 -0700 Subject: [PATCH 3/5] Fixes linter issue in tests --- tests/iotools/test_tmy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/iotools/test_tmy.py b/tests/iotools/test_tmy.py index 90658d4e25..15b496f4d1 100644 --- a/tests/iotools/test_tmy.py +++ b/tests/iotools/test_tmy.py @@ -18,6 +18,7 @@ def test_read_tmy3(): tmy.read_tmy3(TMY3_TESTFILE, map_variables=False) + def test_read_tmy3_buffer(): with open(TMY3_TESTFILE) as f: tmy.read_tmy3(f, map_variables=False) From 818154857b53a31de1a58412ae621047a5af360b Mon Sep 17 00:00:00 2001 From: Jeremy Lucas Date: Thu, 11 Sep 2025 11:02:46 -0700 Subject: [PATCH 4/5] Updates "What's New" docs for change --- docs/sphinx/source/whatsnew/v0.13.1.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.13.1.rst b/docs/sphinx/source/whatsnew/v0.13.1.rst index 26a591e534..87fb1ae64f 100644 --- a/docs/sphinx/source/whatsnew/v0.13.1.rst +++ b/docs/sphinx/source/whatsnew/v0.13.1.rst @@ -29,6 +29,8 @@ Enhancements (:pull:`2500`) * :py:func:`pvlib.spectrum.spectral_factor_firstsolar` no longer emits warnings when airmass and precipitable water values fall out of range. (:pull:`2512`) +* Allows reading TMY data from a Path or file-like object in :py:func:`~pvlib.iotools.read_tmy3`. + (:pull:`2544`, :ghuser:`jerluc`) Documentation ~~~~~~~~~~~~~ From 970688e9cec852522cf83950f97457e8c58c1d4f Mon Sep 17 00:00:00 2001 From: Jeremy Lucas Date: Mon, 15 Sep 2025 09:56:17 -0700 Subject: [PATCH 5/5] Adding a couple of more assertions for TMY3 parsing test --- tests/iotools/test_tmy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/iotools/test_tmy.py b/tests/iotools/test_tmy.py index 15b496f4d1..d50609ec47 100644 --- a/tests/iotools/test_tmy.py +++ b/tests/iotools/test_tmy.py @@ -21,7 +21,9 @@ def test_read_tmy3(): def test_read_tmy3_buffer(): with open(TMY3_TESTFILE) as f: - tmy.read_tmy3(f, map_variables=False) + data, _ = tmy.read_tmy3(f, map_variables=False) + assert 'GHI source' in data.columns + assert len(data) == 8760 def test_read_tmy3_norecolumn():