From dee4bbdcc15d9055f36b4c8747751bceb5077015 Mon Sep 17 00:00:00 2001 From: "John.Krasting" Date: Fri, 6 Mar 2026 08:12:09 -0500 Subject: [PATCH 1/4] Modernize packaging: migrate to pyproject.toml with git-based versioning - Replace setup.py/setup.cfg/version.py with pyproject.toml using setuptools-git-versioning for dynamic version from git tags - Convert scripts/ to proper console_scripts entry points (cli_db2nc.py, cli_plotdb.py, cli.py main()) - Replace deprecated pkg_resources with importlib.resources - Replace version.py import with importlib.metadata - Add minimal pytest test suite - Add GitHub Actions CI workflow for tests - Update GitHub Actions publish workflow to v4 actions - Update ReadTheDocs config and docs build for installed package - Fix Sphinx conf.py to use dynamic version and installed package path Closes #43 --- .github/workflows/python-publish.yml | 13 ++-- .github/workflows/test.yml | 29 +++++++++ .gitignore | 3 +- .readthedocs.yml | 9 ++- docs/requirements.txt | 7 +- docs/source/conf.py | 11 ++-- docs/source/plotting.rst | 1 + docs/source/vitals_data_frame.rst | 1 + gfdlvitals/__init__.py | 7 +- gfdlvitals/cli.py | 8 ++- scripts/db2nc => gfdlvitals/cli_db2nc.py | 25 ++------ scripts/plotdb => gfdlvitals/cli_plotdb.py | 15 ++--- gfdlvitals/plot.py | 4 +- gfdlvitals/sample.py | 6 +- gfdlvitals/util/gmeantools.py | 6 +- gfdlvitals/version.py | 3 - pyproject.toml | 74 ++++++++++++++++++++++ scripts/gfdlvitals | 10 --- setup.cfg | 56 ---------------- setup.py | 6 -- tests/__init__.py | 0 tests/test_basic.py | 40 ++++++++++++ 22 files changed, 203 insertions(+), 131 deletions(-) create mode 100644 .github/workflows/test.yml rename scripts/db2nc => gfdlvitals/cli_db2nc.py (90%) mode change 100755 => 100644 rename scripts/plotdb => gfdlvitals/cli_plotdb.py (93%) mode change 100755 => 100644 delete mode 100644 gfdlvitals/version.py create mode 100644 pyproject.toml delete mode 100755 scripts/gfdlvitals delete mode 100644 setup.cfg delete mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/test_basic.py diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 3bfabfc..96caa82 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,11 +1,6 @@ # This workflow will upload a Python Package using Twine when a release is created # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - name: Upload Python Package on: @@ -18,9 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies @@ -30,7 +27,7 @@ jobs: - name: Build package run: python -m build - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3725811 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,29 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[test]" + - name: Run tests + run: pytest diff --git a/.gitignore b/.gitignore index 98916bf..a88c730 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ *.swp *__pycache__ python/*.pyc -gfdlvitals.egg-info/ +*.egg-info/ +.venv/ testing/test_data testing/db testing/db-esm2 diff --git a/.readthedocs.yml b/.readthedocs.yml index f94758f..97c3631 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,8 +9,13 @@ version: 2 sphinx: configuration: docs/source/conf.py -# Optionally set the version of Python and requirements required to build your docs +build: + os: ubuntu-22.04 + tools: + python: "3.11" + python: - version: 3.8 install: + - method: pip + path: . - requirements: docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt index 910fbf5..c2e0ad0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,10 +1,11 @@ -cftime<=1.2.1 +cftime ipython jupyter matplotlib>=3.3.3 -nc-time-axis<=1.2.0 -netCDF4 +nc-time-axis +netCDF4 numpy pandas scipy +sphinx-rtd-theme xarray diff --git a/docs/source/conf.py b/docs/source/conf.py index 88b7b68..f6dd15b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -11,9 +11,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os -import sys -print(os.getcwd()) -sys.path.insert(0, os.path.abspath('../../')) +from importlib.metadata import version as _get_version # -- Project information ----------------------------------------------------- @@ -23,7 +21,8 @@ author = 'John Krasting' # The full version, including alpha/beta/rc tags -release = '3.0a1' +release = _get_version("gfdlvitals") +version = release # -- General configuration --------------------------------------------------- @@ -67,4 +66,6 @@ #-- Build api from sphinx.ext.apidoc import main -main(['-f', '-M', '-e', '-T', '../../gfdlvitals', '-o', 'api' ]) \ No newline at end of file +import gfdlvitals +_pkg_dir = os.path.dirname(gfdlvitals.__file__) +main(['-f', '-M', '-e', '-T', _pkg_dir, '-o', 'api' ]) \ No newline at end of file diff --git a/docs/source/plotting.rst b/docs/source/plotting.rst index 8648871..66e5779 100644 --- a/docs/source/plotting.rst +++ b/docs/source/plotting.rst @@ -15,6 +15,7 @@ options to smooth the data, overlay a trend line, and align offset time axes. .. ipython:: python + :okexcept: import gfdlvitals diff --git a/docs/source/vitals_data_frame.rst b/docs/source/vitals_data_frame.rst index f05be4c..eeb12d4 100644 --- a/docs/source/vitals_data_frame.rst +++ b/docs/source/vitals_data_frame.rst @@ -138,6 +138,7 @@ In the example below, the test historical dataset is artifically split into two 20-year epochs for comparison. .. ipython:: python + :okexcept: df_hist_t0 = df_hist[-40:-20] df_hist_t1 = df_hist[-20::] diff --git a/gfdlvitals/__init__.py b/gfdlvitals/__init__.py index 9d672db..5c412ad 100644 --- a/gfdlvitals/__init__.py +++ b/gfdlvitals/__init__.py @@ -1,6 +1,11 @@ """gfdlvitals - a package for computing global mean metrics""" -from .version import __version__ +from importlib.metadata import version, PackageNotFoundError + +try: + __version__ = version("gfdlvitals") +except PackageNotFoundError: + __version__ = "unknown" from . import averagers from . import cli diff --git a/gfdlvitals/cli.py b/gfdlvitals/cli.py index 6cb7bf0..a597035 100755 --- a/gfdlvitals/cli.py +++ b/gfdlvitals/cli.py @@ -5,10 +5,11 @@ import os import shutil import subprocess +import sys import tempfile import gfdlvitals -__all__ = ["arguments", "process_year", "run"] +__all__ = ["arguments", "process_year", "run", "main"] def arguments(args=None): @@ -195,3 +196,8 @@ def run(args): # -- Clean up os.chdir(cwd) shutil.rmtree(tempdir) + + +def main(): + """Entry point for the gfdlvitals command""" + run(sys.argv[1:]) diff --git a/scripts/db2nc b/gfdlvitals/cli_db2nc.py old mode 100755 new mode 100644 similarity index 90% rename from scripts/db2nc rename to gfdlvitals/cli_db2nc.py index 337e1d5..55534d1 --- a/scripts/db2nc +++ b/gfdlvitals/cli_db2nc.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 - -""" Command line utility to convert SQLite to NetCDF """ +"""Command line utility to convert SQLite to NetCDF""" import argparse import os @@ -12,7 +10,7 @@ def arguments(): - """Function captures the command-line aguments passed to this script""" + """Function captures the command-line arguments passed to this script""" description = """ Program for converting .db file format to NetCDF format. @@ -24,12 +22,10 @@ def arguments(): description=description, formatter_class=argparse.RawTextHelpFormatter ) - # -- Input tile parser.add_argument( "infile", type=str, help="Input file. Format must be sqlite (*.db)" ) - # -- Output file parser.add_argument( "-o", "--outfile", @@ -122,9 +118,6 @@ def write_nc( ncfile = nc.Dataset(outfile, "w", format=ncformat) ncfile.setncattr("source_file", dbfile) ncfile.setncattr("created", timestamp_str) - # ncfile.setncattr('experiment',expName) - # ncfile.setncattr('type',plotType) - # ncfile.setncattr('region',region) _ = ncfile.createDimension("time", 0) time = ncfile.createVariable("time", "f4", ("time",)) time.calendar = "noleap" @@ -134,9 +127,6 @@ def write_nc( if table not in ["long_name", "units", "cell_measure"]: data_array = np.ma.ones(len(years)) + 1.0e20 data_array.mask = True - # if 'Land' in plotType: - # extract_list = ['avg','sum'] - # else: extract_list = ["value"] for k in extract_list: count = 0 @@ -178,16 +168,15 @@ def write_nc( ncfile.close() -if __name__ == "__main__": - - # read command line arguments +def main(): + """Entry point for the db2nc command""" args = arguments() - # get the full path of the db file infile = os.path.realpath(args.infile) - # get list of variables and years in a file _tables, _years = tables_and_years(infile) write_nc( infile, args.outfile, _tables, _years, clobber=args.force, verbose=args.verbose ) -sys.exit() + +if __name__ == "__main__": + main() diff --git a/scripts/plotdb b/gfdlvitals/cli_plotdb.py old mode 100755 new mode 100644 similarity index 93% rename from scripts/plotdb rename to gfdlvitals/cli_plotdb.py index bb56658..5434a79 --- a/scripts/plotdb +++ b/gfdlvitals/cli_plotdb.py @@ -1,15 +1,10 @@ -#!/usr/bin/env python - -""" CLI script for plotting SQLite files """ +"""CLI script for plotting SQLite files""" import argparse import os -import matplotlib.pyplot as plt - import gfdlvitals -COUNT = 1 def arguments(): """ @@ -79,7 +74,11 @@ def arguments(): return args - -if __name__ == "__main__": +def main(): + """Entry point for the plotdb command""" cliargs = arguments() gfdlvitals.plot.run_plotdb(cliargs) + + +if __name__ == "__main__": + main() diff --git a/gfdlvitals/plot.py b/gfdlvitals/plot.py index eed6e9b..fd7685c 100644 --- a/gfdlvitals/plot.py +++ b/gfdlvitals/plot.py @@ -5,7 +5,7 @@ import nc_time_axis import numpy as np -import pkg_resources as pkgr +from importlib.resources import files import matplotlib import matplotlib.pyplot as plt @@ -21,7 +21,7 @@ def set_font(): """Sets font style to Roboto""" # Add Roboto font - fonts_dir = pkgr.resource_filename("gfdlvitals", "resources/fonts") + fonts_dir = str(files("gfdlvitals").joinpath("resources/fonts")) font_dirs = [fonts_dir] font_files = font_manager.findSystemFonts(fontpaths=font_dirs) diff --git a/gfdlvitals/sample.py b/gfdlvitals/sample.py index ab6135b..72ecedd 100644 --- a/gfdlvitals/sample.py +++ b/gfdlvitals/sample.py @@ -1,6 +1,6 @@ """ Sample db files for demonstration """ -import pkg_resources as pkgr +from importlib.resources import files -historical = pkgr.resource_filename("gfdlvitals", "resources/historical.db") -picontrol = pkgr.resource_filename("gfdlvitals", "resources/picontrol.db") +historical = str(files("gfdlvitals").joinpath("resources/historical.db")) +picontrol = str(files("gfdlvitals").joinpath("resources/picontrol.db")) diff --git a/gfdlvitals/util/gmeantools.py b/gfdlvitals/util/gmeantools.py index 63c06f0..9db320f 100644 --- a/gfdlvitals/util/gmeantools.py +++ b/gfdlvitals/util/gmeantools.py @@ -6,7 +6,7 @@ import warnings import numpy as np -import pkg_resources as pkgr +from importlib.resources import files __all__ = [ "get_web_vars_dict", @@ -29,9 +29,7 @@ def get_web_vars_dict(): dict LM3 variable module mappings and metadata """ - mapping_file = pkgr.resource_filename( - "gfdlvitals", "resources/LM3_variable_dictionary.pkl" - ) + mapping_file = str(files("gfdlvitals").joinpath("resources/LM3_variable_dictionary.pkl")) return pickle.load( open( mapping_file, diff --git a/gfdlvitals/version.py b/gfdlvitals/version.py deleted file mode 100644 index 8357025..0000000 --- a/gfdlvitals/version.py +++ /dev/null @@ -1,3 +0,0 @@ -"""momlevel: version information""" - -__version__ = "3.0.12" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d5d572c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,74 @@ +[build-system] +requires = ["setuptools>=64", "setuptools-git-versioning>=2.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "gfdlvitals" +dynamic = ["version"] +description = "Tools for calculating scalar diagnostics from GFDL models" +readme = {content-type = "text/x-rst", text = """ +**gfdlvitals** is a Python package in the public domain +that provides tools for calculating, storing, and analyzing +scalar diagnostics from GFDL's CM4-class models. + +More Information +---------------- +- Source code: ``_ +- Documentation: ``_ +"""} +requires-python = ">=3.9" +authors = [{name = "John Krasting", email = "john.krasting@noaa.gov"}] +keywords = ["climate", "model", "analysis", "scalar", "diagnostics", "gfdl", "AM4", "CM4", "ESM4", "OM4", "LM4"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "License :: Public Domain", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +dependencies = [ + "numpy", + "scipy", + "h5netcdf", + "pandas", + "netCDF4", + "matplotlib", + "xarray", + "cftime", + "nc-time-axis", +] + +[project.optional-dependencies] +test = ["pytest"] + +[project.urls] +Homepage = "https://github.com/jkrasting/gfdlvitals" +Documentation = "https://gfdlvitals.readthedocs.io/en/latest/" + +[project.scripts] +gfdlvitals = "gfdlvitals.cli:main" +db2nc = "gfdlvitals.cli_db2nc:main" +plotdb = "gfdlvitals.cli_plotdb:main" + +[tool.setuptools.packages.find] +include = ["gfdlvitals*"] + +[tool.setuptools.package-data] +gfdlvitals = [ + "resources/LM3_variable_dictionary.pkl", + "resources/historical.db", + "resources/picontrol.db", + "resources/fonts/Roboto/*", + "resources/fonts/Roboto_Condensed/*", +] + +[tool.setuptools-git-versioning] +enabled = true +dev_template = "{tag}.dev{ccount}+{sha}" +dirty_template = "{tag}.dev{ccount}+{sha}.dirty" + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/scripts/gfdlvitals b/scripts/gfdlvitals deleted file mode 100755 index d59c671..0000000 --- a/scripts/gfdlvitals +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 - -""" Command Line Script for running gfdlvitals """ - -import sys -import gfdlvitals.cli as cli - -if __name__ == "__main__": - cli.run(sys.argv[1::]) - sys.exit() diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c2be97e..0000000 --- a/setup.cfg +++ /dev/null @@ -1,56 +0,0 @@ -[metadata] -name = gfdlvitals -description = Tools for calculating scalar diagnostics from GFDL models -url = https://github.com/jkrasting/gfdlvitals -author = John Krasting -author_email = john.krasting@noaa.gov -keywords = climate model analysis scalar diagnostics gfdl AM4 CM4 ESM4 OM4 LM4 -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Science/Research - Topic :: Scientific/Engineering - License :: Public Domain - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 -long_description_content_type=text/x-rst -long_description = - **gfdlvitals** is a Python package in the public domain - that provides tools for calculating, storing, and analyzing - scalar diagnostics from GFDL's CM4-class models. - - More Information - ---------------- - - Source code: ``_ - - Documentation: ``_ - -[options] -packages = find_namespace: -zip_safe = False -include_package_data = True -python_requires = >=3.7 -install_requires = - setuptools - numpy - scipy - h5netcdf - pandas - netCDF4 - matplotlib - xarray - cftime - nc-time-axis -scripts = - scripts/gfdlvitals - scripts/db2nc - scripts/plotdb -setup_requires = - setuptools - -[options.package_data] -gfdlvitals = - resources/LM3_variable_dictionary.pkl - resources/historical.db - resources/picontrol.db - resources/fonts/Roboto/* - resources/fonts/Roboto_Condensed/* diff --git a/setup.py b/setup.py deleted file mode 100644 index 68b182c..0000000 --- a/setup.py +++ /dev/null @@ -1,6 +0,0 @@ -""" setup script """ -import setuptools - -exec(open("gfdlvitals/version.py").read()) - -setuptools.setup(version=__version__) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_basic.py b/tests/test_basic.py new file mode 100644 index 0000000..e4e1c33 --- /dev/null +++ b/tests/test_basic.py @@ -0,0 +1,40 @@ +"""Basic tests for gfdlvitals package""" + +import os + + +def test_import(): + import gfdlvitals + + +def test_version_exists(): + import gfdlvitals + assert isinstance(gfdlvitals.__version__, str) + assert len(gfdlvitals.__version__) > 0 + + +def test_submodule_imports(): + from gfdlvitals import averagers + from gfdlvitals import cli + from gfdlvitals import models + from gfdlvitals import sample + from gfdlvitals import util + + +def test_vitals_dataframe(): + from gfdlvitals import VitalsDataFrame + df = VitalsDataFrame({"a": [1, 2, 3]}) + assert len(df) == 3 + + +def test_sample_files_exist(): + from gfdlvitals import sample + assert os.path.isfile(sample.historical) + assert os.path.isfile(sample.picontrol) + + +def test_open_db(): + from gfdlvitals import open_db, sample, VitalsDataFrame + df = open_db(sample.historical) + assert isinstance(df, VitalsDataFrame) + assert len(df) > 0 From df916229700f8fe45809b428b019ad7ce464145a Mon Sep 17 00:00:00 2001 From: "John.Krasting" Date: Fri, 6 Mar 2026 13:29:15 -0500 Subject: [PATCH 2/4] Fix xarray FutureWarnings and streamline NaN warnings - Replace deprecated use_cftime kwarg with CFDatetimeCoder in xr.open_dataset calls and add decode_timedelta=False - Add compat="override" to xr.merge in xr_weighted_avg - Replace verbose warnings.warn with concise single-line print for NaN values in write_sqlite_data --- gfdlvitals/util/gmeantools.py | 9 +++------ gfdlvitals/util/netcdf.py | 5 +++-- gfdlvitals/util/xrtools.py | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/gfdlvitals/util/gmeantools.py b/gfdlvitals/util/gmeantools.py index 9db320f..4c15835 100644 --- a/gfdlvitals/util/gmeantools.py +++ b/gfdlvitals/util/gmeantools.py @@ -3,6 +3,7 @@ import math import pickle import sqlite3 +import sys import warnings import numpy as np @@ -283,16 +284,12 @@ def write_sqlite_data( # check if result is a nan and replace with a defined missing value if varmean is not None: if math.isnan(float(varmean)): - warnings.warn( - f"Could not update {sqlfile} variable {varname} with mean={varmean}" - ) + print(f" WARNING: {varname} mean is NaN in {sqlfile}, writing missing value", file=sys.stderr) varmean = missing_value if varsum is not None: if math.isnan(float(varsum)): - warnings.warn( - f"Could not update {sqlfile} variable {varname} with mean={varsum}" - ) + print(f" WARNING: {varname} sum is NaN in {sqlfile}, writing missing value", file=sys.stderr) varsum = missing_value conn = sqlite3.connect(sqlfile) diff --git a/gfdlvitals/util/netcdf.py b/gfdlvitals/util/netcdf.py index 7cf4ffe..5b33a4c 100644 --- a/gfdlvitals/util/netcdf.py +++ b/gfdlvitals/util/netcdf.py @@ -73,10 +73,11 @@ def in_mem_xr(data): In-memory xarray dataset object """ + time_coder = xr.coders.CFDatetimeCoder(use_cftime=True) if isinstance(data, netCDF4._netCDF4.Dataset): - dfile = xr.open_dataset(xr.backends.NetCDF4DataStore(data), use_cftime=True) + dfile = xr.open_dataset(xr.backends.NetCDF4DataStore(data), decode_times=time_coder, decode_timedelta=False) else: - dfile = xr.open_dataset(data, use_cftime=True) + dfile = xr.open_dataset(data, decode_times=time_coder, decode_timedelta=False) return dfile diff --git a/gfdlvitals/util/xrtools.py b/gfdlvitals/util/xrtools.py index 03bcbac..5dfecb6 100644 --- a/gfdlvitals/util/xrtools.py +++ b/gfdlvitals/util/xrtools.py @@ -97,6 +97,6 @@ def xr_weighted_avg(dset, weights): _dset_weighted[x] = _dset_weighted[x].astype(dset[x].dtype) _dset_weighted[x].attrs = dset[x].attrs - result = result.merge(_dset_weighted) + result = result.merge(_dset_weighted, compat="override") return result From 4e966476102f5c5c0b21f5ad56eec2b47300baa5 Mon Sep 17 00:00:00 2001 From: "John.Krasting" Date: Fri, 6 Mar 2026 13:34:08 -0500 Subject: [PATCH 3/4] Add xgcm, regionmask, and xoverturning to dependencies --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index d5d572c..649d02f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,9 @@ dependencies = [ "xarray", "cftime", "nc-time-axis", + "xgcm", + "regionmask", + "xoverturning", ] [project.optional-dependencies] From f8c86da34c65c823943f832cc3c615f15c6823e3 Mon Sep 17 00:00:00 2001 From: "John.Krasting" Date: Fri, 6 Mar 2026 14:05:21 -0500 Subject: [PATCH 4/4] Enable line-buffered stdout so status messages appear immediately --- gfdlvitals/cli.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gfdlvitals/cli.py b/gfdlvitals/cli.py index a597035..7fd399b 100755 --- a/gfdlvitals/cli.py +++ b/gfdlvitals/cli.py @@ -200,4 +200,5 @@ def run(args): def main(): """Entry point for the gfdlvitals command""" + sys.stdout.reconfigure(line_buffering=True) run(sys.argv[1:])