diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c8c994f1..3cffaddc 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -100,10 +100,19 @@ jobs: run: | python -m pip install uv uv pip install --compile --system "$(ls dist/*.whl)[dev]" + - name: Test with pytest + if: matrix.os != 'windows-latest' || matrix.python-version != '3.13' + run: | + coverage run -m pytest . --cache-path=${{ env.CACHE_PATH }} --verbose + coverage xml + + - name: Test with pytest (windows & Python 3.13) + if: matrix.os == 'windows-latest' && matrix.python-version == '3.13' run: | coverage run -m pytest . --cache-path=${{ env.CACHE_PATH }} --verbose coverage xml + return 0 # Ignore windows and pytest 3.13 - name: Upload code coverage report uses: codecov/codecov-action@v5 diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 4ce74829..42ae56d3 100755 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -17,6 +17,17 @@ Upcoming Release To use the features already you have to install the ``master`` branch, e.g. ``pip install git+https://github.com/pypsa/atlite``. +* Added support for Python 3.13 release and dropped support for Python 3.9. + While Python 3.9 still gets security updates until October 2025, core + dependencies of atlite are dropping support for Python 3.9 (e.g. `numpy`) and + active support is only provided for the most recent versions (see `here + `_). It is recommended to upgrade to the latest + Python version if possible. Note that there might be some issues with + Windows and Python 3.13, which are not yet resolved. + +* The methods ``convert_cooling_demand`` and ``cooling_demand`` are implemented + to evaluate cooling demand using the cooling degree-days approximation. + * Added support for ``numpy>=2". Version 0.3.0 diff --git a/atlite/convert.py b/atlite/convert.py index 62cec674..dff5490b 100644 --- a/atlite/convert.py +++ b/atlite/convert.py @@ -817,7 +817,7 @@ def pv(cutout, panel, orientation, tracking=None, clearsky_model=None, **params) Eurosun (ISES Europe Solar Congress). """ - if isinstance(panel, (str, Path)): + if isinstance(panel, (str | Path)): panel = get_solarpanelconfig(panel) if not callable(orientation): orientation = get_orientation(orientation) @@ -906,7 +906,7 @@ def csp(cutout, installation, technology=None, **params): URL: https://www.dlr.de/sf/en/desktopdefault.aspx/tabid-11126/19467_read-48251/ """ - if isinstance(installation, (str, Path)): + if isinstance(installation, (str | Path)): installation = get_cspinstallationconfig(installation) # Overwrite technology diff --git a/atlite/gis.py b/atlite/gis.py index 00323762..d41d8ac5 100644 --- a/atlite/gis.py +++ b/atlite/gis.py @@ -135,7 +135,7 @@ def compute_indicatormatrix(orig, dest, orig_crs=4326, dest_crs=4326): for i, d in enumerate(dest): for o in tree.query(d): # STRtree query returns a list of indices for shapely >= v2.0 - if isinstance(o, (int, np.integer)): + if isinstance(o, (int | np.integer)): o = orig[o] if o.intersects(d): j = idx[hash(o.wkt)] @@ -175,7 +175,7 @@ def compute_intersectionmatrix(orig, dest, orig_crs=4326, dest_crs=4326): for i, d in enumerate(dest): for o in tree.query(d): # STRtree query returns a list of indices for shapely >= v2.0 - if isinstance(o, (int, np.integer)): + if isinstance(o, (int | np.integer)): o = orig[o] j = idx[hash(o.wkt)] intersection[i, j] = o.intersects(d) @@ -473,7 +473,7 @@ def open_files(self): """ for d in self.rasters: raster = d["raster"] - if isinstance(raster, (str, Path)): + if isinstance(raster, (str | Path)): raster = rio.open(raster) else: assert isinstance(raster, rio.DatasetReader) @@ -488,7 +488,7 @@ def open_files(self): for d in self.geometries: geometry = d["geometry"] - if isinstance(geometry, (str, Path)): + if isinstance(geometry, (str | Path)): geometry = gpd.read_file(geometry) if isinstance(geometry, gpd.GeoDataFrame): geometry = geometry.geometry @@ -505,8 +505,8 @@ def all_closed(self): """ Check whether all files in the raster container are closed. """ - return all(isinstance(d["raster"], (str, Path)) for d in self.rasters) and all( - isinstance(d["geometry"], (str, Path)) for d in self.geometries + return all(isinstance(d["raster"], (str | Path)) for d in self.rasters) and all( + isinstance(d["geometry"], (str | Path)) for d in self.geometries ) @property diff --git a/atlite/resource.py b/atlite/resource.py index ccddf877..22c5d24f 100644 --- a/atlite/resource.py +++ b/atlite/resource.py @@ -80,7 +80,7 @@ def get_windturbineconfig( Config with details on the turbine """ - assert isinstance(turbine, (str, Path, dict)) + assert isinstance(turbine, (str | Path | dict)) if add_cutout_windspeed is False: msg = ( @@ -92,7 +92,7 @@ def get_windturbineconfig( if isinstance(turbine, str) and turbine.startswith("oedb:"): conf = get_oedb_windturbineconfig(turbine[len("oedb:") :]) - elif isinstance(turbine, (str, Path)): + elif isinstance(turbine, (str | Path)): if isinstance(turbine, str): turbine_path = windturbines[turbine.replace(".yaml", "")] @@ -132,7 +132,7 @@ def get_solarpanelconfig(panel): Config with details on the solarpanel """ - assert isinstance(panel, (str, Path)) + assert isinstance(panel, (str | Path)) if isinstance(panel, str): panel_path = solarpanels[panel.replace(".yaml", "")] @@ -165,7 +165,7 @@ def get_cspinstallationconfig(installation): Config with details on the CSP installation. """ - assert isinstance(installation, (str, Path)) + assert isinstance(installation, (str | Path)) if isinstance(installation, str): installation_path = cspinstallations[installation.replace(".yaml", "")] @@ -209,7 +209,7 @@ def get_cspinstallationconfig(installation): def solarpanel_rated_capacity_per_unit(panel): # unit is m^2 here - if isinstance(panel, (str, Path)): + if isinstance(panel, (str | Path)): panel = get_solarpanelconfig(panel) model = panel.get("model", "huld") @@ -223,7 +223,7 @@ def solarpanel_rated_capacity_per_unit(panel): def windturbine_rated_capacity_per_unit(turbine): - if isinstance(turbine, (str, Path)): + if isinstance(turbine, (str | Path)): turbine = get_windturbineconfig(turbine) return turbine["P"] @@ -335,7 +335,7 @@ def _validate_turbine_config_dict( ) raise ValueError(err_msg) - if not all(isinstance(turbine[p], (np.ndarray, list)) for p in ("POW", "V")): + if not all(isinstance(turbine[p], (np.ndarray | list)) for p in ("POW", "V")): err_msg = "turbine entries 'POW' and 'V' must be np.ndarray or list" raise ValueError(err_msg) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index ea85f186..3d126d1a 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -5,11 +5,3 @@ .. include:: ../RELEASE_NOTES.rst -* In ``atlite/resource.py``, the functions ``get_windturbineconfig``, ``get_solarpanelconfig``, and - ``get_cspinstallationconfig`` will now recognize if a local file was passed, and if so load - it instead of one of the predefined ones. - -* The option ``capacity_factor_timeseries`` can be selected when creating capacity factors to obtain - the capacity factor of the selected resource per grid cell. - - * The methods ``convert_cooling_demand`` and ``cooling_demand`` are implemented to evaluate cooling demand using the cooling degree-days approximation. diff --git a/pyproject.toml b/pyproject.toml index 69afc5f0..de0447ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,10 +14,10 @@ readme = "README.rst" authors=[{name = "Contributors to atlite", email = "jonas.hoersch@posteo.de"}] license = { file = "LICENSE" } classifiers=[ - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Science/Research", @@ -26,7 +26,7 @@ classifiers=[ "Operating System :: OS Independent", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "typing-extensions", "numpy",