Skip to content

Commit bc25303

Browse files
authored
Pydantic serialize ProxyUPath (fsspec#538)
* tests: add a proxyupath serialization test * tests: add a posixupath windowsupath filepath serialization test * upath.extensions: fix pydantic serialization * upath.implementations.local: fix pydantic serialization * fix typing issue
1 parent 89d2e6d commit bc25303

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

upath/extensions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
else:
3939
from typing_extensions import Self
4040

41+
from pydantic import GetCoreSchemaHandler
42+
from pydantic_core.core_schema import CoreSchema
43+
4144
__all__ = [
4245
"ProxyUPath",
4346
]
@@ -576,5 +579,12 @@ def full_match(
576579
) -> bool:
577580
return self.__wrapped__.full_match(pattern, case_sensitive=case_sensitive)
578581

582+
@classmethod
583+
def __get_pydantic_core_schema__(
584+
cls, source_type: Any, handler: GetCoreSchemaHandler
585+
) -> CoreSchema:
586+
cs = UPath.__get_pydantic_core_schema__.__func__ # type: ignore[attr-defined]
587+
return cs(cls, source_type, handler)
588+
579589

580590
UPath.register(ProxyUPath)

upath/implementations/local.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
from typing_extensions import Self
4949
from typing_extensions import Unpack
5050

51+
from pydantic import GetCoreSchemaHandler
52+
from pydantic_core.core_schema import CoreSchema
53+
5154
from upath.types.storage_options import FileStorageOptions
5255

5356
_WT = TypeVar("_WT", bound="WritablePath")
@@ -725,6 +728,13 @@ def chmod(
725728
)
726729
return super().chmod(mode)
727730

731+
@classmethod
732+
def __get_pydantic_core_schema__(
733+
cls, source_type: Any, handler: GetCoreSchemaHandler
734+
) -> CoreSchema:
735+
cs = UPath.__get_pydantic_core_schema__.__func__ # type: ignore[attr-defined]
736+
return cs(cls, source_type, handler)
737+
728738

729739
UPath.register(LocalPath)
730740

upath/tests/test_pydantic.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
from fsspec.implementations.http import get_client
88

99
from upath import UPath
10+
from upath.implementations.local import FilePath
11+
from upath.implementations.local import PosixUPath
12+
from upath.implementations.local import WindowsUPath
13+
14+
from .utils import only_on_windows
15+
from .utils import skip_on_windows
1016

1117

1218
@pytest.mark.parametrize(
@@ -113,6 +119,43 @@ def test_dump_non_serializable_json():
113119
)
114120

115121

122+
def test_proxyupath_serialization():
123+
from upath.extensions import ProxyUPath
124+
125+
u = ProxyUPath("memory://my/path", some_option=True)
126+
127+
ta = pydantic.TypeAdapter(ProxyUPath)
128+
dumped = ta.dump_python(u, mode="python")
129+
loaded = ta.validate_python(dumped)
130+
131+
assert isinstance(loaded, ProxyUPath)
132+
assert loaded.path == u.path
133+
assert loaded.protocol == u.protocol
134+
assert loaded.storage_options == u.storage_options
135+
136+
137+
@pytest.mark.parametrize(
138+
"path,cls",
139+
[
140+
pytest.param("/my/path", PosixUPath, marks=skip_on_windows(None)),
141+
pytest.param("C:\\my\\path", WindowsUPath, marks=only_on_windows(None)),
142+
("file:///my/path", FilePath),
143+
],
144+
)
145+
def test_localpath_serialization(path, cls):
146+
u = UPath(path)
147+
assert type(u) is cls
148+
149+
ta = pydantic.TypeAdapter(cls)
150+
dumped = ta.dump_python(u, mode="python")
151+
loaded = ta.validate_python(dumped)
152+
153+
assert isinstance(loaded, cls)
154+
assert loaded.path == u.path
155+
assert loaded.protocol == u.protocol
156+
assert loaded.storage_options == u.storage_options
157+
158+
116159
def test_json_schema():
117160
ta = pydantic.TypeAdapter(UPath)
118161
ta.json_schema()

0 commit comments

Comments
 (0)