Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Path refactor #2959

Merged
merged 17 commits into from
Mar 11, 2024
Merged

Path refactor #2959

merged 17 commits into from
Mar 11, 2024

Conversation

etianen
Copy link
Contributor

@etianen etianen commented Feb 17, 2024

This PR implements the proposed refactor in #2944. As well as fixing the Python 3.13 problems, it adds some cool new things! 😎

The big change 💥

trio.Path now subclasses pathlib.PurePath!

This means all the sync method forwarding can be completely removed!

Since it now fits into the pathlib class hierarchy, it can be used interchangeably with other pure paths and methods that take pure paths. This also has the nice effect of making the wrapped async methods just work with regard to input types.

It's also now a non-virtual subclass of os.PathLike[str], which might help some static analysis tools...?

Two concrete subclasses PosixPath and WindowsPath also now exist, matching how pathlib does things. Instantiating a Path actually gets you the appropriate one for the platform, just like pathlib.

The medium change

Async method wrapping is now explicit, fixing Python 3.13. There are three high-level wrapper helpers that work for most cases (_wrap_method, _wrap_method_path and _wrap_method_path_iterable). For methods that don't fit into these, a low-level (_wraps_async) is used.

Other changes

Path is now slotted, to match pathlib. A minor speed boost, with luck!

Fixing Python 3.13

Unlike #2955, this fix actually works! Check it out:

================================================= test session starts ==================================================
platform darwin -- Python 3.13.0a3+, pytest-8.0.1, pluggy-1.4.0
rootdir: /Users/dave.hall/Workspace/trio
configfile: pyproject.toml
collected 33 items                                                                                                     

src/trio/_tests/test_path.py .................................                                                   [100%]

================================================== 33 passed in 0.05s ==================================================

My battle with pyright --verifytypes 😭

The pyright --verifytypes check does not like this syntax:

stat = _wrap_method(pathlib.Path.stat)

It claims this is an ambiguous type, asking me to annotate it with something that can't be expressed with the Python type system except for a per-method unweildy protocol!

The workaround would be to put in a dummy method and use a decorator syntax, which pyright thinks is absolutely okay despite inferring to exactly the same thing! It's also pretty disgusting to look at, and bloats the implementation with boilerplate.

@_wrap_method(pathlib.Path.stat)
def stat(self) -> Any:
    raise AssertionError("unreachable!")

I can't see any way to disable this check for just this file, or just these lines. Any suggestions?! 😭

Copy link

codecov bot commented Feb 17, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.64%. Comparing base (379d99b) to head (ba5b6c7).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2959      +/-   ##
==========================================
- Coverage   99.64%   99.64%   -0.01%     
==========================================
  Files         117      117              
  Lines       17643    17594      -49     
  Branches     3176     3171       -5     
==========================================
- Hits        17581    17532      -49     
  Misses         43       43              
  Partials       19       19              
Files Coverage Δ
src/trio/__init__.py 100.00% <100.00%> (ø)
src/trio/_path.py 100.00% <100.00%> (ø)
src/trio/_tests/test_exports.py 99.61% <100.00%> (+<0.01%) ⬆️
src/trio/_tests/test_path.py 100.00% <100.00%> (ø)


assert not hasattr(MockWrapper, "_private")


Copy link
Contributor Author

Choose a reason for hiding this comment

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

All of this can be removed, now we're not using metaclasses.

@@ -39,7 +39,7 @@ def sync_attrs(path: trio.Path) -> None:
assert_type(path.drive, str)
assert_type(path.root, str)
assert_type(path.anchor, str)
assert_type(path.parents[3], pathlib.Path)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I count this as a bugfix!

@etianen etianen force-pushed the dh/path-refactor branch 4 times, most recently from 5776eaa to 0dbda03 Compare February 17, 2024 16:45
@etianen etianen force-pushed the dh/path-refactor branch 6 times, most recently from cb177d5 to c12689a Compare February 17, 2024 17:32
Copy link
Member

@CoolCat467 CoolCat467 left a comment

Choose a reason for hiding this comment

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

Questions about a few minor things

src/trio/_path.py Outdated Show resolved Hide resolved
src/trio/_path.py Outdated Show resolved Hide resolved
@jakkdl
Copy link
Member

jakkdl commented Feb 18, 2024

The pyright --verifytypes check does not like this syntax:
stat = _wrap_method(pathlib.Path.stat)
It claims this is an ambiguous type, asking me to annotate it with something that can't be expressed with the Python type system except for a per-method unweildy protocol!
The workaround would be to put in a dummy method and use a decorator syntax, which pyright thinks is absolutely okay despite inferring to exactly the same thing! It's also pretty disgusting to look at, and bloats the implementation with boilerplate.

The error message is specifically about it being ambigious and could be inferred differently by other type checkers, so whether it's being inferred to the same thing by pyright is not really relevant. In most other cases we've resorted to doing the disgusting/bloated boilerplate to get around these errors, but I'll have a closer look later on whether I think it's worth it. We are after all running type checks on both mypy & pyright, so we can probably ignore it.

I can't see any way to disable this check for just this file, or just these lines. Any suggestions?! 😭

Maybe I should resuscitate #2910 and get the output-filtering script finished/merged.

@etianen
Copy link
Contributor Author

etianen commented Feb 18, 2024

The error message is specifically about it being ambiguous and could be inferred differently by other type checkers

I think I'm disagreeing here with pyright. I can write the same thing using a decorator syntax, which provides no additional typing information, and it's happy. It just seems to be complaining that I'm doing a variable assignment and not explicitly annotating the variable.

This is related to my point about the static visibility linter, really. This check seems to have a very uncompromising idea of what's ambiguous. The use of ParamSpec and Concatenate here has, AFAIK, absolutely only one possible meaning. That's not ambiguous! It's simply not explicit.

I'm a huge fan of static analysis tools in general, and favor very strict typing and linting. But to me, these checks feel overboard. I realize that this exactly the argument people make to me about strict mypy checks, so maybe it's a case of comfort levels? But at the point inheritance and strictly-typed metaprograming become forbidden, it feels too much to me.

I'm left in a bit of a quandry for this PR though. Hopefully you have a solution in mind with #2910.

@etianen
Copy link
Contributor Author

etianen commented Feb 18, 2024

Also, apologies if the tone of my messages comes over as overly critical! I'm a big fan of how welcoming this project is to new contributors, so this stems from a concern about placing too-high barriers in front of new contributions.

It's hard to express frustration without sounding... frustrated. 🥵

@jakkdl
Copy link
Member

jakkdl commented Feb 18, 2024

disagreeing with pyright seems very fair in this case, and given how complex the Path wrapping is already I'm open to just ignoring what --verifytypes says. I don't know for sure why it forbids assignment here.
Will look at stuff tomorrow :)

@etianen
Copy link
Contributor Author

etianen commented Feb 18, 2024

Thanks for the feedback! I've implemented the suggested changes. The new docs in particular look very nice! 💪

image

I'll await your findings on the --verifytypes problem. I couldn't find a way to selectively ignore this file, so hopefully you'll have a better time of it than me! 😅

@CoolCat467
Copy link
Member

@etianen
Copy link
Contributor Author

etianen commented Feb 18, 2024

One slightly interesting point https://trio--2959.org.readthedocs.build/en/2959/reference-io.html#trio.Path.link_to

I'm assuming these docs were built not on Python 3.12? The method is indeed gone in 3.12.

@etianen
Copy link
Contributor Author

etianen commented Feb 20, 2024

I've just had another look at this. Unfortunately, verifytypes does not respect:

  • exclude
  • ignore
  • defineConstant

The only solution I can see is the solution in #2910. Do we keep this open and wait for that to merge, or do we temporarily disable the check?

Copy link
Contributor

@A5rocks A5rocks left a comment

Choose a reason for hiding this comment

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

I forgot: could you add a newsfragment?

Also a couple comments, ranging from nitpicks to asking for a test or two

src/trio/_path.py Outdated Show resolved Hide resolved
os.PathLike.register(Path)
) -> AsyncIOWrapper[IO[Any]]: ...

@_wraps_async(pathlib.Path.open) # type: ignore[misc] # Overload return mismatch.
Copy link
Contributor

Choose a reason for hiding this comment

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

If this is what I think it is (mismatch because we don't have overloads for "r" vs "w" vs whatever), then IMO this should have a TODO. I realize you didn't write this type ignore and just copied it over, but yeah.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is probably actually because we return an AsyncIOWrapper, not the sync file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's neither! It's Type of decorated function contains type "Any" ("Callable[[Path, str, int, Optional[str], Optional[str], Optional[str]], Coroutine[Any, Any, AsyncIOWrapper[IO[Any]]]]")

For some reason, using any in a decorated function is naughty. It's not naughty if the function isn't decorated though. 🤷

src/trio/_tests/test_path.py Show resolved Hide resolved
src/trio/_tests/type_tests/path.py Outdated Show resolved Hide resolved
src/trio/_tests/type_tests/path.py Show resolved Hide resolved
@A5rocks
Copy link
Contributor

A5rocks commented Feb 21, 2024

The only solution I can see is the solution in #2910. Do we keep this open and wait for that to merge, or do we temporarily disable the check?

I'm sure specifically filtering pyright errors should be a pretty simple thing to do; let's wait a few days and see if that happens. If it doesn't, then temporarily disabling the check sounds good. (I'm hoping we can get a release out in ~a week, so that this and a bunch of changes can go out)

@TeamSpen210
Copy link
Contributor

Perhaps we should open an issue for Pyright. Thinking about it, it seems like a bug that verifytypes doesn't pick up on inference being required for the decorator, that's inconsistent.

@etianen
Copy link
Contributor Author

etianen commented Feb 21, 2024

Hopefully that's all points addressed! Some directly, others laterally.

Copy link
Contributor

@A5rocks A5rocks left a comment

Choose a reason for hiding this comment

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

Looks good, I like that this decreases the total amount of code!

(hopefully someone else can also give this a lookover because my passes might have missed something)

Copy link
Member

@CoolCat467 CoolCat467 left a comment

Choose a reason for hiding this comment

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

Looks pretty good other than one thing I noticed

src/trio/_tests/type_tests/path.py Outdated Show resolved Hide resolved
Copy link
Member

@CoolCat467 CoolCat467 left a comment

Choose a reason for hiding this comment

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

Very nice!

@A5rocks
Copy link
Contributor

A5rocks commented Mar 11, 2024

pyright changes are merged, you should be able to python src/trio/_tests/check_type_completeness.py --overwrite-file (or something like that) and that should be it! i tried doing that but for some reason github codespaces doesn't recognize that i can push to the branch and i dont want to bother setting this up locally.

@jakkdl
Copy link
Member

jakkdl commented Mar 11, 2024

I added some logic to ignore everything instead of listing all the errors in the json. I feel like has_docstring_at_runtime should've been able to pick up the docstrings, but I haven't bothered figuring out why it doesn't.

@etianen etianen merged commit f890f8f into python-trio:master Mar 11, 2024
28 checks passed
@etianen etianen deleted the dh/path-refactor branch March 11, 2024 21:35
@etianen
Copy link
Contributor Author

etianen commented Mar 11, 2024

It's in! Thanks all!

@jakkdl
Copy link
Member

jakkdl commented Mar 13, 2024

Thank you! And sorry for the delay with finishing up #2910 :)

@A5rocks
Copy link
Contributor

A5rocks commented Mar 17, 2024

fyi this went out in 0.25.0!

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.

5 participants