Skip to content

Conversation

@snejus
Copy link
Member

@snejus snejus commented Nov 5, 2025

Drop support for Python 3.9 and pyupgrade the codebase.

Dependency upgrades:

image

Copilot AI review requested due to automatic review settings November 5, 2025 11:50
@snejus snejus requested review from a team and semohr as code owners November 5, 2025 11:50
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `beetsplug/bpd/__init__.py:285` </location>
<code_context>
             self.ctrl_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             self.ctrl_sock.connect((self.ctrl_host, self.ctrl_port))
-        self.ctrl_sock.sendall((f"{message}\n").encode("utf-8"))
+        self.ctrl_sock.sendall((f"{message}\n").encode())

     def _send_event(self, event):
</code_context>

<issue_to_address>
**issue (bug_risk):** Encoding without specifying 'utf-8' may cause issues with non-ASCII characters.

Explicitly setting 'utf-8' avoids platform-dependent encoding issues and ensures correct handling of non-ASCII characters.
</issue_to_address>

### Comment 2
<location> `beetsplug/fetchart.py:685` </location>
<code_context>
         if not (album.albumartist and album.album):
             return
-        search_string = f"{album.albumartist},{album.album}".encode("utf-8")
+        search_string = f"{album.albumartist},{album.album}".encode()

         try:
</code_context>

<issue_to_address>
**issue (bug_risk):** Not specifying encoding in .encode() may lead to inconsistent results.

Explicitly setting the encoding to 'utf-8' ensures consistent behavior across different environments.
</issue_to_address>

### Comment 3
<location> `beets/autotag/__init__.py:119` </location>
<code_context>
def _apply_metadata(
    info: AlbumInfo | TrackInfo,
    db_obj: Album | Item,
    nullable_fields: Sequence[str] = [],
):
    """Set the db_obj's metadata to match the info."""
    special_fields = SPECIAL_FIELDS[
        "album" if isinstance(info, AlbumInfo) else "track"
    ]

    for field, value in info.items():
        # We only overwrite fields that are not already hardcoded.
        if field in special_fields:
            continue

        # Don't overwrite fields with empty values unless the
        # field is explicitly allowed to be overwritten.
        if value is None and field not in nullable_fields:
            continue

        db_obj[field] = value

</code_context>

<issue_to_address>
**issue (code-quality):** Replace mutable default arguments with None ([`default-mutable-arg`](https://docs.sourcery.ai/Reference/Default-Rules/suggestions/default-mutable-arg/))
</issue_to_address>

### Comment 4
<location> `beets/test/_common.py:100` </location>
<code_context>
def item(lib=None, **kwargs):
    defaults = dict(
        title="the title",
        artist="the artist",
        albumartist="the album artist",
        album="the album",
        genre="the genre",
        lyricist="the lyricist",
        composer="the composer",
        arranger="the arranger",
        grouping="the grouping",
        work="the work title",
        mb_workid="the work musicbrainz id",
        work_disambig="the work disambiguation",
        year=1,
        month=2,
        day=3,
        track=4,
        tracktotal=5,
        disc=6,
        disctotal=7,
        lyrics="the lyrics",
        comments="the comments",
        bpm=8,
        comp=True,
        length=60.0,
        bitrate=128000,
        format="FLAC",
        mb_trackid="someID-1",
        mb_albumid="someID-2",
        mb_artistid="someID-3",
        mb_albumartistid="someID-4",
        mb_releasetrackid="someID-5",
        album_id=None,
        mtime=12345,
    )
    i = Item(**{**defaults, **kwargs})
    if lib:
        lib.add(i)
    return i

</code_context>

<issue_to_address>
**suggestion (code-quality):** Applies the dictionary union operator where applicable ([`use-dictionary-union`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-dictionary-union/))

```suggestion
    i = Item(**defaults | kwargs)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request bumps the minimum Python version from 3.9 to 3.10, and updates the codebase to use Python 3.10+ syntax and features.

  • Minimum Python version updated from 3.9 to 3.10
  • Type annotations modernized to use PEP 604 union syntax (|) and built-in generics
  • Import cleanup to move collection types from typing to collections.abc

Reviewed Changes

Copilot reviewed 43 out of 44 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pyproject.toml Updated minimum Python version from 3.9 to 3.10
docs/guides/installation.rst Updated installation documentation to reflect Python 3.10 requirement
docs/changelog.rst Added changelog entry for Python 3.10 requirement
CONTRIBUTING.rst Updated development environment examples to use Python 3.10
.github/workflows/*.yaml Updated CI/CD workflows to use Python 3.10 as minimum version
.git-blame-ignore-revs Added commit hash for this refactoring
test/test_sort.py Updated mock import to use unittest.mock
test/plugins/*.py Modernized type hints using PEP 604 syntax
beetsplug/*.py Moved collection types to TYPE_CHECKING blocks and updated type hints
beets/*.py Moved collection types to TYPE_CHECKING blocks and updated type hints
beets/util/*.py Moved collection types and updated type hints
beets/plugins.py Removed Python 3.9 specific GenericAlias workaround

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@codecov
Copy link

codecov bot commented Nov 5, 2025

Codecov Report

❌ Patch coverage is 96.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 67.61%. Comparing base (29b9958) to head (07f6192).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
beetsplug/bpd/__init__.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #6144      +/-   ##
==========================================
+ Coverage   67.34%   67.61%   +0.27%     
==========================================
  Files         136      136              
  Lines       18492    18521      +29     
  Branches     3134     3129       -5     
==========================================
+ Hits        12453    12523      +70     
+ Misses       5370     5334      -36     
+ Partials      669      664       -5     
Files with missing lines Coverage Δ
beets/autotag/__init__.py 87.71% <100.00%> (ø)
beets/dbcore/db.py 94.52% <100.00%> (+0.11%) ⬆️
beets/importer/session.py 94.36% <100.00%> (ø)
beets/importer/stages.py 88.05% <100.00%> (+0.07%) ⬆️
beets/importer/tasks.py 90.70% <100.00%> (+0.01%) ⬆️
beets/logging.py 98.00% <100.00%> (+3.55%) ⬆️
beets/metadata_plugins.py 76.52% <100.00%> (ø)
beets/plugins.py 83.05% <ø> (-0.07%) ⬇️
beets/ui/__init__.py 82.34% <100.00%> (+0.02%) ⬆️
beets/util/__init__.py 79.02% <100.00%> (+0.08%) ⬆️
... and 17 more

... and 14 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@semohr semohr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly reasonable to me 👍

You upgraded quite alot of dependencies, we might want to mention this in the change-log too. I guess this was necessary for the 3.10 bump?

I mentioned it in a comment too, but it might be worth it to add some more ruff rules now. Especially some of the pyupgrade ones. There are many rules that could be useful for us, these ones are the primary issues addressed in the PR:
https://docs.astral.sh/ruff/rules/non-pep604-annotation-union/
https://docs.astral.sh/ruff/rules/non-pep585-annotation/
https://docs.astral.sh/ruff/rules/super-call-with-parameters/

@sakgoyal
Copy link

sakgoyal commented Nov 5, 2025

You should also squash these commits into a single commit instead of the current 5 commits. it keeps the git history cleaner.

git reset --soft HEAD~5 # or however many commits you want to squash
git add .
git commit -m "..."
git push --force

@semohr semohr added this to the 2.6.0 milestone Nov 5, 2025
@snejus
Copy link
Member Author

snejus commented Nov 8, 2025

You should also squash these commits into a single commit instead of the current 5 commits. it keeps the git history cleaner.

git reset --soft HEAD~5 # or however many commits you want to squash
git add .
git commit -m "..."
git push --force

Each commit is tidy and intentional:

* 9 minutes ago             Šarūnas Nejus 9682bfd00 Enable all pyupgrade lint rules (HEAD -> drop-python-39)
* 3 days ago                Šarūnas Nejus f324bb0ce Ignore pyupgrade blame
# this commit is added to .git-blame-ignore-revs to keep original authors in the blame
* 3 days ago                Šarūnas Nejus 0f6250b52 pyupgrade Python 3.10
* 3 days ago                Šarūnas Nejus b9fde9a47 Update python version references
# shows which dependencies were upgraded specifically due to Python version upgrade
* 3 days ago                Šarūnas Nejus a7830beba Update python requirement and dependencies
# gives meaning to the next commit
* 3 days ago                Šarūnas Nejus d64efbb6c Upgrade deps before upgrade

@snejus
Copy link
Member Author

snejus commented Nov 8, 2025

You upgraded quite alot of dependencies, we might want to mention this in the change-log too. I guess this was necessary for the 3.10 bump?

Yes, and this may enable support for Python 3.14, actually. Let me check. I'm not sure how dependency version upgrades are relevant to users or maintainers - we update them often without a mention in the changelog.

@semohr
Copy link
Contributor

semohr commented Nov 8, 2025

Not necessarily for users but for people using beets as a library it might be of interest. I agree tho, no need to add them to the changelog.

Just noticed we have scipy twice as dependency, you know how this happend?

@snejus
Copy link
Member Author

snejus commented Nov 8, 2025

Just noticed we have scipy twice as dependency, you know how this happend?

See pyproject.toml and the PR where I added support for Python 3.13 - it needed a higher version on Python 3.13 to fetch the wheel from PyPI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants