From a5a5b348d6b7c799271390ab03fff63dbc6fab50 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 19 Jan 2026 07:50:57 +0100 Subject: [PATCH 1/7] Added pre-commit config to run ruff check --- .pre-commit-config.yaml | 17 +++++++++++++++++ pyproject.toml | 4 ++++ 2 files changed, 21 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a96bda3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +default_install_hook_types: [pre-commit, pre-push] +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.13 + hooks: + - id: ruff-check + args: [--fix] + - repo: local + hooks: + - id: pytest + name: Run pytest + entry: uv run --frozen pytest --exitfirst + language: python + types: [python] + stages: [pre-push] + pass_filenames: false + always_run: true diff --git a/pyproject.toml b/pyproject.toml index b602488..2398ad0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,3 +36,7 @@ docs = [ "Sphinx~=8.1.3", "sphinx-rtd-theme~=3.0.2", ] +dev = [ + "pre-commit>=4.5.1", + "ruff>=0.14.13", +] From 9a7a069cb809bfa9d3f9c88f97d613a12173763d Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 19 Jan 2026 08:08:57 +0100 Subject: [PATCH 2/7] Added ruff configuration and address linting issues --- discid/__init__.py | 13 +++++++++++++ discid/libdiscid.py | 2 +- discid/util.py | 2 +- doc/conf.py | 12 ++++++------ pyproject.toml | 3 +++ 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/discid/__init__.py b/discid/__init__.py index f19b38b..32fa0d7 100644 --- a/discid/__init__.py +++ b/discid/__init__.py @@ -35,6 +35,19 @@ __version__ = "1.3.0" +__all__ = [ + "LIBDISCID_VERSION_STRING", + "FEATURES", + "FEATURES_IMPLEMENTED", + "Disc", + "Track", + "DiscError", + "TOCError", + "read", + "put", + "get_default_device", +] + # these constants are defined here so sphinx can catch the "docstrings" diff --git a/discid/libdiscid.py b/discid/libdiscid.py index c7eb308..36438b6 100644 --- a/discid/libdiscid.py +++ b/discid/libdiscid.py @@ -26,7 +26,7 @@ from ctypes import c_void_p, c_char_p from ctypes.util import find_library -from discid.util import _encode, _decode +from discid.util import _decode _LIB_BASE_NAME = "discid" _LIB_MAJOR_VERSION = 0 diff --git a/discid/util.py b/discid/util.py index 552c333..e0a9c15 100644 --- a/discid/util.py +++ b/discid/util.py @@ -37,7 +37,7 @@ def _decode(byte_string): """Decode byte string to (unicode) string """ # this test for bytes works on Python 2 and 3 - if type(byte_string) == type(b"test"): + if type(byte_string) is type(b"test"): return byte_string.decode() else: # probably mocked for sphinx diff --git a/doc/conf.py b/doc/conf.py index 4ac1241..c61a74d 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,9 @@ -import sys, os +import ctypes +import sys +import os + +# to gather version information +import discid sys.path.insert(0, os.path.abspath('.')) # for extensions sys.path.insert(0, os.path.abspath('..')) # for the code @@ -9,12 +14,8 @@ class Mock(object): def __call__(self, *args): return Mock() def __getattr__(cls, name): return Mock() -import ctypes ctypes.cdll.LoadLibrary = Mock() -# to gather version information -import discid - # -- General configuration ----------------------------------------------------- needs_sphinx = "1.0" @@ -121,4 +122,3 @@ def __getattr__(cls, name): return Mock() ] texinfo_domain_indices = False - diff --git a/pyproject.toml b/pyproject.toml index 2398ad0..e723abb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,3 +40,6 @@ dev = [ "pre-commit>=4.5.1", "ruff>=0.14.13", ] + +[tool.ruff] +src=["discid"] From b5a76648fbe067e8fec77ed5659df1b4ac9b520e Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 19 Jan 2026 08:18:54 +0100 Subject: [PATCH 3/7] Updated implementations of util._encode / util._decode We don't have to consider Python 2 specifics anymore and can aim for a cleaner implementation. --- discid/util.py | 18 +++++++++--------- test_discid.py | 12 +++++++++--- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/discid/util.py b/discid/util.py index e0a9c15..fd4c22e 100644 --- a/discid/util.py +++ b/discid/util.py @@ -22,26 +22,26 @@ SECTORS_PER_SECOND = 75 -def _encode(string): +def _encode(string: str|bytes): """Encode (unicode) string to byte string """ - try: + if isinstance(string, str): return string.encode() - except AttributeError: - # already byte string (Python 3) + elif isinstance(string, bytes): return string + raise TypeError('Unexpected type, expected string or bytes') # UnicodeDecodeError (Python 2) is NOT caught # device names should be ASCII -def _decode(byte_string): +def _decode(byte_string: bytes|str): """Decode byte string to (unicode) string """ # this test for bytes works on Python 2 and 3 - if type(byte_string) is type(b"test"): + if isinstance(byte_string, bytes): return byte_string.decode() - else: - # probably mocked for sphinx - return None + elif isinstance(byte_string, str): + return byte_string + raise TypeError('Unexpected type, expected string or bytes') def _sectors_to_seconds(sectors): """Round sectors to seconds like done on MusicBrainz Server diff --git a/test_discid.py b/test_discid.py index 50dd106..bb870ff 100755 --- a/test_discid.py +++ b/test_discid.py @@ -7,6 +7,7 @@ import unittest import discid +import discid.util test_discs = [ { @@ -37,13 +38,18 @@ class TestModulePrivate(unittest.TestCase): # lots of encoding tests # not part of the actual API, but this is quite different in Python 2/3 def test_encode(self): - self.assertTrue(type(discid.util._encode("test")) is type(b"test")) + self.assertTrue(type(discid.util._encode("test")) is bytes) self.assertEqual(discid.util._encode("test"), b"test") + self.assertEqual(discid.util._encode(b"test"), b"test") + with self.assertRaises(TypeError): + discid.util._encode(42) # type: ignore def test_decode(self): - self.assertTrue(type(discid.util._decode(b"test")) - is type(b"test".decode())) + self.assertTrue(type(discid.util._decode(b"test")) is str) self.assertEqual(discid.util._decode(b"test"), "test") + self.assertEqual(discid.util._decode("test"), "test") + with self.assertRaises(TypeError): + discid.util._encode(42) # type: ignore def test_encoding(self): string = "test" From 9b05d370b7405bc1b7652599a188d15014ec3cc8 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 19 Jan 2026 08:21:18 +0100 Subject: [PATCH 4/7] Run ruff in Github actions --- .github/workflows/lint.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/lint.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..60aba60 --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,17 @@ +name: Linting +on: + push: + paths: &path-list + - '**/*.py' + pull_request: + paths: *path-list + +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Ruff lint + uses: astral-sh/ruff-action@v3 + with: + args: "check --output-format=github" From d561523ddda87b36af704080c7157ab08ff613af Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 19 Jan 2026 08:36:15 +0100 Subject: [PATCH 5/7] Added ruff lint configuration and enabled specific tests --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index e723abb..291f858 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,3 +43,7 @@ dev = [ [tool.ruff] src=["discid"] + +[tool.ruff.lint] +select = ["E", "F", "W"] +ignore = ["E501"] From 4bd325978b293e63dbc96c33d844e36ac0ce5e9c Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 19 Jan 2026 08:41:09 +0100 Subject: [PATCH 6/7] Fixed: Do not use mutable data structures for argument defaults --- discid/disc.py | 7 +++++-- discid/libdiscid.py | 2 +- pyproject.toml | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/discid/disc.py b/discid/disc.py index b7569e7..5200d56 100644 --- a/discid/disc.py +++ b/discid/disc.py @@ -32,7 +32,7 @@ FEATURES_IMPLEMENTED = list(_FEATURE_MAPPING.keys()) -def read(device=None, features=[]): +def read(device=None, features=None): """Reads the TOC from the device given as string and returns a :class:`Disc` object. @@ -123,7 +123,7 @@ def _get_error_msg(self): _LIB.discid_read_sparse.restype = c_int except AttributeError: pass - def read(self, device=None, features=[]): + def read(self, device=None, features=None): """Reads the TOC from the device given as string The user is supposed to use :func:`discid.read`. @@ -131,6 +131,9 @@ def read(self, device=None, features=[]): if "read" not in FEATURES: raise NotImplementedError("discid_read not implemented on platform") + if features is None: + features = [] + # only use features implemented on this platform and in this module self._requested_features = list(set(features) & set(FEATURES) & set(FEATURES_IMPLEMENTED)) diff --git a/discid/libdiscid.py b/discid/libdiscid.py index 36438b6..7bf040e 100644 --- a/discid/libdiscid.py +++ b/discid/libdiscid.py @@ -155,7 +155,7 @@ def _get_features(): features = ["read"] # no generic platform yet try: # test for ISRC/MCN API (introduced 0.3.0) - _LIB.discid_get_mcn + _LIB.discid_get_mcn # noqa: B018 (useless-expression) except AttributeError: pass else: diff --git a/pyproject.toml b/pyproject.toml index 291f858..74a70d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,5 +45,5 @@ dev = [ src=["discid"] [tool.ruff.lint] -select = ["E", "F", "W"] +select = ["E", "F", "W", "B"] ignore = ["E501"] From 56b53f5eca08904e5ea18cedd76c77d0020b0a99 Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 19 Jan 2026 08:46:25 +0100 Subject: [PATCH 7/7] Enabled the ruff line-length check, but increased allowed length to 120 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 74a70d1..ef52bff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ dev = [ [tool.ruff] src=["discid"] +line-length=120 [tool.ruff.lint] select = ["E", "F", "W", "B"] -ignore = ["E501"]