Coverage for python/lsst/resources/proxied.py: 82%
87 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-22 09:18 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-22 09:18 +0000
1# This file is part of lsst-resources.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12from __future__ import annotations
14__all__ = ("ProxiedResourcePath",)
16import contextlib
17import dataclasses
18import logging
19import re
20from abc import ABC, abstractmethod
21from collections.abc import Generator, Iterator
22from typing import TYPE_CHECKING
24from ._resourcePath import ResourceHandleProtocol, ResourceInfo, ResourcePath, ResourcePathExpression
25from .utils import TransactionProtocol
27try:
28 import fsspec
29 from fsspec.spec import AbstractFileSystem
30except ImportError:
31 # Hidden from type checkers so that the names above keep the types they
32 # have when fsspec is installed.
33 if not TYPE_CHECKING:
34 fsspec = None
35 AbstractFileSystem = type
38log = logging.getLogger(__name__)
41class ProxiedResourcePath(ABC, ResourcePath):
42 """URI that is represented internally by another type of URI for file I/O.
44 For example ``abc://xyz/file.txt`` could be the public URI form but
45 internally all file access is forwarded to a ``file`` URI.
46 """
48 _proxy: ResourcePath | None = None
50 @abstractmethod
51 def _set_proxy(self) -> None:
52 """Calculate the internal `ResourcePath` corresponding to the public
53 version.
54 """
55 raise NotImplementedError("Proxy must be configured")
57 def _get_proxy(self) -> ResourcePath:
58 """Retrieve the proxied ResourcePath."""
59 proxy = self._proxy
60 if proxy is None:
61 raise FileNotFoundError(f"Internal error: No proxy ResourcePath available for {self}")
62 return proxy
64 def to_fsspec(self) -> tuple[AbstractFileSystem, str]:
65 try:
66 proxy = self._get_proxy()
67 except FileNotFoundError:
68 raise NotImplementedError(f"No proxy registered for {self}. Resource does not exist.") from None
69 return proxy.to_fsspec()
71 def isdir(self) -> bool:
72 if self.dirLike is None:
73 try:
74 proxy = self._get_proxy()
75 except FileNotFoundError:
76 return False
77 self.dirLike = proxy.isdir()
78 return self.dirLike
80 def exists(self) -> bool:
81 try:
82 proxy = self._get_proxy()
83 except FileNotFoundError:
84 # If there is no proxy registered then the resource can not exist.
85 return False
86 return proxy.exists()
88 def remove(self) -> None:
89 proxy = self._get_proxy()
90 proxy.remove()
92 def read(self, size: int = -1) -> bytes:
93 proxy = self._get_proxy()
94 return proxy.read(size=size)
96 @contextlib.contextmanager
97 def as_local(
98 self, multithreaded: bool = True, tmpdir: ResourcePathExpression | None = None
99 ) -> Generator[ResourcePath]:
100 proxy = self._get_proxy()
101 with proxy.as_local(multithreaded=multithreaded, tmpdir=tmpdir) as loc:
102 yield loc
104 @contextlib.contextmanager
105 def open(
106 self,
107 mode: str = "r",
108 *,
109 encoding: str | None = None,
110 prefer_file_temporary: bool = False,
111 ) -> Generator[ResourceHandleProtocol]:
112 proxy = self._get_proxy()
113 with proxy.open(mode, encoding=encoding, prefer_file_temporary=prefer_file_temporary) as fh:
114 yield fh
116 def walk(
117 self, file_filter: str | re.Pattern | None = None
118 ) -> Iterator[list | tuple[ResourcePath, list[str], list[str]]]:
119 try:
120 proxy = self._get_proxy()
121 except FileNotFoundError as e:
122 raise ValueError(str(e)) from None
123 for proxied_root, dirs, files in proxy.walk(file_filter=file_filter):
124 # Need to return the directory in the original form and not the
125 # proxy form.
126 relative_to_self = proxied_root.path.removeprefix(proxy.path)
127 root = self.replace(path=self._pathModule.join(self.path, relative_to_self))
128 yield root, dirs, files
130 def size(self) -> int:
131 proxy = self._get_proxy()
132 return proxy.size()
134 def get_info(self) -> ResourceInfo:
135 proxy = self._get_proxy()
136 return dataclasses.replace(proxy.get_info(), uri=str(self))
138 def write(self, data: bytes, overwrite: bool = True) -> None:
139 proxy = self._get_proxy()
140 proxy.write(data, overwrite=overwrite)
142 def mkdir(self) -> None:
143 proxy = self._get_proxy()
144 proxy.mkdir()
146 def transfer_from(
147 self,
148 src: ResourcePath,
149 transfer: str = "copy",
150 overwrite: bool = False,
151 transaction: TransactionProtocol | None = None,
152 multithreaded: bool = True,
153 ) -> None:
154 proxy = self._get_proxy()
155 proxy.transfer_from(
156 src, transfer=transfer, overwrite=overwrite, transaction=transaction, multithreaded=multithreaded
157 )