diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c6ca25d4..6ab85b4a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -38,3 +38,25 @@ jobs: - name: Run tests run: uv run --no-sync pytest -v + + ruff: + name: Ruff ${{ matrix.command }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # check and format are separate so a failure names which one + command: ["check", "format --check"] + steps: + - uses: actions/checkout@v7 + + - name: Install uv + uses: astral-sh/setup-uv@v9.0.0 + with: + enable-cache: true + + - name: Install the project + run: uv sync --locked + + - name: Run ruff ${{ matrix.command }} + run: uv run --no-sync ruff ${{ matrix.command }} . diff --git a/pyproject.toml b/pyproject.toml index 117d0aa3..8ee82b4b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,10 @@ audible-quickstart = "audible_cli:quickstart" [dependency-groups] dev = [ "pyinstaller", - "pytest>=8.4.2" + "pytest>=8.4.2", + # Pinned: the enabled rule families pull in new stable rules on an + # upgrade, which would turn the gate red without a code change + "ruff==0.16.2" ] [tool.hatch.build.targets.sdist] @@ -105,6 +108,41 @@ src = ["src", "plugin_cmds", "utils"] target-version = "py311" output-format = "grouped" +[tool.ruff.format] +# Files the formatter has never run over. Take one off the list by +# running `ruff format` on it in a commit of its own. +exclude = [ + "docs/source/conf.py", + "plugin_cmds/cmd_decrypt.py", + "plugin_cmds/cmd_get-annotations.py", + "plugin_cmds/cmd_goodreads-transform.py", + "plugin_cmds/cmd_image-urls.py", + "plugin_cmds/cmd_listening-stats.py", + "plugin_cmds/convert_oa_cred.py", + "plugin_cmds/README.md", + "pyi_entrypoint.py", + "README.md", + "src/audible_cli/cli.py", + "src/audible_cli/cmds/cmd_activation_bytes.py", + "src/audible_cli/cmds/cmd_api.py", + "src/audible_cli/cmds/cmd_download.py", + "src/audible_cli/cmds/cmd_library.py", + "src/audible_cli/cmds/cmd_manage.py", + "src/audible_cli/cmds/cmd_quickstart.py", + "src/audible_cli/cmds/cmd_wishlist.py", + "src/audible_cli/cmds/__init__.py", + "src/audible_cli/config.py", + "src/audible_cli/constants.py", + "src/audible_cli/decorators.py", + "src/audible_cli/downloader.py", + "src/audible_cli/_logging.py", + "src/audible_cli/__main__.py", + "src/audible_cli/models.py", + "src/audible_cli/plugins.py", + "src/audible_cli/utils.py", + "utils/update_chapter_titles.py", +] + [tool.ruff.lint] ignore = [ "D10", # temporarily ignore missing docstrings @@ -150,5 +188,41 @@ lines-after-imports = 2 [tool.ruff.lint.per-file-ignores] "tests/*" = ["S101"] +# Baseline: findings that predate the Ruff gate. Each entry lists the rules a +# file already breaks. A new file, and a rule not listed for a file, both +# still fail; another violation of a rule already listed for that same file +# does not, because per-file-ignores keys on file and rule rather than on +# individual findings. Shrink the list by fixing a rule in a file and +# dropping its code here. +"docs/source/conf.py" = ["A001", "I001"] +"plugin_cmds/cmd_decrypt.py" = ["C401", "C417", "PLR0917", "PLW0108", "Q000", "RUF005", "RUF100", "UP006", "UP007", "UP045", "W291", "W293"] +"plugin_cmds/cmd_get-annotations.py" = ["F541", "I001"] +"plugin_cmds/cmd_goodreads-transform.py" = ["D202", "D415", "G004", "I001", "W293"] +"plugin_cmds/cmd_image-urls.py" = ["D415", "I001"] +"plugin_cmds/cmd_listening-stats.py" = ["C416", "D403", "D415", "I001", "Q000", "UP034"] +"plugin_cmds/convert_oa_cred.py" = ["A002", "C408", "D205", "D209", "D415", "I001", "Q000", "W291", "W293"] +"pyi_entrypoint.py" = ["Q000"] +"src/audible_cli/__init__.py" = ["RUF022", "UP009"] +"src/audible_cli/__main__.py" = ["I001"] +"src/audible_cli/_logging.py" = ["B028", "C408", "D205", "D209", "G004", "UP007", "UP045"] +"src/audible_cli/cli.py" = ["C408", "D415", "I001"] +"src/audible_cli/cmds/__init__.py" = ["D205", "D212", "D416", "I001"] +"src/audible_cli/cmds/cmd_activation_bytes.py" = ["I001"] +"src/audible_cli/cmds/cmd_api.py" = ["D415", "G004", "W291"] +"src/audible_cli/cmds/cmd_download.py" = ["ASYNC240", "C901", "D403", "D415", "G004", "I001", "PLR0917", "PLR5501", "PLW0603", "PLW2901", "RUF010", "S101", "UP015"] +"src/audible_cli/cmds/cmd_library.py" = ["D403", "D415", "I001"] +"src/audible_cli/cmds/cmd_manage.py" = ["D403", "D415", "PLR0917"] +"src/audible_cli/cmds/cmd_quickstart.py" = ["D415", "I001", "W291"] +"src/audible_cli/cmds/cmd_wishlist.py" = ["D403", "D415", "G004", "I001", "Q000"] +"src/audible_cli/config.py" = ["B904", "D202", "D415", "D416", "D417", "G004", "I001", "UP006", "UP007", "UP035", "UP045", "W291", "W293"] +"src/audible_cli/constants.py" = ["UP006", "UP035"] +"src/audible_cli/decorators.py" = ["B904", "D205", "D415", "F541", "G004", "I001", "RUF001", "UP035", "W293"] +"src/audible_cli/downloader.py" = ["G004", "PGH004", "PLR0917", "RUF100", "UP006", "UP007", "UP015", "UP035", "UP045", "W293"] +"src/audible_cli/exceptions.py" = ["D415", "UP032"] +"src/audible_cli/models.py" = ["B904", "D202", "D205", "D415", "F541", "G004", "I001", "S101", "UP006", "UP007", "UP035", "UP045", "W291", "W293"] +"src/audible_cli/plugins.py" = ["D200", "D201", "D202", "D205", "D212", "D415", "D416", "PGH004", "Q000", "RUF010", "RUF021", "RUF100", "UP007", "UP025", "UP031"] +"src/audible_cli/utils.py" = ["D202", "G004", "I001", "PGH004", "PLR0917", "UP006", "UP007", "UP035", "UP045"] +"utils/update_chapter_titles.py" = ["C408", "D205", "D212", "D415", "Q000", "S101", "W293"] + [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/tests/test_download_cli.py b/tests/test_download_cli.py index ecc12626..4ac03c36 100644 --- a/tests/test_download_cli.py +++ b/tests/test_download_cli.py @@ -67,11 +67,15 @@ def download(session, tmp_path, *extra): return CliRunner().invoke( cmd_download.cli, [ - "--asin", "ASIN0001", + "--asin", + "ASIN0001", "--cover", - "--output-dir", str(tmp_path), - "--filename-mode", "ascii", - "--chapter-type", "flat", + "--output-dir", + str(tmp_path), + "--filename-mode", + "ascii", + "--chapter-type", + "flat", *extra, ], obj=session, diff --git a/tests/test_download_queue.py b/tests/test_download_queue.py index d6bd6740..e932ff4c 100644 --- a/tests/test_download_queue.py +++ b/tests/test_download_queue.py @@ -45,9 +45,7 @@ async def main(): for i, job in enumerate(jobs): cmd_download.QUEUE.put_nowait((job, {"n": i})) - consumers = [ - asyncio.create_task(consume(run)) for _ in range(sim_jobs) - ] + consumers = [asyncio.create_task(consume(run)) for _ in range(sim_jobs)] try: await asyncio.wait_for(cmd_download.QUEUE.join(), timeout=timeout) finally: diff --git a/tests/test_models_dates.py b/tests/test_models_dates.py index 4cb10f9b..79ea747b 100644 --- a/tests/test_models_dates.py +++ b/tests/test_models_dates.py @@ -86,10 +86,14 @@ def test_items_without_any_date_are_kept_not_crashed_on(fields): def test_date_added_branch_still_filters(): items = [ library_item( - "KEEP", purchase_date=None, library_status={"date_added": "2020-05-05T10:00:00Z"} + "KEEP", + purchase_date=None, + library_status={"date_added": "2020-05-05T10:00:00Z"}, ), library_item( - "DROP", purchase_date=None, library_status={"date_added": "2001-01-01T10:00:00Z"} + "DROP", + purchase_date=None, + library_status={"date_added": "2001-01-01T10:00:00Z"}, ), ] diff --git a/uv.lock b/uv.lock index 68ad335d..1de5902f 100644 --- a/uv.lock +++ b/uv.lock @@ -82,6 +82,7 @@ cryptography = [ dev = [ { name = "pyinstaller" }, { name = "pytest" }, + { name = "ruff" }, ] [package.metadata] @@ -105,6 +106,7 @@ provides-extras = ["cryptography"] dev = [ { name = "pyinstaller" }, { name = "pytest", specifier = ">=8.4.2" }, + { name = "ruff", specifier = "==0.16.2" }, ] [[package]] @@ -578,6 +580,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, ] +[[package]] +name = "ruff" +version = "0.16.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/e1/4508a569211b35599016e84ba65c1a992b7a4004b4b6c4bea02a851cba1b/ruff-0.16.2.tar.gz", hash = "sha256:c3d7828d12e8927a6fc65fe38e2c2541b9e762d360a1786d752cb1b8883b3c9c", size = 4885811, upload-time = "2026-08-07T13:31:01.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/57/db19951540f98859c956b50bdb4d31089b4d91e9f15e2968e7d5193806d5/ruff-0.16.2-py3-none-linux_armv6l.whl", hash = "sha256:3c8de4cf2181f01d57946d87d777aa52916976fc09942aed89938fab5e013318", size = 10847925, upload-time = "2026-08-07T13:30:14.468Z" }, + { url = "https://files.pythonhosted.org/packages/13/5a/995fe85a8470d3e391ac0f7fa8054bb454eaf33ee138196d6172ed1079c0/ruff-0.16.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a48cc05c6fbc811ca81b5d7ba95375affea6582d1b8024e455e41afbbf55344", size = 11072662, upload-time = "2026-08-07T13:30:18.143Z" }, + { url = "https://files.pythonhosted.org/packages/32/53/370d767c61c71a971a4ace36703a7ecd8c393956349a7325d7fab2b56827/ruff-0.16.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a2c0d14fcbb26c91f0f867a6dc9bd71bbc30b1b6151829c884f23faeab2e5700", size = 10566771, upload-time = "2026-08-07T13:30:20.899Z" }, + { url = "https://files.pythonhosted.org/packages/85/d6/9d96948caf5a632be62d62202d5ec914d6856f204fd79eb036e5915e79ea/ruff-0.16.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:335c621622c4650330be50842561c6586ac6971bb8ab5407fe34dcc9efb16bbe", size = 10975825, upload-time = "2026-08-07T13:30:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/92/ea87129b3414acb0b5770563779c51804d37ac67675c7ba35447ddb14773/ruff-0.16.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e66910f2c37cc753f9ef6580c914a621b80c4fa3549d3e3521e29d0f5bfc3f", size = 10649437, upload-time = "2026-08-07T13:30:26.097Z" }, + { url = "https://files.pythonhosted.org/packages/ac/43/f8f291dcd4af5bb7872b74fdfa41a7cd7c856ca1d4069670971cf1b9f5cb/ruff-0.16.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7e36fbfba65510548156902bcf1350a979a958ce0347ce0f90d73894036b39f", size = 11446761, upload-time = "2026-08-07T13:30:28.752Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/ef991fb2fcf516ab71f0808adcdd8da5e18c8cde447f4ceaf5f47a5132a5/ruff-0.16.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0eab35f80df8f134aae5d1630e751901321d317cc8e50dc39e36fa3ed34cd12", size = 12336364, upload-time = "2026-08-07T13:30:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/f3/24/f615e74f307e6ca0e56a482872477b856c70d530aa356abfb6dfe5ca8a80/ruff-0.16.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ea8c0594feb894e89c8c61ab9c103d38b0ea72dfde6c594107147ca31b1140", size = 11630720, upload-time = "2026-08-07T13:30:34.426Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d3/8ef50149e8412a77f7ab409efdef0e2b23803707a3863da4fc64cb23d459/ruff-0.16.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab3d62dde0b19facdd632008cc4827fc28ada7736c6bd35ab6f1050f0bfed53f", size = 11466130, upload-time = "2026-08-07T13:30:36.958Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a7/a19334985c4dea8c381981fa252cd854c7ee52dc4b1686dc16f4a911c702/ruff-0.16.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e43e1f5b8388da9eca1b9e88328d47a5cec794633ccf6f7484ac2dd15eee92c0", size = 11523634, upload-time = "2026-08-07T13:30:39.822Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/96d192b0e742412ceda08c0a50f9669b253dde9fd6a60ea1a10c9fa79a63/ruff-0.16.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c24788a980581e1d7ea3a0cbe4344c4fbeb0a6a9b1f4713aa46bb104f8294690", size = 10949807, upload-time = "2026-08-07T13:30:42.745Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/e26599ceca11e79ee255c7df515995561edf87e9ca1893284e44d98f5a86/ruff-0.16.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:81806b08329130005dd4a8a8394a0c9da8c6f4cafb16ba438d2a2ee6a18bedf1", size = 10646891, upload-time = "2026-08-07T13:30:45.522Z" }, + { url = "https://files.pythonhosted.org/packages/68/01/800c4b1f97bc8d7c6029e06b1f20473a3cf1e13c4933d8f3342add83fc55/ruff-0.16.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce4e02bad779bef557f541a1b31f20d6abeae1cc05ed1b1ac019d4ffd1044c8", size = 11162063, upload-time = "2026-08-07T13:30:48.131Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d0/1477ea50fc5a0d4b0b71d1d63d50770bdd794d90b43e37a7618e63ec9894/ruff-0.16.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0422abdf70070255fc4073ce9dfc814cc03db577013761ddd09bc1e4a9a4fbd", size = 11556038, upload-time = "2026-08-07T13:30:50.686Z" }, + { url = "https://files.pythonhosted.org/packages/b8/76/a7776f32048d991e16d4fa8ff91790b877342d3596cc3ed04acdbf1aaedc/ruff-0.16.2-py3-none-win32.whl", hash = "sha256:bf3a63d78fb39f4bf5ac8ae52051c5520505301abe19ba4e204c453b3f09bb0b", size = 10872850, upload-time = "2026-08-07T13:30:53.471Z" }, + { url = "https://files.pythonhosted.org/packages/00/0d/929c800d920e61397d82a01b60bffc68da3052c17d31de59efaad2e4ed75/ruff-0.16.2-py3-none-win_amd64.whl", hash = "sha256:bcabe2f6d0fc7819f1431793005af4e4de7371927d037345bf941252b195b9fa", size = 12023338, upload-time = "2026-08-07T13:30:56.193Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6c/93e26c22c5f78ff87363e07da49c84955affbeb1098bd1936bf3b3f293bf/ruff-0.16.2-py3-none-win_arm64.whl", hash = "sha256:d614e95cedf38a2053fd351c55b103ba30d017d61688fdbfd40ee0412852a99f", size = 11374065, upload-time = "2026-08-07T13:30:58.775Z" }, +] + [[package]] name = "setuptools" version = "83.0.0"