neural-amp-modeler

Neural network emulator for guitar amplifiers
Log | Files | Refs | README | LICENSE

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:
M.github/workflows/python-package.yml | 5+++--
Mdocs/source/installation.rst | 10++++++++++
Denvironments/requirements.txt | 25-------------------------
Mnam/cli.py | 18+++++++++++++++++-
Mnam/train/_version.py | 81++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mpyproject.toml | 5+++++
Mtests/test_nam/test_train/test_gui/test_main.py | 24++++++++++++++++--------
Mtests/test_nam/test_train/test_version.py | 46++++++++++++++++++++++++++++++++++++++++++++++
Mtests/test_nam/test_version.py | 1+
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__")