Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -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"
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions discid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
7 changes: 5 additions & 2 deletions discid/disc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -123,14 +123,17 @@ 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`.
"""
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))
Expand Down
4 changes: 2 additions & 2 deletions discid/libdiscid.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
18 changes: 9 additions & 9 deletions discid/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions doc/conf.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -121,4 +122,3 @@ def __getattr__(cls, name): return Mock()
]

texinfo_domain_indices = False

11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
12 changes: 9 additions & 3 deletions test_discid.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import unittest

import discid
import discid.util

test_discs = [
{
Expand Down Expand Up @@ -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"
Expand Down