Skip to content

Resolve optional Path correctly#197

Merged
snejus merged 2 commits into
mainfrom
resolve-optional-path-correctly
Jul 19, 2026
Merged

Resolve optional Path correctly#197
snejus merged 2 commits into
mainfrom
resolve-optional-path-correctly

Conversation

@snejus

@snejus snejus commented Jul 16, 2026

Copy link
Copy Markdown
Member

I was adding types to beetsplug/fetchart.py and found that this

@cached_property
def fallback(self) -> Path | None:
    return self.config["fallback"].get(
        confuse.Optional(confuse.templates.Path())
    )

fails with

beetsplug/fetchart.py:1370: error: Incompatible return value type (got "PurePath | None", expected "Path | None")  [return-value]

@snejus
snejus requested review from Copilot, semohr and wisp3rwind July 16, 2026 14:46
@github-actions

Copy link
Copy Markdown

Thank you for the PR! The changelog has not been updated, so here is a friendly reminder to check if you need to add an entry.

@jackwilsdon

jackwilsdon commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Can we not keep this as a PurePath?

Pure paths are useful in some special cases; for example:

  1. If you want to manipulate Windows paths on a Unix machine (or vice versa). You cannot instantiate a WindowsPath when running on Unix, but you can instantiate PureWindowsPath.

  2. You want to make sure that your code only manipulates paths without actually accessing the OS. In this case, instantiating one of the pure classes may be useful since those simply don’t have any OS-accessing operations.

https://docs.python.org/3/library/pathlib.html

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes the type behavior of confuse.templates.Path when wrapped in confuse.Optional(...), so type-checkers infer pathlib.Path | None (instead of pathlib.PurePath | None)—addressing the reported mypy return-type mismatch.

Changes:

  • Update confuse.templates.Path to be typed as Filename[pathlib.Path] (not PurePath).
  • Ensure as_template() converts pathlib.PurePath inputs to pathlib.Path before constructing a Path template.
  • Add a typing assertion test to validate Optional(Path()) returns pathlib.Path | None.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
test/test_valid.py Adds a typing assertion for Optional(Path()), but currently imports typing_extensions in a way that breaks Python 3.13+ test runs.
confuse/templates.py Adjusts Path’s generic type to pathlib.Path and updates as_template() to pass a concrete pathlib.Path default.

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

Comment thread test/test_valid.py
@snejus

snejus commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

Can we not keep this as a PurePath?

Looking at the following graph
image

It seems like both PosixPath and WindowsPath inherit from Path. PurePath is too 'stripped down' and generic to be interchangeable with Path. Did a quick lookup on Perplexity: https://www.perplexity.ai/search/c861f2e7-5cb6-4fc0-84b1-833677217bed

@jackwilsdon

jackwilsdon commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Do we need to use Path in beets or could it be using PurePath? You can always turn a PurePath into a Path by doing Path(pure_path).

It feels more natural to me to have confuse returning an agnostic PurePath (unless I am misunderstanding how it works).

@snejus

snejus commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

Quoting from the URL I shared:

Core distinction

  • PurePath only implements operations that can be done by looking at the path string: joining, normalising, .parent, .name, .suffix, etc. It never performs system calls or inspects the real filesystem.
  • Path inherits from PurePath and adds methods that actually hit the OS: .exists(), .is_file(), .is_dir(), .iterdir(), .open(), .unlink(), .mkdir(), .chmod(), .stat(), .cwd(), .home(), etc.

So any time code expects a “real” path that you can read from, write to, or query, a PurePath is not a drop‑in replacement: many of those methods simply don’t exist on PurePath and conceptually shouldn’t, because a pure path is allowed to represent paths that don’t correspond to any real filesystem at all (e.g. Windows paths on a Unix machine).

I think we should assume that users expect to work with 'real' paths?

@jackwilsdon

Copy link
Copy Markdown
Contributor

Yeah fair enough.

Comment thread confuse/templates.py Outdated
@snejus
snejus force-pushed the resolve-optional-path-correctly branch from 5d148ac to f2e16cb Compare July 16, 2026 15:49
@snejus

snejus commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

Added another sneaky commit, to make sure that confuse.Path points to confuse.templates.Path instead of pathlib.Path. We should make all imports explicit in the future.

@snejus
snejus force-pushed the resolve-optional-path-correctly branch from 2923e8f to 6c6bd01 Compare July 18, 2026 17:48
snejus added 2 commits July 18, 2026 18:50
I was adding types to beetsplug/fetchart.py and found that this

  @cached_property
  def fallback(self) -> Path | None:
      return self.config["fallback"].get(
          confuse.Optional(confuse.templates.Path())
      )

fails with

  beetsplug/fetchart.py:1370: error: Incompatible return value type (got "PurePath | None", expected "Path | None")  [return-value]
@snejus
snejus force-pushed the resolve-optional-path-correctly branch from 6c6bd01 to e24ce6e Compare July 18, 2026 17:50
@snejus
snejus requested a review from semohr July 18, 2026 18:00
@snejus
snejus merged commit d4b1500 into main Jul 19, 2026
15 checks passed
@snejus
snejus deleted the resolve-optional-path-correctly branch July 19, 2026 14:52
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