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" 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/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/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 c7eb308..7bf040e 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 @@ -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/discid/util.py b/discid/util.py index 552c333..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) == 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/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 b602488..ef52bff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,3 +36,14 @@ docs = [ "Sphinx~=8.1.3", "sphinx-rtd-theme~=3.0.2", ] +dev = [ + "pre-commit>=4.5.1", + "ruff>=0.14.13", +] + +[tool.ruff] +src=["discid"] +line-length=120 + +[tool.ruff.lint] +select = ["E", "F", "W", "B"] 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"