1# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2# SPDX-License-Identifier: Apache-2.0
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import logging
17import os
18import threading
19from collections.abc import Callable, Iterator
20from typing import Any, Optional, Union
21from urllib.parse import ParseResult, urlparse
22
23from .client import StorageClient
24from .config import RESERVED_POSIX_PROFILE_NAME, SUPPORTED_IMPLICIT_PROFILE_PROTOCOLS, StorageClientConfig
25from .file import ObjectFile, PosixFile
26from .telemetry import Telemetry
27from .types import MSC_PROTOCOL, ExecutionMode, ObjectMetadata, PatternList, SignerType, SymlinkHandling, SyncResult
28
29_TELEMETRY_PROVIDER: Optional[Callable[[], Telemetry]] = None
30_TELEMETRY_PROVIDER_LOCK = threading.Lock()
31_STORAGE_CLIENT_CACHE: dict[str, StorageClient] = {}
32_STORAGE_CLIENT_CACHE_LOCK = threading.Lock()
33_PROCESS_ID = os.getpid()
34
35logger = logging.getLogger(__name__)
36
37
38def _reinitialize_after_fork() -> None:
39 """
40 Reinitialize module state after fork to ensure fork-safety.
41
42 This function is called automatically after a fork to:
43 1. Clear the storage client cache (cached clients may have invalid state)
44 2. Reinitialize locks (parent's lock state must not be inherited)
45 3. Update process ID tracking
46
47 Note: The telemetry provider is intentionally inherited by child processes,
48 only its lock is reinitialized.
49 """
50 global _STORAGE_CLIENT_CACHE, _STORAGE_CLIENT_CACHE_LOCK
51 global _TELEMETRY_PROVIDER_LOCK
52 global _PROCESS_ID
53
54 _STORAGE_CLIENT_CACHE.clear()
55 _STORAGE_CLIENT_CACHE_LOCK = threading.Lock()
56 # we don't need to reset telemetry provider as it is supposed to be a top-level Python function
57 _TELEMETRY_PROVIDER_LOCK = threading.Lock()
58 _PROCESS_ID = os.getpid()
59
60
61def _check_and_reinitialize_if_forked() -> None:
62 """
63 Check if the current process is a fork and reinitialize if needed.
64
65 This provides fork-safety for systems where os.register_at_fork is not available
66 or as a fallback mechanism.
67 """
68 global _PROCESS_ID
69
70 current_pid = os.getpid()
71 if current_pid != _PROCESS_ID:
72 _reinitialize_after_fork()
73
74
75if hasattr(os, "register_at_fork"):
76 os.register_at_fork(after_in_child=_reinitialize_after_fork)
77
78
[docs]
79def get_telemetry_provider() -> Optional[Callable[[], Telemetry]]:
80 """
81 Get the function used to create :py:class:``Telemetry`` instances for storage clients created by shortcuts.
82
83 :return: A function that provides a telemetry instance.
84 """
85 global _TELEMETRY_PROVIDER
86
87 return _TELEMETRY_PROVIDER
88
89
[docs]
90def set_telemetry_provider(telemetry_provider: Optional[Callable[[], Telemetry]]) -> None:
91 """
92 Set the function used to create :py:class:``Telemetry`` instances for storage clients created by shortcuts.
93
94 :param telemetry_provider: A function that provides a telemetry instance. The function must be defined at the top level of a module to work with pickling.
95 """
96 global _TELEMETRY_PROVIDER
97 global _TELEMETRY_PROVIDER_LOCK
98
99 with _TELEMETRY_PROVIDER_LOCK:
100 _TELEMETRY_PROVIDER = telemetry_provider
101
102
103def _build_full_path(original_url: str, pr: ParseResult) -> str:
104 """
105 Helper function to construct the full path from a parsed URL, including query and fragment.
106
107 :param original_url: The original URL before parsing
108 :param pr: The parsed URL result from urlparse
109 :return: The complete path including query and fragment if present
110 """
111 path = pr.path
112 if pr.query:
113 path += "?" + pr.query
114 elif original_url.endswith("?"):
115 path += "?" # handle the glob pattern that has a trailing question mark
116 if pr.fragment:
117 path += "#" + pr.fragment
118 return path
119
120
121def _resolve_msc_url(url: str) -> tuple[str, str]:
122 """
123 Resolve an MSC URL to a profile name and path.
124
125 :param url: The MSC URL to resolve (msc://profile/path)
126 :return: A tuple of (profile_name, path)
127 """
128 pr = urlparse(url)
129 profile = pr.netloc
130 path = _build_full_path(url, pr)
131 if path.startswith("/"):
132 path = path[1:]
133 return profile, path
134
135
136def _resolve_non_msc_url(url: str) -> tuple[str, str]:
137 """
138 Resolve a non-MSC URL to a profile name and path.
139
140 Resolution process:
141 1. First check if MSC config exists
142 2. If config exists, check for possible path mapping
143 3. If no mapping is found, fall back to the reserved POSIX profile (``__filesystem__``) for file paths or create an implicit profile based on URL
144
145 :param url: The non-MSC URL to resolve
146 :return: A tuple of (profile_name, path)
147 """
148 # Check if we have a valid path mapping, if so check if there is a matching mapping
149 path_mapping = StorageClientConfig.read_path_mapping()
150 if path_mapping:
151 # Look for a matching mapping
152 possible_mapping = path_mapping.find_mapping(url)
153 if possible_mapping:
154 return possible_mapping # return the profile name and path
155
156 # For file paths, use the default POSIX profile
157 if url.startswith("file://"):
158 pr = urlparse(url)
159 return RESERVED_POSIX_PROFILE_NAME, _build_full_path(url, pr)
160 elif url.startswith("/"):
161 url = os.path.normpath(url)
162 return RESERVED_POSIX_PROFILE_NAME, url
163
164 # For other URL protocol, create an implicit profile name
165 pr = urlparse(url)
166 protocol = pr.scheme.lower()
167
168 # Translate relative paths to absolute paths
169 if not protocol:
170 return RESERVED_POSIX_PROFILE_NAME, os.path.realpath(url)
171
172 # Validate the protocol is supported
173 if protocol not in SUPPORTED_IMPLICIT_PROFILE_PROTOCOLS:
174 supported_protocols = ", ".join([f"{p}://" for p in SUPPORTED_IMPLICIT_PROFILE_PROTOCOLS])
175 raise ValueError(
176 f'Unknown URL "{url}", expecting "{MSC_PROTOCOL}" or a supported protocol ({supported_protocols}) or a POSIX path'
177 )
178
179 # Build the implicit profile name using the format _protocol-bucket
180 bucket = pr.netloc
181 if not bucket:
182 raise ValueError(f'Invalid URL "{url}", bucket name is required for {protocol}:// URLs')
183
184 profile_name = f"_{protocol}-{bucket}"
185
186 # Return normalized path with leading slash removed
187 path = pr.path
188 if path.startswith("/"):
189 path = path[1:]
190
191 return profile_name, path
192
193
[docs]
194def resolve_storage_client(url: str) -> tuple[StorageClient, str]:
195 """
196 Build and return a :py:class:`multistorageclient.StorageClient` instance based on the provided URL or path.
197
198 This function parses the given URL or path and determines the appropriate storage profile and path.
199 It supports URLs with the protocol ``msc://``, as well as POSIX paths or ``file://`` URLs for local file
200 system access. If the profile has already been instantiated, it returns the cached client. Otherwise,
201 it creates a new :py:class:`StorageClient` and caches it.
202
203 The function also supports implicit profiles for non-MSC URLs. When a non-MSC URL is provided (like s3://,
204 gs://, ais://, file://), MSC will infer the storage provider based on the URL protocol and create an implicit
205 profile with the naming convention "_protocol-bucket" (e.g., "_s3-bucket1", "_gs-bucket1").
206
207 Path mapping defined in the MSC configuration are also applied before creating implicit profiles.
208 This allows for explicit mappings between source paths and destination MSC profiles.
209
210 This function is fork-safe: after a fork, the cache is automatically cleared and new client instances
211 are created in the child process to avoid sharing stale connections or file descriptors.
212
213 :param url: The storage location, which can be:
214 - A URL in the format ``msc://profile/path`` for object storage.
215 - A local file system path (absolute POSIX path) or a ``file://`` URL.
216 - A non-MSC URL with a supported protocol (s3://, gs://, ais://).
217
218 :return: A tuple containing the :py:class:`multistorageclient.StorageClient` instance and the parsed path.
219
220 :raises ValueError: If the URL's protocol is neither ``msc`` nor a valid local file system path
221 or a supported non-MSC protocol.
222 """
223 global _STORAGE_CLIENT_CACHE
224 global _STORAGE_CLIENT_CACHE_LOCK
225
226 _check_and_reinitialize_if_forked()
227
228 # Normalize the path for msc:/ prefix due to pathlib.Path('msc://')
229 if url.startswith("msc:/") and not url.startswith("msc://"):
230 url = url.replace("msc:/", "msc://")
231
232 # Resolve the URL to a profile name and path
233 profile, path = _resolve_msc_url(url) if url.startswith(MSC_PROTOCOL) else _resolve_non_msc_url(url)
234
235 # Check if the profile has already been instantiated
236 if profile in _STORAGE_CLIENT_CACHE:
237 return _STORAGE_CLIENT_CACHE[profile], path
238
239 # Create a new StorageClient instance and cache it
240 with _STORAGE_CLIENT_CACHE_LOCK:
241 if profile in _STORAGE_CLIENT_CACHE:
242 return _STORAGE_CLIENT_CACHE[profile], path
243 else:
244 client = StorageClient(
245 config=StorageClientConfig.from_file(profile=profile, telemetry_provider=get_telemetry_provider())
246 )
247 _STORAGE_CLIENT_CACHE[profile] = client
248
249 return client, path
250
251
[docs]
252def open(url: str, mode: str = "rb", **kwargs: Any) -> Union[PosixFile, ObjectFile]:
253 """
254 Open a file at the given URL using the specified mode.
255
256 The function utilizes the :py:class:`multistorageclient.StorageClient` to open a file at the provided path.
257 The URL is parsed, and the corresponding :py:class:`multistorageclient.StorageClient` is retrieved or built.
258
259 :param url: The URL of the file to open. (example: ``msc://profile/prefix/dataset.tar``)
260 :param mode: The file mode to open the file in.
261
262 :return: A file-like object that allows interaction with the file.
263
264 :raises ValueError: If the URL's protocol does not match the expected protocol ``msc``.
265 """
266 client, path = resolve_storage_client(url)
267 return client.open(path, mode, **kwargs)
268
269
[docs]
270def glob(pattern: str, attribute_filter_expression: Optional[str] = None) -> list[str]:
271 """
272 Return a list of files matching a pattern.
273
274 This function supports glob-style patterns for matching multiple files within a storage system. The pattern is
275 parsed, and the associated :py:class:`multistorageclient.StorageClient` is used to retrieve the
276 list of matching files.
277
278 :param pattern: The glob-style pattern to match files. (example: ``msc://profile/prefix/**/*.tar``)
279 :param attribute_filter_expression: The attribute filter expression to apply to the result.
280
281 :return: A list of file paths matching the pattern.
282
283 :raises ValueError: If the URL's protocol does not match the expected protocol ``msc``.
284 """
285 client, path = resolve_storage_client(pattern)
286 if not pattern.startswith(MSC_PROTOCOL) and client.profile == RESERVED_POSIX_PROFILE_NAME:
287 return client.glob(path, include_url_prefix=False, attribute_filter_expression=attribute_filter_expression)
288 else:
289 return client.glob(path, include_url_prefix=True, attribute_filter_expression=attribute_filter_expression)
290
291
[docs]
292def upload_file(url: str, local_path: str, attributes: Optional[dict[str, Any]] = None) -> None:
293 """
294 Upload a file to the given URL from a local path.
295
296 The function utilizes the :py:class:`multistorageclient.StorageClient` to upload a file (object) to the
297 provided path. The URL is parsed, and the corresponding :py:class:`multistorageclient.StorageClient`
298 is retrieved or built.
299
300 :param url: The URL of the file. (example: ``msc://profile/prefix/dataset.tar``)
301 :param local_path: The local path of the file.
302
303 :raises ValueError: If the URL's protocol does not match the expected protocol ``msc``.
304 """
305 client, path = resolve_storage_client(url)
306 return client.upload_file(remote_path=path, local_path=local_path, attributes=attributes)
307
308
[docs]
309def download_file(url: str, local_path: str) -> None:
310 """
311 Download a file in a given remote_path to a local path
312
313 The function utilizes the :py:class:`multistorageclient.StorageClient` to download a file (object) at the
314 provided path. The URL is parsed, and the corresponding :py:class:`multistorageclient.StorageClient`
315 is retrieved or built.
316
317 :param url: The URL of the file to download. (example: ``msc://profile/prefix/dataset.tar``)
318 :param local_path: The local path where the file should be downloaded.
319
320 :raises ValueError: If the URL's protocol does not match the expected protocol ``msc``.
321 """
322 client, path = resolve_storage_client(url)
323 return client.download_file(remote_path=path, local_path=local_path)
324
325
[docs]
326def is_empty(url: str) -> bool:
327 """
328 Checks whether the specified URL contains any objects.
329
330 :param url: The URL to check, typically pointing to a storage location.
331 :return: ``True`` if there are no objects/files under this URL, ``False`` otherwise.
332
333 :raises ValueError: If the URL's protocol does not match the expected protocol ``msc``.
334 """
335 client, path = resolve_storage_client(url)
336 return client.is_empty(path)
337
338
[docs]
339def is_file(url: str) -> bool:
340 """
341 Checks whether the specified url points to a file (rather than a directory or folder).
342
343 The function utilizes the :py:class:`multistorageclient.StorageClient` to check if a file (object) exists
344 at the provided path. The URL is parsed, and the corresponding :py:class:`multistorageclient.StorageClient`
345 is retrieved or built.
346
347 :param url: The URL to check the existence of a file. (example: ``msc://profile/prefix/dataset.tar``)
348 """
349 client, path = resolve_storage_client(url)
350 return client.is_file(path=path)
351
352
[docs]
353def sync(
354 source_url: str,
355 target_url: str,
356 delete_unmatched_files: bool = False,
357 execution_mode: ExecutionMode = ExecutionMode.LOCAL,
358 patterns: Optional[PatternList] = None,
359 preserve_source_attributes: bool = False,
360 ignore_hidden: bool = True,
361 dryrun: bool = False,
362 dryrun_output_path: Optional[str] = None,
363 symlink_handling: SymlinkHandling = SymlinkHandling.FOLLOW,
364) -> SyncResult:
365 """
366 Syncs files from the source storage to the target storage.
367
368 :param source_url: The URL for the source storage.
369 :param target_url: The URL for the target storage.
370 :param delete_unmatched_files: Whether to delete files at the target that are not present at the source.
371 :param execution_mode: The execution mode to use. Currently supports "local" and "ray".
372 :param patterns: PatternList for include/exclude filtering. If None, all files are included.
373 :param preserve_source_attributes: Whether to preserve source file metadata attributes during synchronization.
374 When False (default), only file content is copied. When True, custom metadata attributes are also preserved.
375
376 .. warning::
377 **Performance Impact**: When enabled without a ``metadata_provider`` configured, this will make a HEAD
378 request for each object to retrieve attributes, which can significantly impact performance on large-scale
379 sync operations. For production use at scale, configure a ``metadata_provider`` in your storage profile.
380 :param ignore_hidden: Whether to ignore hidden files and directories (starting with dot). Default is True.
381 :param dryrun: If True, only enumerate and compare objects without performing any copy/delete operations.
382 The returned :py:class:`SyncResult` will include a :py:class:`DryrunResult` with paths to JSONL files.
383 :param dryrun_output_path: Directory to write dryrun JSONL files into. If None (default), a temporary
384 directory is created automatically. Ignored when dryrun is False.
385 :param symlink_handling: How to handle symbolic links during sync.
386 :py:attr:`SymlinkHandling.FOLLOW` (default) dereferences symlinks and copies the target's bytes.
387 :py:attr:`SymlinkHandling.SKIP` excludes symlinks from the sync.
388 :py:attr:`SymlinkHandling.PRESERVE` recreates symlinks on the target via
389 :py:meth:`AbstractStorageClient.make_symlink` instead of copying bytes (required for
390 round-trip preservation of symlinks).
391 """
392 source_client, source_path = resolve_storage_client(source_url)
393 target_client, target_path = resolve_storage_client(target_url)
394 return target_client.sync_from(
395 source_client,
396 source_path,
397 target_path,
398 delete_unmatched_files,
399 execution_mode=execution_mode,
400 patterns=patterns,
401 preserve_source_attributes=preserve_source_attributes,
402 ignore_hidden=ignore_hidden,
403 dryrun=dryrun,
404 dryrun_output_path=dryrun_output_path,
405 symlink_handling=symlink_handling,
406 )
407
408
[docs]
409def sync_replicas(
410 source_url: str,
411 replica_indices: Optional[list[int]] = None,
412 delete_unmatched_files: bool = False,
413 execution_mode: ExecutionMode = ExecutionMode.LOCAL,
414 patterns: Optional[PatternList] = None,
415 ignore_hidden: bool = True,
416 symlink_handling: SymlinkHandling = SymlinkHandling.FOLLOW,
417) -> None:
418 """
419 Syncs files from the source storage to all the replicas.
420
421 :param source_url: The URL for the source storage.
422 :param replica_indices: Specify the indices of the replicas to sync to. If not provided, all replicas will be synced. Index starts from 0.
423 :param delete_unmatched_files: Whether to delete files at the replicas that are not present at the source.
424 :param execution_mode: The execution mode to use. Currently supports "local" and "ray".
425 :param patterns: PatternList for include/exclude filtering. If None, all files are included.
426 :param ignore_hidden: Whether to ignore hidden files and directories (starting with dot). Default is True.
427 :param symlink_handling: How to handle symbolic links during sync.
428 :py:attr:`SymlinkHandling.FOLLOW` (default) dereferences symlinks and copies the target's bytes.
429 :py:attr:`SymlinkHandling.SKIP` excludes symlinks from the sync.
430 :py:attr:`SymlinkHandling.PRESERVE` recreates symlinks on each replica via
431 :py:meth:`AbstractStorageClient.make_symlink` instead of copying bytes.
432 """
433 source_client, source_path = resolve_storage_client(source_url)
434 source_client.sync_replicas(
435 source_path,
436 replica_indices=replica_indices,
437 delete_unmatched_files=delete_unmatched_files,
438 execution_mode=execution_mode,
439 patterns=patterns,
440 ignore_hidden=ignore_hidden,
441 symlink_handling=symlink_handling,
442 )
443
444
[docs]
445def list(
446 url: str,
447 start_after: Optional[str] = None,
448 end_at: Optional[str] = None,
449 include_directories: bool = False,
450 attribute_filter_expression: Optional[str] = None,
451 show_attributes: bool = False,
452 follow_symlinks: Optional[bool] = None,
453 patterns: Optional[PatternList] = None,
454 symlink_handling: SymlinkHandling = SymlinkHandling.FOLLOW,
455) -> Iterator[ObjectMetadata]:
456 """
457 Lists the contents of the specified URL prefix.
458
459 This function retrieves the corresponding :py:class:`multistorageclient.StorageClient`
460 for the given URL and returns an iterator of objects (files or directories) stored under the provided prefix.
461
462 :param url: The prefix to list objects under.
463 :param start_after: The key to start after (i.e. exclusive). An object with this key doesn't have to exist.
464 :param end_at: The key to end at (i.e. inclusive). An object with this key doesn't have to exist.
465 :param include_directories: Whether to include directories in the result. When True, directories are returned alongside objects.
466 :param attribute_filter_expression: The attribute filter expression to apply to the result.
467 :param show_attributes: Whether to return attributes in the result.
468 :param follow_symlinks: **Deprecated.** Use ``symlink_handling`` instead.
469 :param patterns: PatternList for include/exclude filtering. If None, all files are included.
470 :param symlink_handling: How to handle symbolic links. Only applicable for POSIX file storage.
471 :return: An iterator of :py:class:`ObjectMetadata` objects representing the files (and optionally directories)
472 accessible under the specified URL prefix. The returned keys will always be prefixed with msc://.
473 """
474 client, path = resolve_storage_client(url)
475 return client.list(
476 path=path,
477 start_after=start_after,
478 end_at=end_at,
479 include_directories=include_directories,
480 include_url_prefix=True,
481 attribute_filter_expression=attribute_filter_expression,
482 show_attributes=show_attributes,
483 follow_symlinks=follow_symlinks,
484 patterns=patterns,
485 symlink_handling=symlink_handling,
486 )
487
488
[docs]
489def list_recursive(
490 url: str,
491 start_after: Optional[str] = None,
492 end_at: Optional[str] = None,
493 max_workers: int = 32,
494 look_ahead: int = 2,
495 follow_symlinks: Optional[bool] = None,
496 patterns: Optional[PatternList] = None,
497 symlink_handling: SymlinkHandling = SymlinkHandling.FOLLOW,
498) -> Iterator[ObjectMetadata]:
499 """
500 Lists files recursively under the specified URL.
501
502 This function retrieves the corresponding :py:class:`multistorageclient.StorageClient`
503 for the given URL and returns an iterator of files under the provided path.
504
505 :param url: The path to list objects under.
506 :param start_after: The key to start after (i.e. exclusive). An object with this key doesn't have to exist.
507 :param end_at: The key to end at (i.e. inclusive). An object with this key doesn't have to exist.
508 :param max_workers: Maximum concurrent workers for provider-level recursive listing.
509 :param look_ahead: Prefixes to buffer per worker for provider-level recursive listing.
510 :param follow_symlinks: **Deprecated.** Use ``symlink_handling`` instead.
511 :param patterns: PatternList for include/exclude filtering. If None, all files are included.
512 :param symlink_handling: How to handle symbolic links during listing.
513 :return: An iterator of :py:class:`ObjectMetadata` objects representing files accessible under the specified URL path.
514 The returned keys use the same URL-prefix behavior as :py:meth:`multistorageclient.list`.
515 """
516 client, path = resolve_storage_client(url)
517 return client.list_recursive(
518 path=path,
519 start_after=start_after,
520 end_at=end_at,
521 max_workers=max_workers,
522 look_ahead=look_ahead,
523 include_url_prefix=True,
524 follow_symlinks=follow_symlinks,
525 patterns=patterns,
526 symlink_handling=symlink_handling,
527 )
528
529
[docs]
530def write(url: str, body: bytes, attributes: Optional[dict[str, Any]] = None) -> None:
531 """
532 Writes an object to the storage provider at the specified path.
533
534 :param url: The path where the object should be written.
535 :param body: The content to write to the object.
536 """
537 client, path = resolve_storage_client(url)
538 client.write(path=path, body=body, attributes=attributes)
539
540
[docs]
541def make_symlink(url: str, target_url: str) -> None:
542 """
543 Creates a symbolic link at ``url`` pointing to ``target_url``.
544
545 Both URLs must resolve to the same storage profile.
546
547 :param url: The URL where the symlink will be created.
548 :param target_url: The URL of the target that the symlink points to.
549 :raises ValueError: If the two URLs resolve to different storage profiles.
550 """
551 client, path = resolve_storage_client(url)
552 target_client, target_path = resolve_storage_client(target_url)
553 if client is not target_client:
554 raise ValueError("Cannot create cross-profile symlink: url and target_url must belong to the same profile.")
555 client.make_symlink(path=path, target=target_path)
556
557
[docs]
558def delete(url: str, recursive: bool = False) -> None:
559 """
560 Deletes the specified object(s) from the storage provider.
561
562 This function retrieves the corresponding :py:class:`multistorageclient.StorageClient`
563 for the given URL and deletes the object(s) at the specified path.
564
565 :param url: The URL of the object to delete. (example: ``msc://profile/prefix/file.txt``)
566 :param recursive: Whether to delete objects in the path recursively.
567 """
568 client, path = resolve_storage_client(url)
569 client.delete(path, recursive=recursive)
570
571
[docs]
572def info(url: str) -> ObjectMetadata:
573 """
574 Retrieves metadata or information about an object stored at the specified path.
575
576 :param url: The URL of the object to retrieve information about. (example: ``msc://profile/prefix/file.txt``)
577
578 :return: An :py:class:`ObjectMetadata` object representing the object's metadata.
579 """
580 client, path = resolve_storage_client(url)
581 return client.info(path)
582
583
592
593
[docs]
594def generate_presigned_url(
595 url: str,
596 *,
597 method: str = "GET",
598 signer_type: Optional[SignerType] = None,
599 signer_options: Optional[dict[str, Any]] = None,
600) -> str:
601 """
602 Generate a pre-signed URL granting temporary access to the object at *url*.
603
604 :param url: The storage URL. (example: ``msc://profile/prefix/file.bin``)
605 :param method: The HTTP method the URL should authorise (e.g. ``"GET"``, ``"PUT"``).
606 :param signer_type: The signing backend to use. ``None`` means the provider's native signer.
607 :param signer_options: Backend-specific options forwarded to the signer.
608 :return: A pre-signed URL string.
609 """
610 client, path = resolve_storage_client(url)
611 return client.generate_presigned_url(path, method=method, signer_type=signer_type, signer_options=signer_options)