webdav.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. from __future__ import annotations
  2. import os
  3. from typing import Any
  4. from typing import Mapping
  5. from urllib.parse import urlsplit
  6. from fsspec.registry import known_implementations
  7. from fsspec.registry import register_implementation
  8. from upath._compat import FSSpecAccessorShim as _FSSpecAccessorShim
  9. from upath._compat import str_remove_prefix
  10. from upath._compat import str_remove_suffix
  11. from upath.core import UPath
  12. __all__ = [
  13. "WebdavPath",
  14. ]
  15. # webdav was only registered in fsspec>=2022.5.0
  16. if "webdav" not in known_implementations:
  17. import webdav4.fsspec
  18. register_implementation("webdav", webdav4.fsspec.WebdavFileSystem)
  19. # accessors are deprecated
  20. _WebdavAccessor = _FSSpecAccessorShim
  21. class WebdavPath(UPath):
  22. __slots__ = ()
  23. @classmethod
  24. def _transform_init_args(
  25. cls,
  26. args: tuple[str | os.PathLike, ...],
  27. protocol: str,
  28. storage_options: dict[str, Any],
  29. ) -> tuple[tuple[str | os.PathLike, ...], str, dict[str, Any]]:
  30. if not args:
  31. args = ("/",)
  32. elif args and protocol in {"webdav+http", "webdav+https"}:
  33. args0, *argsN = args
  34. url = urlsplit(str(args0))
  35. base = url._replace(scheme=protocol.split("+")[1], path="").geturl()
  36. args0 = url._replace(scheme="", netloc="").geturl() or "/"
  37. storage_options["base_url"] = base
  38. args = (args0, *argsN)
  39. if "base_url" not in storage_options:
  40. raise ValueError(
  41. f"must provide `base_url` storage option for args: {args!r}"
  42. )
  43. return super()._transform_init_args(args, "webdav", storage_options)
  44. @classmethod
  45. def _parse_storage_options(
  46. cls, urlpath: str, protocol: str, storage_options: Mapping[str, Any]
  47. ) -> dict[str, Any]:
  48. so = dict(storage_options)
  49. if urlpath.startswith(("webdav+http:", "webdav+https:")):
  50. url = urlsplit(str(urlpath))
  51. base = url._replace(scheme=url.scheme.split("+")[1], path="").geturl()
  52. urlpath = url._replace(scheme="", netloc="").geturl() or "/"
  53. so.setdefault("base_url", base)
  54. return super()._parse_storage_options(urlpath, "webdav", so)
  55. @property
  56. def path(self) -> str:
  57. # webdav paths don't start at "/"
  58. return str_remove_prefix(super().path, "/")
  59. def __str__(self):
  60. base_url = str_remove_suffix(self.storage_options["base_url"], "/")
  61. return super().__str__().replace("webdav://", f"webdav+{base_url}/", 1)