Contributing ============ .. contents:: :depth: 3 Thank you! ---------- First off, thank you for considering contributing to beets! It’s people like you that make beets continue to succeed. These guidelines describe how you can help most effectively. By following these guidelines, you can make life easier for the development team as it indicates you respect the maintainers’ time; in return, the maintainers will reciprocate by helping to address your issue, review changes, and finalize pull requests. Types of Contributions ---------------------- We love to get contributions from our community—you! There are many ways to contribute, whether you’re a programmer or not. The first thing to do, regardless of how you'd like to contribute to the project, is to check out our :doc:`Code of Conduct ` and to keep that in mind while interacting with other contributors and users. Non-Programming ~~~~~~~~~~~~~~~ - Promote beets! Help get the word out by telling your friends, writing a blog post, or discussing it on a forum you frequent. - Improve the documentation_. It’s incredibly easy to contribute here: just find a page you want to modify and hit the “Edit on GitHub” button in the upper-right. You can automatically send us a pull request for your changes. - GUI design. For the time being, beets is a command-line-only affair. But that’s mostly because we don’t have any great ideas for what a good GUI should look like. If you have those great ideas, please get in touch. - Benchmarks. We’d like to have a consistent way of measuring speed improvements in beets’ tagger and other functionality as well as a way of comparing beets’ performance to other tools. You can help by compiling a library of freely-licensed music files (preferably with incorrect metadata) for testing and measurement. - Think you have a nice config or cool use-case for beets? We’d love to hear about it! Submit a post to our `discussion board `__ under the “Show and Tell” category for a chance to get featured in `the docs `__. - Consider helping out fellow users by `responding to support requests `__ . Programming ~~~~~~~~~~~ - As a programmer (even if you’re just a beginner!), you have a ton of opportunities to get your feet wet with beets. - For developing plugins, or hacking away at beets, there’s some good information in the `“For Developers” section of the docs `__. .. _development-tools: Development Tools +++++++++++++++++ In order to develop beets, you will need a few tools installed: - uv_ for packaging, virtual environment and dependency management - poethepoet_ to run tasks, such as linting, formatting, testing Follow the `uv installation instructions `__ to install uv_. poethepoet_ is a stand-alone CLI tool. uv can install such tools in isolated environments so their dependencies don't interfere with your system. Then install poethepoet_: :: $ uv tool install "poethepoet>=0.26,<0.47.0" .. _getting-the-source: Getting the Source ++++++++++++++++++ The easiest way to get started with the latest beets source is to clone the repository and install ``beets`` in a local virtual environment using uv_. This can be done with: .. code-block:: bash $ git clone https://github.com/beetbox/beets.git $ cd beets $ uv sync This will install ``beets`` and all development dependencies into a ``.venv`` virtual environment. See ``uv sync --help`` for options, including installing ``extra`` dependencies for plugins (e.g. ``uv sync --extra fetchart``). To run commands within this virtual environment, either activate it: :: $ source .venv/bin/activate $ pytest Or prefix commands with ``uv run``: :: $ uv run pytest Additionally, poethepoet_ task runner assists us with the most common operations. Formatting, linting, testing are defined as ``poe`` tasks in pyproject.toml_. Run: :: $ poe to see all available tasks. They can be used like this, for example .. code-block:: sh $ poe lint # check code style $ poe format # fix formatting issues $ poe test # run tests # ... fix failing tests $ poe test --lf # re-run failing tests (note the additional pytest option) $ poe check-types --pretty # check types with an extra option for mypy Code Contribution Ideas +++++++++++++++++++++++ - We maintain a set of `issues marked as “good first issue” `__. These are issues that would serve as a good introduction to the codebase. Claim one and start exploring! - Like testing? Our `test coverage `__ is somewhat low. You can help out by finding low-coverage modules or checking out other `testing-related issues `__. - There are several ways to improve the tests in general (see :ref:`testing` and some places to think about performance optimization (see `Optimization `__). - Not all of our code is up to our coding conventions. In particular, the `library API documentation `__ are currently quite sparse. You can help by adding to the docstrings in the code and to the documentation pages themselves. beets follows `PEP-257 `__ for docstrings and in some places, we also sometimes use `ReST autodoc syntax for Sphinx `__ to, for example, refer to a class name. Your First Contribution ----------------------- If this is your first time contributing to an open source project, welcome! If you are confused at all about how to contribute or what to contribute, take a look at `this great tutorial `__, or stop by our `discussion board`_ if you have any questions. We maintain a list of issues we reserved for those new to open source labeled `first timers only`_. Since the goal of these issues is to get users comfortable with contributing to an open source project, please do not hesitate to ask any questions. .. _first timers only: https://github.com/beetbox/beets/issues?q=is%3Aopen+is%3Aissue+label%3A%22first+timers+only%22 How to Submit Your Work ----------------------- Do you have a great bug fix, new feature, or documentation expansion you’d like to contribute? Follow these steps to create a GitHub pull request and your code will ship in no time. 1. Fork the beets repository and clone it (see above) to create a workspace. 2. Install pre-commit, following the instructions `here `_. 3. Make your changes. 4. Add tests. If you’ve fixed a bug, write a test to ensure that you’ve actually fixed it. If there’s a new feature or plugin, please contribute tests that show that your code does what it says. 5. Add documentation. If you’ve added a new command flag, for example, find the appropriate page under ``docs/`` where it needs to be listed. 6. Add a changelog entry to ``docs/changelog.rst`` near the top of the document. 7. Run the tests and style checker, see :ref:`testing`. 8. Push to your fork and open a pull request! We’ll be in touch shortly. 9. If you add commits to a pull request, please add a comment or re-request a review after you push them since GitHub doesn’t automatically notify us when commits are added. Remember, code contributions have four parts: the code, the tests, the documentation, and the changelog entry. Thank you for contributing! .. admonition:: Ownership If you are the owner of a plugin, please consider reviewing pull requests that affect your plugin. If you are not the owner of a plugin, please consider becoming one! You can do so by adding an entry to ``.github/CODEOWNERS``. This way, you will automatically receive a review request for pull requests that adjust the code that you own. If you have any questions, please ask on our `discussion board`_. The Code -------- The documentation has a section on the `library API `__ that serves as an introduction to beets’ design. Coding Conventions ------------------ General ~~~~~~~ There are a few coding conventions we use in beets: - Whenever you access the library database, do so through the provided Library methods or via a Transaction object. Never call ``lib.conn.*`` directly. For example, do this: .. code-block:: python with g.lib.transaction() as tx: rows = tx.query("SELECT DISTINCT {field} FROM {model._table} ORDER BY {sort_field}") To fetch Item objects from the database, use lib.items(…) and supply a query as an argument. Resist the urge to write raw SQL for your query. If you must use lower-level queries into the database, do this, for example: .. code-block:: python with lib.transaction() as tx: rows = tx.query("SELECT path FROM items WHERE album_id = ?", (album_id,)) Transaction objects help control concurrent access to the database and assist in debugging conflicting accesses. - f-strings should be used instead of the ``%`` operator and ``str.format()`` calls. - Never ``print`` informational messages; use the `logging `__ module instead. In particular, we have our own logging shim, so you’ll see ``from beets import logging`` in most files. - The loggers use `str.format `__-style logging instead of ``%``-style, so you can type ``log.debug("{}", obj)`` to do your formatting. - Exception handlers must use ``except A as B:`` instead of ``except A, B:``. Style ~~~~~ We use `ruff `__ to format and lint the codebase. Run ``poe check-format`` and ``poe lint`` to check your code for style and linting errors. Running ``poe format`` will automatically format your code according to the specifications required by the project. Similarly, run ``poe format-docs`` and ``poe lint-docs`` to ensure consistent documentation formatting and check for any issues. Blame Ignore Revisions ~~~~~~~~~~~~~~~~~~~~~~ The ``.git-blame-ignore-revs`` file lists mechanical commits that make history harder to inspect, such as repository-wide formatting changes, large renames, and other behavior-preserving rewrites. Configure Git to use this file locally: .. code-block:: sh $ git config blame.ignoreRevsFile .git-blame-ignore-revs When you make a mechanical-only change that should not distract future ``git blame`` output, keep it in its own commit. Then, add its **commit summary** with the commit hash to ``.git-blame-ignore-revs`` under the appropriate year. Do not add commits that mix mechanical edits with behavior changes; those should remain visible in blame output. Editor Settings ~~~~~~~~~~~~~~~ Personally, I work on beets with vim_. Here are some ``.vimrc`` lines that might help with PEP 8-compliant Python coding: :: filetype indent on autocmd FileType python setlocal shiftwidth=4 tabstop=4 softtabstop=4 expandtab shiftround autoindent Consider installing `this alternative Python indentation plugin `__. I also like `neomake `__ with its flake8 checker. .. _testing: Testing ------- Running the Tests ~~~~~~~~~~~~~~~~~ Use ``poe`` to run tests: :: $ poe test [pytest options] Coverage ++++++++ The ``test`` command does not include coverage as it slows down testing. In order to measure it, use the ``test-with-coverage`` task $ poe test-with-coverage [pytest options] You are welcome to explore coverage by opening the HTML report in ``.reports/html/index.html``. Note that for each covered line the report shows **which tests cover it** (expand the list on the right-hand side of the affected line). You can find project coverage status on Codecov_. Red Flags +++++++++ The pytest-random_ plugin makes it easy to randomize the order of tests. ``poe test --random`` will occasionally turn up failing tests that reveal ordering dependencies—which are bad news! Test Dependencies +++++++++++++++++ The tests have a few more dependencies than beets itself. (The additional dependencies consist of testing utilities and dependencies of non-default plugins exercised by the test suite.) The dependencies are listed under the ``test`` group in the ``dependency-groups`` section of pyproject.toml_. Writing Tests ~~~~~~~~~~~~~ Writing tests is done by adding or modifying files in folder test_. Take a look at test-query_ to get a basic view on how tests are written. Since we are currently migrating the tests from unittest_ to pytest_, new tests should be written using pytest_. Contributions migrating existing tests are welcome! External API requests under test should be mocked with requests-mock_, However, we still want to know whether external APIs are up and that they return expected responses, therefore we test them weekly with our `integration test`_ suite. In order to add such a test, mark your test with the ``integration_test`` marker .. code-block:: python @pytest.mark.integration_test def test_external_api_call(): ... This way, the test will be run only in the integration test suite. beets also defines custom pytest markers in ``test/conftest.py``: - ``integration_test``: runs only when ``INTEGRATION_TEST=true`` is set. - ``on_lyrics_update``: runs only when ``LYRICS_UPDATED=true`` is set. - ``requires_import("module", force_ci=True)``: runs the test only when the module is importable. With the default ``force_ci=True``, this import check is bypassed on GitHub Actions for ``beetbox/beets`` so CI still runs the test. Set ``force_ci=False`` to allow CI to skip when the module is missing. .. code-block:: python @pytest.mark.integration_test def test_external_api_call(): ... @pytest.mark.on_lyrics_update def test_real_lyrics_backend(): ... @pytest.mark.requires_import("langdetect") def test_language_detection(): ... @pytest.mark.requires_import("librosa", force_ci=False) def test_autobpm_command(): ... Notes on AI Usage ----------------- We are not opposed to AI-generated contributions, but communication should be handled by a real person. We will likely have questions about your PR, and we need you to understand the proposed changes in order to discuss with us what the implications are. > Currently we value human oversight and accountability, AI as a tool, not a contributor. .. _codecov: https://app.codecov.io/github/beetbox/beets .. _discussion board: https://github.com/beetbox/beets/discussions .. _documentation: https://beets.readthedocs.io/en/stable/ .. _integration test: https://github.com/beetbox/beets/actions?query=workflow%3A%22integration+tests%22 .. _poethepoet: https://poethepoet.natn.io/index.html .. _pyproject.toml: https://github.com/beetbox/beets/blob/master/pyproject.toml .. _pytest: https://docs.pytest.org/en/stable/ .. _pytest-random: https://github.com/klrmn/pytest-random .. _requests-mock: https://requests-mock.readthedocs.io/en/latest/response.html .. _test: https://github.com/beetbox/beets/tree/master/test .. _test-query: https://github.com/beetbox/beets/blob/master/test/test_query.py .. _unittest: https://docs.python.org/3/library/unittest.html .. _uv: https://docs.astral.sh/uv/ .. _vim: https://www.vim.org/