@@ -625,13 +625,109 @@ assert ExtraUPath("s3://bucket/file.txt").some_extra_method() == "hello world"
625625
626626## Migration Guide
627627
628- UPath's internal implementation is likely going to change with larger changes in
629- CPython's stdlib ` pathlib ` landing in the next Python versions (` 3.13 ` , ` 3.14 ` ).
630- To reduce the problems for user code, when these changes are landing in ` UPath ` ,
631- there have been some significant changes in ` v0.2.0 ` . This migration guide tries
632- to help migrating code that extensively relies on private implementation details
633- of the ` UPath ` class of versions ` v0.1.x ` to the new and better supported public
634- interface of ` v0.2.0 `
628+ UPath's internal implementation is converging towards a more stable state,
629+ with changes in CPython's stdlib ` pathlib ` having landed in newer Python versions
630+ (` 3.13 ` , ` 3.14 ` ) and the currently private interface for JoinablePaths,
631+ ReadablePaths, and WriteablePaths stabilizing. There will likely be other
632+ breaking changes down the line, but we'll make the transition as smooth
633+ as possible.
634+
635+ ### migrating to ` v0.3.0 `
636+
637+ Version ` 0.3.0 ` introduced a breaking change to fix a longstanding bug related to
638+ ` os.PathLike ` protocol compliance. This change affects how UPath instances work
639+ with standard library functions that expect local filesystem paths.
640+
641+ #### Background: PathLike protocol and local filesystem paths
642+
643+ In Python, ` os.PathLike ` objects and ` pathlib.Path ` subclasses represent local
644+ filesystem paths. This is used by the standard library - functions like
645+ ` os.remove() ` , ` shutil.copy() ` , and similar expect paths that point to the local
646+ filesystem. However, UPath implementations like ` S3Path ` or ` MemoryPath ` do not
647+ represent local filesystem paths and should not be treated as such.
648+
649+ Prior to ` v0.3.0 ` , all UPath instances incorrectly implemented ` os.PathLike ` ,
650+ which could lead to runtime errors when non-local paths were passed to functions
651+ expecting local paths. Starting with ` v0.3.0 ` , only local UPath implementations
652+ (` PosixUPath ` , ` WindowsUPath ` , and ` FilePath ` ) implement ` os.PathLike ` .
653+
654+ #### Migration strategy
655+
656+ If your code passes UPath instances to functions expecting ` os.PathLike ` objects,
657+ you have several options:
658+
659+ ** Option 1: Explicitly request a local path** (Recommended)
660+
661+ ``` python
662+ import os
663+ from upath import UPath
664+
665+ # Explicitly specify the file:// protocol to get a FilePath instance
666+ path = UPath(__file__ , protocol = " file" )
667+ assert isinstance (path, os.PathLike) # True
668+
669+ # Now you can safely use it with os functions
670+ os.remove(path)
671+ ```
672+
673+ ** Option 2: Use UPath's filesystem operations**
674+
675+ ``` python
676+ from upath import UPath
677+
678+ # Works for any UPath implementation, not just local paths
679+ path = UPath(" s3://bucket/file.txt" )
680+ path.unlink() # UPath's native unlink method
681+ ```
682+
683+ ** Option 3: Use type checking with upath.types**
684+
685+ For code that needs to work with different path types, use the type hints from
686+ ` upath.types ` to properly specify your requirements:
687+
688+ ``` python
689+ from upath import UPath
690+ from upath.types import (
691+ JoinablePathLike,
692+ ReadablePathLike,
693+ WritablePathLike,
694+ )
695+
696+ def read_only_local_file (path : os.PathLike) -> None :
697+ """ Read a file on the local filesystem."""
698+ with open (path) as f:
699+ return f.read_text()
700+
701+ def write_only_local_file (path : os.PathLike) -> None :
702+ """ Write to a file on the local filesystem."""
703+ with open (path) as f:
704+ f.write_text(" hello world" )
705+
706+ def read_any_file (path : WritablePathLike) -> None :
707+ """ Write a file on any filesystem."""
708+ return UPath(path).read_text()
709+
710+ def read_any_file (path : WritablePathLike) -> None :
711+ """ Write a file on any filesystem."""
712+ UPath(path).write_text(" hello world" )
713+ ```
714+
715+ #### Example: Incorrect code that would fail
716+
717+ The following example shows code that would incorrectly work in ` v0.2.x ` but
718+ properly fail in ` v0.3.0 ` :
719+
720+ ``` python
721+ import os
722+ from upath import UPath
723+
724+ # This creates a MemoryPath, which is not a local filesystem path
725+ path = UPath(" memory:///file.txt" )
726+
727+ # In v0.2.x this would incorrectly accept the path and fail at runtime
728+ # In v0.3.0 this correctly fails at type-check time
729+ os.remove(path) # TypeError: expected str, bytes or os.PathLike, not MemoryPath
730+ ```
635731
636732### migrating to ` v0.2.0 `
637733
0 commit comments