Repository: lbl-anp/becquerel (language: python)

Issue:
Reconfigure how `__metadata__` is handled
Instead of writing `__metadata__.py` from information in the README and elsewhere, have `__metadata__.py` be in the repo and be the source of this other information.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/coveralls.yaml b/.github/workflows/coveralls.yaml
new file mode 100644
index 00000000..f37d9276
--- /dev/null
+++ b/.github/workflows/coveralls.yaml
@@ -0,0 +1,41 @@
+name: coveralls
+
+on:
+  push:
+    branches: [ $default-branch ]
+  pull_request:
+
+jobs:
+  coveralls_test:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python 3.8
+      uses: actions/setup-python@v2
+      with:
+        python-version: 3.8
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip setuptools wheel
+        pip install --only-binary=numpy,scipy -r requirements-dev.txt
+    - name: Install package
+      run: |
+        pip install -e .
+    - name: Test with pytest
+      run: |
+        pytest
+    - name: Coveralls
+      uses: AndreMiras/coveralls-python-action@develop
+      with:
+        parallel: true
+        flag-name: Unit Test
+
+  coveralls_finish:
+    needs: coveralls_test
+    runs-on: ubuntu-latest
+    steps:
+    - name: Coveralls Finished
+      uses: AndreMiras/coveralls-python-action@develop
+      with:
+        parallel-finished: true
+        debug: true
diff --git a/.gitignore b/.gitignore
index e9b191fc..93c9cd59 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,4 +176,3 @@ $RECYCLE.BIN/
 
 # Becquerel settings
 __df_cache_*.csv
-__metadata__.py
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
new file mode 100644
index 00000000..c6f4b2e9
--- /dev/null
+++ b/COPYRIGHT.txt
@@ -0,0 +1,13 @@
+Becquerel v. 0.3.0, Copyright (c) 2017, The Regents of the University of
+California (UC), through Lawrence Berkeley National Laboratory, and the UC
+Berkeley campus (subject to receipt of any required approvals from the U.S.
+Dept. of Energy). All rights reserved. If you have questions about your rights
+to use or distribute this software, please contact Berkeley Lab's Innovation &
+Partnerships Office at  IPO@lbl.gov.
+
+NOTICE. This Software was developed under funding from the U.S. Department of
+Energy and the U.S. Government consequently retains certain rights. As such,
+the U.S. Government has been granted for itself and others acting on its
+behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software
+to reproduce, distribute copies to the public, prepare derivative works, and
+perform publicly and display publicly, and to permit other to do so.
diff --git a/LICENSE.txt b/LICENSE.txt
index a8e0ebb4..72c21fc5 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,13 +1,20 @@
-*** License Agreement ***
-
 *** Copyright Notice ***
 
-Becquerel v. 0.3.0, Copyright (c) 2017, The Regents of the University of California (UC), through Lawrence Berkeley National Laboratory, and the UC Berkeley campus (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved.
-If you have questions about your rights to use or distribute this software, please contact Berkeley Lab's Innovation & Partnerships Office at  IPO@lbl.gov.
+Becquerel v. 0.3.0, Copyright (c) 2017, The Regents of the University of
+California (UC), through Lawrence Berkeley National Laboratory, and the UC
+Berkeley campus (subject to receipt of any required approvals from the U.S.
+Dept. of Energy). All rights reserved. If you have questions about your rights
+to use or distribute this software, please contact Berkeley Lab's Innovation &
+Partnerships Office at  IPO@lbl.gov.
 
-NOTICE.  This Software was developed under funding from the U.S. Department of Energy and the U.S. Government consequently retains certain rights.  As such, the U.S. Government has been granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software to reproduce, distribute copies to the public, prepare derivative works, and perform publicly and display publicly, and to permit other to do so.
+NOTICE. This Software was developed under funding from the U.S. Department of
+Energy and the U.S. Government consequently retains certain rights. As such,
+the U.S. Government has been granted for itself and others acting on its
+behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software
+to reproduce, distribute copies to the public, prepare derivative works, and
+perform publicly and display publicly, and to permit other to do so.
 
-**************************************************************************
+*** License Agreement ***
 
  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 
@@ -20,5 +27,3 @@ NOTICE.  This Software was developed under funding from the U.S. Department of E
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 You are under no obligation whatsoever to provide any bug fixes, patches, or upgrades to the features, functionality or performance of the source code ("Enhancements") to anyone; however, if you choose to make your Enhancements available either publicly, or directly to Lawrence Berkeley National Laboratory, without imposing a separate written license agreement for such Enhancements, then you hereby grant the following license to Lawrence Berkeley National Laboratory, University if California, Berkeley, and the U.S. Dept. of Energy: a  non-exclusive, royalty-free perpetual license to install, use, modify, prepare derivative works, incorporate into other computer software, distribute, and sublicense such enhancements or derivative works thereof, in binary and source code form.
-
-***************************************************************************
diff --git a/README.md b/README.md
index 794a8ee0..4c2d73f6 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,8 @@
 # becquerel
 
+[![tests](https://github.com/lbl-anp/becquerel/actions/workflows/tests.yaml/badge.svg)](https://github.com/lbl-anp/becquerel/actions/workflows/tests.yaml)
+[![Coverage Status](https://coveralls.io/repos/github/lbl-anp/becquerel/badge.svg?branch=main)](https://coveralls.io/github/lbl-anp/becquerel?branch=main)
+
 Becquerel is a Python package for analyzing nuclear spectroscopic
 measurements. The core functionalities are reading and writing different
 spectrum file types, fitting spectral features, performing detector
@@ -60,8 +63,8 @@ Dept. of Energy). All rights reserved. If you have questions about your rights
 to use or distribute this software, please contact Berkeley Lab's Innovation &
 Partnerships Office at  IPO@lbl.gov.
 
-NOTICE.  This Software was developed under funding from the U.S. Department of
-Energy and the U.S. Government consequently retains certain rights.  As such,
+NOTICE. This Software was developed under funding from the U.S. Department of
+Energy and the U.S. Government consequently retains certain rights. As such,
 the U.S. Government has been granted for itself and others acting on its
 behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software
 to reproduce, distribute copies to the public, prepare derivative works, and
diff --git a/becquerel/__init__.py b/becquerel/__init__.py
index 454ace1a..26c8c60f 100644
--- a/becquerel/__init__.py
+++ b/becquerel/__init__.py
@@ -1,17 +1,18 @@
 """Becquerel: Tools for radiation spectral analysis."""
 
-from . import core
-from . import parsers
-from . import tools
-from .__metadata__ import __description__, __url__
-from .__metadata__ import __version__, __license__, __copyright__
+from .__metadata__ import (
+    __description__,
+    __url__,
+    __version__,
+    __license__,
+    __copyright__,
+)
 
-from .core.rebin import rebin, RebinError, RebinWarning
-from .core.spectrum import Spectrum, SpectrumError, UncalibratedError
-from .core.spectrum import SpectrumWarning
+from . import core
+from .core import utils, fitting
+from .core.autocal import AutoCalibrator, AutoCalibratorError
 from .core.energycal import LinearEnergyCal, EnergyCalError, BadInput
-from .core.utils import UncertaintiesError
-from .core.plotting import SpectrumPlotter, PlottingError
+from .core.fitting import Fitter
 from .core.peakfinder import (
     PeakFilter,
     PeakFilterError,
@@ -19,50 +20,59 @@
     PeakFinder,
     PeakFinderError,
 )
-from .core.autocal import AutoCalibrator, AutoCalibratorError
+from .core.plotting import SpectrumPlotter, PlottingError
+from .core.rebin import rebin, RebinError, RebinWarning
+from .core.spectrum import Spectrum, SpectrumError, UncalibratedError, SpectrumWarning
+from .core.utils import UncertaintiesError
 
-from .core import utils
-from .core import fitting
-from .core.fitting import Fitter
+from . import parsers
 
-from .tools import nndc
+from . import tools
+from .tools import nndc, xcom, materials
 from .tools.element import Element
 from .tools.isotope import Isotope
 from .tools.isotope_qty import IsotopeQuantity
-from .tools import xcom
-from .tools import materials
 
 import warnings
 
 warnings.simplefilter("default", DeprecationWarning)
 
 __all__ = [
+    "__description__",
+    "__url__",
+    "__version__",
+    "__license__",
+    "__copyright__",
     "core",
-    "parsers",
-    "tools",
-    "rebin",
-    "RebinError",
-    "RebinWarning",
-    "Spectrum",
-    "SpectrumError",
-    "SpectrumWarning",
-    "SpectrumPlotter",
-    "PlottingError",
-    "UncalibratedError",
+    "utils",
+    "fitting",
+    "AutoCalibrator",
+    "AutoCalibratorError",
     "LinearEnergyCal",
     "EnergyCalError",
     "BadInput",
-    "UncertaintiesError",
+    "Fitter",
     "PeakFilter",
     "PeakFilterError",
     "GaussianPeakFilter",
     "PeakFinder",
     "PeakFinderError",
-    "AutoCalibrator",
-    "AutoCalibratorError",
-    "__description__",
-    "__url__",
-    "__version__",
-    "__license__",
-    "__copyright__",
+    "SpectrumPlotter",
+    "PlottingError",
+    "rebin",
+    "RebinError",
+    "RebinWarning",
+    "Spectrum",
+    "SpectrumError",
+    "UncalibratedError",
+    "SpectrumWarning",
+    "UncertaintiesError",
+    "parsers",
+    "tools",
+    "nndc",
+    "xcom",
+    "materials",
+    "Element",
+    "Isotope",
+    "IsotopeQuantity",
 ]
diff --git a/becquerel/__metadata__.py b/becquerel/__metadata__.py
new file mode 100644
index 00000000..c5765008
--- /dev/null
+++ b/becquerel/__metadata__.py
@@ -0,0 +1,40 @@
+"""becquerel package metadata."""
+
+__name__ = "becquerel"
+__author__ = "The Becquerel Development Team"
+__maintainer__ = __author__
+__email__ = "becquerel-dev@lbl.gov"
+__description__ = "Tools for radiation spectral analysis."
+__url__ = "https://github.com/lbl-anp/becquerel"
+__version__ = "0.3.0"
+# classifiers from list at https://pypi.org/classifiers/
+__classifiers__ = [
+    "Development Status :: 4 - Beta",
+    "Intended Audience :: Science/Research",
+    "License :: Other/Proprietary License",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3.6",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Topic :: Scientific/Engineering",
+    "Topic :: Scientific/Engineering :: Physics",
+]
+__license__ = "Other/Proprietary License (see LICENSE.txt)"
+__copyright__ = """\
+Becquerel v. 0.3.0, Copyright (c) 2017, The Regents of the University of
+California (UC), through Lawrence Berkeley National Laboratory, and the UC
+Berkeley campus (subject to receipt of any required approvals from the U.S.
+Dept. of Energy). All rights reserved. If you have questions about your rights
+to use or distribute this software, please contact Berkeley Lab's Innovation &
+Partnerships Office at  IPO@lbl.gov.
+
+NOTICE. This Software was developed under funding from the U.S. Department of
+Energy and the U.S. Government consequently retains certain rights. As such,
+the U.S. Government has been granted for itself and others acting on its
+behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software
+to reproduce, distribute copies to the public, prepare derivative works, and
+perform publicly and display publicly, and to permit other to do so.
+"""
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 8af56357..83c92555 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,4 +1,6 @@
 -r requirements.txt
+coverage
+flake8
 black>=20.8b1
 pytest>=3.0.0
 pytest-cov
diff --git a/setup.cfg b/setup.cfg
index 32c726c6..b8c5ab62 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [aliases]
-test=pytest
+test = pytest
 
 [tool:pytest]
 addopts = --black --cov=becquerel --cov-report term --cov-report html:htmlcov -m "not plottest"
@@ -10,3 +10,6 @@ filterwarnings =
     always
     ; Not fixing the problem: https://github.com/pytest-dev/pytest/pull/3613
     ignore:.*t resolve package from __spec__ or __package__.*:ImportWarning
+
+[coverage:run]
+relative_files = True
diff --git a/setup.py b/setup.py
index 30ade05a..72bdfc2f 100644
--- a/setup.py
+++ b/setup.py
@@ -4,88 +4,57 @@
 import sys
 import site
 from setuptools import setup, find_packages
+import importlib.util
 
 # Enables --editable install with --user
 # https://github.com/pypa/pip/issues/7953
 site.ENABLE_USER_SITE = "--user" in sys.argv[1:]
 
-NAME = "becquerel"
-
-MAJOR = 0
-MINOR = 3
-MICRO = 0
-VERSION = f"{MAJOR}.{MINOR}.{MICRO}"
-
-DESCRIPTION = __doc__.split("\n")[0].split(": ")[-1]
-URL = "https://github.com/lbl-anp/becquerel"
-MAINTAINER = "The Becquerel Development Team"
-EMAIL = "becquerel-dev@lbl.gov"
+_spec = importlib.util.spec_from_file_location(
+    "__metadata__", "./becquerel/__metadata__.py"
+)
+METADATA = importlib.util.module_from_spec(_spec)
+_spec.loader.exec_module(METADATA)
 
-# classifiers from list at https://pypi.org/classifiers/
-CLASSIFIERS = """\
-Development Status :: 3 - Alpha
-Intended Audience :: Science/Research
-License :: Other/Proprietary License
-Operating System :: OS Independent
-Programming Language :: Python
-Programming Language :: Python :: 3
-Programming Language :: Python :: 3 :: Only
-Programming Language :: Python :: 3.6
-Programming Language :: Python :: 3.7
-Programming Language :: Python :: 3.8
-Topic :: Scientific/Engineering
-Topic :: Scientific/Engineering :: Physics
-"""
+# Enables --editable install with --user
+# https://github.com/pypa/pip/issues/7953
+site.ENABLE_USER_SITE = "--user" in sys.argv[1:]
 
-with open("README.md", "r") as fh:
-    README = fh.read()
 # remove package title from description
-README = "\n".join(README.split("\n")[2:])
+with open("README.md", "r") as fh:
+    README = "\n".join(fh.readlines()[2:])
 
 with open("CONTRIBUTING.md", "r") as fh:
     CONTRIBUTING = fh.read()
 
-with open("LICENSE.txt", "r") as fh:
-    LICENSE = fh.read()
-
 with open("requirements.txt", "r") as fh:
-    REQUIREMENTS = fh.read()
+    REQUIREMENTS = [_line for _line in fh.readlines() if _line]
 
 with open("requirements-dev.txt", "r") as fh:
     REQUIREMENTS_DEV = [l.strip() for l in fh.readlines() if not l.startswith("-r")]
 
 # make long description from README and CONTRIBUTING
 # but move copyright notice to the end
-LONG_DESCRIPTION, COPYRIGHT = README.split("## Copyright Notice")
-LONG_DESCRIPTION += "\n" + CONTRIBUTING
-LONG_DESCRIPTION += "\n" + "## Copyright Notice" + COPYRIGHT
-
-# write metadata to a file that will be imported by becquerel
-with open("becquerel/__metadata__.py", "w") as f:
-    print('"""Becquerel package metadata."""', file=f)
-    print("", file=f)
-    print(f'__description__ = "{DESCRIPTION}"', file=f)
-    print(f'__url__ = "{URL}"', file=f)
-    print(f'__version__ = "{VERSION}"', file=f)
-    print(f'__license__ = """{LICENSE}"""', file=f)
-    print(f'__copyright__ = """{COPYRIGHT}"""', file=f)
+LONG_DESCRIPTION = "{0}\n{2}\n## Copyright Notice\n{1}".format(
+    *README.split("## Copyright Notice"), CONTRIBUTING
+)
 
 setup(
-    name=NAME,
-    version=VERSION,
-    description=DESCRIPTION,
+    name=METADATA.__name__,
+    version=METADATA.__version__,
+    description=METADATA.__description__,
     long_description=LONG_DESCRIPTION,
     long_description_content_type="text/markdown",
-    url=URL,
-    download_url=URL + "/releases",
-    maintainer=MAINTAINER,
-    maintainer_email=EMAIL,
-    classifiers=[_f for _f in CLASSIFIERS.split("\n") if _f],
+    url=METADATA.__url__,
+    download_url=f"{METADATA.__url__}/releases",
+    maintainer=METADATA.__maintainer__,
+    maintainer_email=METADATA.__email__,
+    classifiers=METADATA.__classifiers__,
     platforms="any",
     packages=find_packages(),
     python_requires=">=3.6",
-    install_requires=[_f for _f in REQUIREMENTS.split("\n") if _f],
+    install_requires=REQUIREMENTS,
     setup_requires=["pytest-runner"],
+    license=METADATA.__license__,
     tests_require=REQUIREMENTS_DEV,
-    license="Other/Proprietary License (see LICENSE.txt)",
 )


Test patch:
diff --git a/.github/workflows/pip_build_test.yaml b/.github/workflows/tests.yaml
similarity index 92%
rename from .github/workflows/pip_build_test.yaml
rename to .github/workflows/tests.yaml
index e0f6a5fd..f0ca7c3e 100644
--- a/.github/workflows/pip_build_test.yaml
+++ b/.github/workflows/tests.yaml
@@ -1,9 +1,9 @@
 # This workflow will install dependencies, install becquerel and run tests.
-# It runs on multiple python3 versions on macOS and Ubuntu.
+# It runs on multiple python3 versions on macOS, Ubuntu and Windows.
 # It runs when a commit (merge) is push to the main (default) branch or for updates to a PR targeting main.
 # Based on: https://github.com/actions/starter-workflows/blob/main/ci/python-package.yml
 
-name: Python package
+name: tests
 
 on:
 
@@ -38,7 +38,7 @@ jobs:
           pip install -r requirements-dev.txt
       - name: Install package
         run: |
-          pip install -e .
+          pip install .
       - name: Test with pytest
         run: |
           python -c 'import becquerel;'
diff --git a/tests/autocal_test.py b/tests/autocal_test.py
index a6abaac9..407840e2 100644
--- a/tests/autocal_test.py
+++ b/tests/autocal_test.py
@@ -8,14 +8,11 @@
 
 
 # read in spectra
-filename1 = os.path.join(os.path.dirname(bq.__file__), "../tests/samples/sim_spec.csv")
-filename2 = os.path.join(
-    os.path.dirname(bq.__file__), "../tests/samples/Mendocino_07-10-13_Acq-10-10-13.Spe"
-)
-filename3 = os.path.join(
-    os.path.dirname(bq.__file__), "../tests/samples/nai_detector.csv"
-)
-filename4 = os.path.join(os.path.dirname(bq.__file__), "../tests/samples/SGM102432.csv")
+SAMPLES_PATH = os.path.join(os.path.dirname(__file__), "samples")
+filename1 = os.path.join(SAMPLES_PATH, "sim_spec.csv")
+filename2 = os.path.join(SAMPLES_PATH, "Mendocino_07-10-13_Acq-10-10-13.Spe")
+filename3 = os.path.join(SAMPLES_PATH, "nai_detector.csv")
+filename4 = os.path.join(SAMPLES_PATH, "SGM102432.csv")
 
 counts = []
 with open(filename1, "r") as f:
diff --git a/tests/metadata_test.py b/tests/metadata_test.py
new file mode 100644
index 00000000..114078f6
--- /dev/null
+++ b/tests/metadata_test.py
@@ -0,0 +1,46 @@
+"""Ensure the metadata is updated and valid."""
+from utils import REPO_PATH
+import becquerel as bq
+
+COPYRIGHT = (REPO_PATH / "COPYRIGHT.txt").read_text()
+LICENSE = (REPO_PATH / "LICENSE.txt").read_text()
+README = (REPO_PATH / "README.md").read_text()
+COPYRIGHT_IN_LICENSE = (
+    LICENSE.split("*** Copyright Notice ***")[-1]
+    .split("*** License Agreement ***")[0]
+    .strip()
+    + "\n"
+)
+
+
+class TestCopyright:
+    def test_license(self):
+        assert COPYRIGHT == COPYRIGHT_IN_LICENSE
+
+    def test_readme(self):
+        assert README.endswith(COPYRIGHT)
+
+    def test_metadata(self):
+        assert bq.__metadata__.__copyright__ == COPYRIGHT
+
+
+class TestVersion:
+    def test_copyright(self):
+        assert (
+            COPYRIGHT.split("Becquerel v. ")[1].split(",")[0]
+            == bq.__metadata__.__version__
+        )
+
+    def test_license(self):
+        assert (
+            LICENSE.split("Becquerel v. ")[1].split(",")[0]
+            == bq.__metadata__.__version__
+        )
+
+    def test_readme(self):
+        assert (
+            README.split("## Copyright Notice")[1]
+            .split("Becquerel v. ")[1]
+            .split(",")[0]
+            == bq.__metadata__.__version__
+        )
diff --git a/tests/rebin_test.py b/tests/rebin_test.py
index a61d411b..15224774 100644
--- a/tests/rebin_test.py
+++ b/tests/rebin_test.py
@@ -142,7 +142,7 @@ def _test_unchanged_binning(self, lam, edges, method, dtype):
         assert np.allclose(old_counts, new_counts)
 
     def _gen_old_new_counts(self, lam, old_edges, new_edges, method, dtype):
-        """ generate old and new counts (1D)"""
+        """generate old and new counts (1D)"""
         old_counts = make_fake_spec_array(lam, len(old_edges) - 1, dtype=dtype)
         new_counts = bq.core.rebin.rebin(
             old_counts, old_edges, new_edges, method=method, zero_pad_warnings=False
diff --git a/tests/utils.py b/tests/utils.py
new file mode 100644
index 00000000..94b6fa76
--- /dev/null
+++ b/tests/utils.py
@@ -0,0 +1,5 @@
+"""Helpers for testing."""
+from pathlib import Path
+
+TESTS_PATH = Path(__file__).parent.absolute()
+REPO_PATH = TESTS_PATH.parent