Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
...

## [0.3.3] - 2025-10-08
### Added
- upath.implementations: add `ZipPath` for ZIP archive filesystem access (#442)
- upath.implementations: add `TarPath` for TAR archive filesystem access (#443)
- tests: add chained ZIP and TAR path tests (#440)

### Fixed
- upath.core: remove `chain_parser` parameter from type overloads to improve type narrowing (#436)

### Changed
- docs: update README with notes about `__fspath__()` behavior

## [0.3.2] - 2025-10-05
### Added
Expand Down Expand Up @@ -242,7 +253,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- started a changelog to keep track of significant changes

[Unreleased]: https://github.com/fsspec/universal_pathlib/compare/v0.3.2...HEAD
[Unreleased]: https://github.com/fsspec/universal_pathlib/compare/v0.3.3...HEAD
[0.3.3]: https://github.com/fsspec/universal_pathlib/compare/v0.3.2...v0.3.3
[0.3.2]: https://github.com/fsspec/universal_pathlib/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/fsspec/universal_pathlib/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/fsspec/universal_pathlib/compare/v0.2.6...v0.3.0
Expand Down
110 changes: 103 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -625,13 +625,109 @@ assert ExtraUPath("s3://bucket/file.txt").some_extra_method() == "hello world"

## Migration Guide

UPath's internal implementation is likely going to change with larger changes in
CPython's stdlib `pathlib` landing in the next Python versions (`3.13`, `3.14`).
To reduce the problems for user code, when these changes are landing in `UPath`,
there have been some significant changes in `v0.2.0`. This migration guide tries
to help migrating code that extensively relies on private implementation details
of the `UPath` class of versions `v0.1.x` to the new and better supported public
interface of `v0.2.0`
UPath's internal implementation is converging towards a more stable state,
with changes in CPython's stdlib `pathlib` having landed in newer Python versions
(`3.13`, `3.14`) and the currently private interface for JoinablePaths,
ReadablePaths, and WriteablePaths stabilizing. There will likely be other
breaking changes down the line, but we'll make the transition as smooth
as possible.

### migrating to `v0.3.0`

Version `0.3.0` introduced a breaking change to fix a longstanding bug related to
`os.PathLike` protocol compliance. This change affects how UPath instances work
with standard library functions that expect local filesystem paths.

#### Background: PathLike protocol and local filesystem paths

In Python, `os.PathLike` objects and `pathlib.Path` subclasses represent local
filesystem paths. This is used by the standard library - functions like
`os.remove()`, `shutil.copy()`, and similar expect paths that point to the local
filesystem. However, UPath implementations like `S3Path` or `MemoryPath` do not
represent local filesystem paths and should not be treated as such.

Prior to `v0.3.0`, all UPath instances incorrectly implemented `os.PathLike`,
which could lead to runtime errors when non-local paths were passed to functions
expecting local paths. Starting with `v0.3.0`, only local UPath implementations
(`PosixUPath`, `WindowsUPath`, and `FilePath`) implement `os.PathLike`.

#### Migration strategy

If your code passes UPath instances to functions expecting `os.PathLike` objects,
you have several options:

**Option 1: Explicitly request a local path** (Recommended)

```python
import os
from upath import UPath

# Explicitly specify the file:// protocol to get a FilePath instance
path = UPath(__file__, protocol="file")
assert isinstance(path, os.PathLike) # True

# Now you can safely use it with os functions
os.remove(path)
```

**Option 2: Use UPath's filesystem operations**

```python
from upath import UPath

# Works for any UPath implementation, not just local paths
path = UPath("s3://bucket/file.txt")
path.unlink() # UPath's native unlink method
```

**Option 3: Use type checking with upath.types**

For code that needs to work with different path types, use the type hints from
`upath.types` to properly specify your requirements:

```python
from upath import UPath
from upath.types import (
JoinablePathLike,
ReadablePathLike,
WritablePathLike,
)

def read_only_local_file(path: os.PathLike) -> None:
"""Read a file on the local filesystem."""
with open(path) as f:
return f.read_text()

def write_only_local_file(path: os.PathLike) -> None:
"""Write to a file on the local filesystem."""
with open(path) as f:
f.write_text("hello world")

def read_any_file(path: WritablePathLike) -> None:
"""Write a file on any filesystem."""
return UPath(path).read_text()

def read_any_file(path: WritablePathLike) -> None:
"""Write a file on any filesystem."""
UPath(path).write_text("hello world")
```

#### Example: Incorrect code that would fail

The following example shows code that would incorrectly work in `v0.2.x` but
properly fail in `v0.3.0`:

```python
import os
from upath import UPath

# This creates a MemoryPath, which is not a local filesystem path
path = UPath("memory:///file.txt")

# In v0.2.x this would incorrectly accept the path and fail at runtime
# In v0.3.0 this correctly fails at type-check time
os.remove(path) # TypeError: expected str, bytes or os.PathLike, not MemoryPath
```

### migrating to `v0.2.0`

Expand Down