commit b47f53f8c3141aa5d3d276921997348feaff3942
parent af1c4d5137c50eb9b30e626e476455628c0ed3f0
Author: Steven Atkinson <steven@atkinson.mn>
Date: Sat, 15 Feb 2025 12:49:07 -0600
[BUGFIX] Fix GUI (#533)
* Fix GUI
Fix version parsing
Add more tests for version class
* Update train/_version.py
Better error message
* Add hello world CLI entry point to verify installation
* Simplify install process for testing
* Hack for CI/CD version determined by pyproject.toml
* Clean up development
Remove environments/requirements.txt
Add docuemntation recommending to use Anaconda environment definitions.
Diffstat:
9 files changed, 176 insertions(+), 39 deletions(-)
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
@@ -27,15 +27,16 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- python -m pip install flake8 pytest
- if [ -f environments/requirements.txt ]; then pip install -r environments/requirements.txt; fi
python -m pip install .
+ nam-hello-world
- name: Lint with flake8
run: |
+ python -m pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
+ python -m pip install pytest pytest-mock
xvfb-run -a pytest
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
@@ -33,3 +33,13 @@ To update an existing installation:
.. code-block:: console
pip install --upgrade neural-amp-modeler
+
+Local development installation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you're interested in developing this package, there are Anaconda environment
+definitions included in the ``environments/`` directory. Use the one that's
+appropriate for the platform you're developing on. The
+``.github/workflows/python-pckage.yml`` is also helpful if you want to be sure
+that you're testing your developments in the same way that contributions will be
+automatically tested (via GitHub Actions).
diff --git a/environments/requirements.txt b/environments/requirements.txt
@@ -1,25 +0,0 @@
-# File: requirements.txt
-# Created Date: 2021-01-24
-# Author: Steven Atkinson (steven@atkinson.mn)
-
-auraloss==0.3.0 # 0.4.0 changes API for MRSTFT loss
-black
-flake8
-matplotlib
-numpy<2
-pip
-pre-commit
-pydantic>=2.0.0
-pytest
-pytest-mock
-pytorch_lightning
-scipy
-sounddevice
-# Performance note: https://github.com/sdatkinson/neural-amp-modeler/issues/505
-torch
-# `transformers` is not required, but if you have it, it needs to be recent
-# enough so I'm adding it.
-transformers>=4
-tqdm
-wavio
-wheel
diff --git a/nam/cli.py b/nam/cli.py
@@ -82,10 +82,26 @@ from argparse import ArgumentParser as _ArgumentParser
from pathlib import Path as _Path
from nam.train.full import main as _nam_full
-from nam.train.gui import run as _nam_gui # noqa F401 Used as an entry point
+from nam.train.gui import run as nam_gui # noqa F401 Used as an entry point
from nam.util import timestamp as _timestamp
+def nam_hello_world():
+ """
+ This is a minimal CLI entry point that's meant to be used to ensure that NAM
+ was installed successfully
+ """
+ from nam import __version__
+ msg = f"""
+ Neural Amp Modeler
+
+ by Steven Atkinson
+
+ Version {__version__}
+ """
+ print(msg)
+
+
def nam_full():
parser = _ArgumentParser()
parser.add_argument("data_config_path", type=str)
diff --git a/nam/train/_version.py b/nam/train/_version.py
@@ -6,25 +6,68 @@
Version utility
"""
+from typing import Optional as _Optional
+
from .._version import __version__
+class IncomparableVersionError(ValueError):
+ """
+ Error raised when two versions can't be compared.
+ """
+
+ pass
+
+
class Version:
- def __init__(self, major: int, minor: int, patch: int):
+ def __init__(self, major: int, minor: int, patch: int, dev: _Optional[str] = None):
self.major = major
self.minor = minor
self.patch = patch
+ self.dev = dev
+ self.dev_int = self._parse_dev_int(dev)
@classmethod
def from_string(cls, s: str):
- major, minor, patch = [int(x) for x in s.split(".")]
- return cls(major, minor, patch)
+ def special_case(s: str) -> _Optional[dict]:
+ """
+ Regretful hacks
+ """
+ # It seems like the git repo isn't accessible to setuptools_scm's version
+ # guesser, so it comes up with this during install:
+ if s == "0.1.dev1":
+ # This will be fine.
+ return {
+ "major": 0,
+ "minor": 1,
+ "patch": 0,
+ "dev": "dev1"
+ }
+ return None
+
+ if special_case(s) is not None:
+ return cls(**special_case(s))
+
+ # Typical
+ parts = s.split(".")
+ if len(parts) == 3: # e.g. "0.7.1"
+ dev = None
+ elif len(parts) == 4: # e.g. "0.7.1.dev7"
+ dev = parts[3]
+ else:
+ raise ValueError(f"Invalid version string {s}")
+ try:
+ major, minor, patch = [int(x) for x in parts[:3]]
+ except ValueError as e:
+ raise ValueError(f"Failed to parse version from string '{s}':\n{e}")
+ return cls(major=major, minor=minor, patch=patch, dev=dev)
def __eq__(self, other) -> bool:
return (
self.major == other.major
and self.minor == other.minor
and self.patch == other.patch
+ and self.dev == other.dev
)
def __lt__(self, other) -> bool:
@@ -36,10 +79,42 @@ class Version:
return self.minor < other.minor
if self.patch != other.patch:
return self.patch < other.patch
+ if self.dev != other.dev:
+ # None is defined as least
+ if self.dev is None and other.dev is not None:
+ return True
+ elif self.dev is not None and other.dev is None:
+ return False
+ assert self.dev is not None
+ assert other.dev is not None
+ if self.dev_int is None:
+ raise IncomparableVersionError(
+ f"Version {str(self)} has incomparable dev version {self.dev}"
+ )
+ if other.dev_int is None:
+ raise IncomparableVersionError(
+ f"Version {str(other)} has incomparable dev version {other.dev}"
+ )
+ return self.dev_int < other.dev_int
+ raise RuntimeError(
+ f"Unhandled comparison between versions {str(self)} and {str(other)}"
+ )
def __str__(self) -> str:
return f"{self.major}.{self.minor}.{self.patch}"
+ def _parse_dev_int(self, dev: _Optional[str]) -> _Optional[int]:
+ """
+ Turn the string into an int that can be compared if possible.
+ """
+ if dev is None:
+ return None
+ if not isinstance(dev, str):
+ raise TypeError(f"Invalid dev string type {type(dev)}")
+ if not dev.startswith("dev") or len(dev) <= 3: # "misc", "dev", etc
+ return None
+ return int(dev.removeprefix("dev"))
+
PROTEUS_VERSION = Version(4, 0, 0)
diff --git a/pyproject.toml b/pyproject.toml
@@ -14,12 +14,16 @@ requires-python = ">=3.8"
dependencies = [
"auraloss==0.3.0",
"matplotlib",
+ "numpy<2",
"pydantic>=2.0.0",
"pytorch_lightning",
"scipy",
"sounddevice",
"tensorboard",
"torch",
+ # `transformers` is not required, but if you have it, it needs to be recent
+ # enough so I'm adding it.
+ "transformers>=4",
"tqdm",
"wavio>=0.0.5",
]
@@ -44,6 +48,7 @@ homepage = "https://github.com/sdatkinson/"
[project.scripts]
nam = "nam.cli:nam_gui"
nam-full = "nam.cli:nam_full"
+nam-hello-world = "nam.cli:nam_hello_world"
[tool.setuptools_scm]
version_scheme = "guess-next-dev"
diff --git a/tests/test_nam/test_train/test_gui/test_main.py b/tests/test_nam/test_train/test_gui/test_main.py
@@ -9,14 +9,22 @@ import pytest
from nam.train import gui
-class TestPathButton(object):
- def test_system_text_color(self):
- """
- Issue 428
- """
- top_level = tk.Toplevel()
- label = tk.Label(master=top_level, text="My text", fg=gui._SYSTEM_TEXT_COLOR)
- label.pack()
+# class TestPathButton(object):
+# def test_system_text_color(self):
+# """
+# Issue 428
+# """
+# top_level = tk.Toplevel()
+# label = tk.Label(master=top_level, text="My text", fg=gui._SYSTEM_TEXT_COLOR)
+# label.pack()
+
+
+def test_get_current_version():
+ """
+ Make sure this at least runs!
+ See #516
+ """
+ v = gui._get_current_version()
if __name__ == "__main__":
diff --git a/tests/test_nam/test_train/test_version.py b/tests/test_nam/test_train/test_version.py
@@ -2,14 +2,32 @@
# Created Date: Saturday April 29th 2023
# Author: Steven Atkinson (steven@atkinson.mn)
+"""
+Tests for version class
+"""
+
+import pytest as _pytest
+
from nam.train import _version
+def test_dev_int():
+ """
+ Assert that dev_int is properly parsed
+ """
+ assert _version.Version(0, 0, 0).dev_int is None
+ assert _version.Version(0, 0, 0, "dev").dev_int is None
+ assert _version.Version(0, 0, 0, "misc").dev_int is None
+ assert _version.Version(0, 0, 0, "dev11").dev_int == 11
+
+
def test_eq():
assert _version.Version(0, 0, 0) == _version.Version(0, 0, 0)
assert _version.Version(0, 0, 0) != _version.Version(0, 0, 1)
assert _version.Version(0, 0, 0) != _version.Version(0, 1, 0)
assert _version.Version(0, 0, 0) != _version.Version(1, 0, 0)
+ assert _version.Version(0, 0, 0) != _version.Version(0, 0, 0, dev="dev0")
+ assert _version.Version(0, 0, 0) != _version.Version(0, 0, 0, dev="dev1")
def test_lt():
@@ -20,3 +38,31 @@ def test_lt():
assert _version.Version(1, 2, 3) < _version.Version(2, 0, 0)
assert not _version.Version(1, 2, 3) < _version.Version(0, 4, 5)
+
+
+def test_lt_incomparable():
+ """
+ Assert that the error is properly raised for incomparable versions
+ """
+ with _pytest.raises(_version.IncomparableVersionError):
+ _version.Version(0, 0, 0, "incomparable") < _version.Version(0, 0, 0, "dev1")
+
+
+def test_current_version():
+ """
+ Test that the current version is valid
+ """
+ from nam import __version__
+
+ # First off, assert that the current version can be understood by _version.Version.
+ # Broken by PR 516 (pyproject.toml)--watch out!
+ v = _version.Version.from_string(__version__)
+
+ # Check comparisons like used in GUI:
+ assert _version.Version(0, 0, 0) != v
+ assert _version.Version(0, 0, 0) < v
+ # Just checking both orders. If we're actually at this version, then fine, move it up!
+ high_major_version_that_we_will_probably_never_get_to = 1000
+ assert v < _version.Version(
+ high_major_version_that_we_will_probably_never_get_to, 0, 0
+ )
diff --git a/tests/test_nam/test_version.py b/tests/test_nam/test_version.py
@@ -6,6 +6,7 @@ import pytest as _pytest
import nam as _nam
+
def test_has_version():
assert hasattr(_nam, "__version__")