lamindb 1.2a2__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ from django.db.models import CASCADE, PROTECT, Q
16
16
  from lamin_utils import colors, logger
17
17
  from lamindb_setup import settings as setup_settings
18
18
  from lamindb_setup._init_instance import register_storage_in_instance
19
+ from lamindb_setup.core import doc_args
19
20
  from lamindb_setup.core._settings_storage import init_storage
20
21
  from lamindb_setup.core.hashing import HASH_LENGTH, hash_dir, hash_file
21
22
  from lamindb_setup.core.types import UPathStr
@@ -37,6 +38,7 @@ from lamindb.errors import FieldValidationError
37
38
  from lamindb.models.query_set import QuerySet
38
39
 
39
40
  from ..base.users import current_user_id
41
+ from ..core._compat import is_package_installed
40
42
  from ..core.loaders import load_to_memory
41
43
  from ..core.storage import (
42
44
  LocalPathClasses,
@@ -48,7 +50,6 @@ from ..core.storage import (
48
50
  from ..core.storage._anndata_accessor import _anndata_n_observations
49
51
  from ..core.storage._pyarrow_dataset import PYARROW_SUFFIXES
50
52
  from ..core.storage._tiledbsoma import _soma_n_observations
51
- from ..core.storage.objects import is_package_installed
52
53
  from ..core.storage.paths import (
53
54
  AUTO_KEY_PREFIX,
54
55
  auto_storage_key_from_artifact,
@@ -93,6 +94,8 @@ WARNING_RUN_TRANSFORM = "no run & transform got linked, call `ln.track()` & re-r
93
94
 
94
95
  WARNING_NO_INPUT = "run input wasn't tracked, call `ln.track()` and re-run"
95
96
 
97
+ DEBUG_KWARGS_DOC = "**kwargs: Internal arguments for debugging."
98
+
96
99
  try:
97
100
  from ..core.storage._zarr import identify_zarr_type
98
101
  except ImportError:
@@ -113,6 +116,7 @@ if TYPE_CHECKING:
113
116
 
114
117
  from lamindb.base.types import StrField
115
118
  from lamindb.core.storage._backed_access import AnnDataAccessor, BackedAccessor
119
+ from lamindb.core.types import ScverseDataStructures
116
120
 
117
121
  from ..base.types import (
118
122
  ArtifactKind,
@@ -126,7 +130,7 @@ if TYPE_CHECKING:
126
130
  INCONSISTENT_STATE_MSG = (
127
131
  "Trying to read a folder artifact from an outdated version, "
128
132
  "this can result in an incosistent state.\n"
129
- "Read from the latest version: artifact.versions.filter(is_latest=True).one()"
133
+ "Read from the latest version: artifact.versions.get(is_latest=True)"
130
134
  )
131
135
 
132
136
 
@@ -364,7 +368,7 @@ def get_relative_path_to_directory(
364
368
 
365
369
  def get_artifact_kwargs_from_data(
366
370
  *,
367
- data: Path | UPath | str | pd.DataFrame | AnnData | MuData,
371
+ data: Path | UPath | str | pd.DataFrame | ScverseDataStructures,
368
372
  key: str | None,
369
373
  run: Run | None,
370
374
  format: str | None,
@@ -554,7 +558,7 @@ def data_is_spatialdata(data: SpatialData | UPathStr) -> bool:
554
558
 
555
559
 
556
560
  def _check_otype_artifact(
557
- data: UPathStr | pd.DataFrame | AnnData | MuData | SpatialData,
561
+ data: UPathStr | pd.DataFrame | ScverseDataStructures,
558
562
  otype: str | None = None,
559
563
  ) -> str:
560
564
  if otype is None:
@@ -601,10 +605,10 @@ def get_run(run: Run | None) -> Run | None:
601
605
  run = context.run
602
606
  if run is None and not settings.creation.artifact_silence_missing_run_warning:
603
607
  # here we check that this is not a read-only connection
604
- # normally for our connection strings the read-only role name has _read in it
608
+ # normally for our connection strings the read-only role name has "read" in it
605
609
  # not absolutely safe but the worst case is that the warning is not shown
606
610
  instance = setup_settings.instance
607
- if instance.dialect != "postgresql" or "_read" not in instance.db:
611
+ if instance.dialect != "postgresql" or "read" not in instance.db:
608
612
  logger.warning(WARNING_RUN_TRANSFORM)
609
613
  # suppress run by passing False
610
614
  elif not run:
@@ -1427,7 +1431,11 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1427
1431
  kwargs["uid"] = uid
1428
1432
 
1429
1433
  # only set key now so that we don't do a look-up on it in case revises is passed
1430
- if revises is not None:
1434
+ if revises is not None and revises.key is not None:
1435
+ assert revises.key.endswith(kwargs["suffix"]), ( # noqa: S101
1436
+ revises.key,
1437
+ kwargs["suffix"],
1438
+ )
1431
1439
  kwargs["key"] = revises.key
1432
1440
 
1433
1441
  kwargs["kind"] = kind
@@ -1467,39 +1475,23 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1467
1475
  def n_objects(self) -> int:
1468
1476
  return self.n_files
1469
1477
 
1470
- # add the below because this is what people will have in their code
1471
- # if they implement the recommended migration strategy
1472
- # - FeatureSet -> Schema
1473
- # - featureset -> schema
1474
- # - feature_set -> schema
1475
- # @property
1476
- # def schemas(self) -> QuerySet[Schema]:
1477
- # """Schemas linked to artifact via many-to-many relationship.
1478
-
1479
- # Is now mediating the private `.feature_sets` relationship during
1480
- # a transition period to better schema management.
1481
-
1482
- # .. versionchanged: 1.0
1483
- # Was previously called `.feature_sets`.
1484
-
1485
- # """
1486
- # return self.feature_sets
1487
-
1488
1478
  @property
1489
1479
  def path(self) -> Path:
1490
1480
  """Path.
1491
1481
 
1492
- File in cloud storage, here AWS S3:
1482
+ Example::
1493
1483
 
1494
- >>> artifact = ln.Artifact("s3://my-bucket/my-file.csv").save()
1495
- >>> artifact.path
1496
- S3QueryPath('s3://my-bucket/my-file.csv')
1484
+ import lamindb as ln
1497
1485
 
1498
- File in local storage:
1486
+ # File in cloud storage, here AWS S3:
1487
+ artifact = ln.Artifact("s3://my-bucket/my-file.csv").save()
1488
+ artifact.path
1489
+ #S3QueryPath('s3://my-bucket/my-file.csv')
1499
1490
 
1500
- >>> ln.Artifact("./myfile.csv", key="myfile.csv").save()
1501
- >>> artifact.path
1502
- PosixPath('/home/runner/work/lamindb/lamindb/docs/guide/mydata/myfile.csv')
1491
+ # File in local storage:
1492
+ ln.Artifact("./myfile.csv", key="myfile.csv").save()
1493
+ artifact.path
1494
+ #> PosixPath('/home/runner/work/lamindb/lamindb/docs/guide/mydata/myfile.csv')
1503
1495
  """
1504
1496
  from lamindb import settings
1505
1497
 
@@ -1519,6 +1511,34 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1519
1511
  filepath, cache_key=cache_key
1520
1512
  )
1521
1513
 
1514
+ @classmethod
1515
+ def get(
1516
+ cls,
1517
+ idlike: int | str | None = None,
1518
+ **expressions,
1519
+ ) -> Artifact:
1520
+ """Get a single artifact.
1521
+
1522
+ Args:
1523
+ idlike: Either a uid stub, uid or an integer id.
1524
+ expressions: Fields and values passed as Django query expressions.
1525
+
1526
+ Raises:
1527
+ :exc:`docs:lamindb.errors.DoesNotExist`: In case no matching record is found.
1528
+
1529
+ See Also:
1530
+ - Guide: :doc:`docs:registries`
1531
+ - Method in `Record` base class: :meth:`~lamindb.models.Record.get`
1532
+
1533
+ Examples::
1534
+
1535
+ artifact = ln.Artifact.get("tCUkRcaEjTjhtozp0000")
1536
+ artifact = ln.Arfifact.get(key="my_datasets/my_file.parquet")
1537
+ """
1538
+ from .query_set import QuerySet
1539
+
1540
+ return QuerySet(model=cls).get(idlike, **expressions)
1541
+
1522
1542
  @classmethod
1523
1543
  def from_df(
1524
1544
  cls,
@@ -1546,17 +1566,19 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1546
1566
  :class:`~lamindb.Feature`
1547
1567
  Track features.
1548
1568
 
1549
- Examples:
1550
- >>> df = ln.core.datasets.df_iris_in_meter_batch1()
1551
- >>> df.head()
1552
- sepal_length sepal_width petal_length petal_width iris_organism_code
1553
- 0 0.051 0.035 0.014 0.002 0
1554
- 1 0.049 0.030 0.014 0.002 0
1555
- 2 0.047 0.032 0.013 0.002 0
1556
- 3 0.046 0.031 0.015 0.002 0
1557
- 4 0.050 0.036 0.014 0.002 0
1558
- >>> artifact = ln.Artifact.from_df(df, description="Iris flower collection batch1")
1559
- >>> artifact.save()
1569
+ Example::
1570
+
1571
+ import lamindb as ln
1572
+
1573
+ df = ln.core.datasets.df_iris_in_meter_batch1()
1574
+ df.head()
1575
+ #> sepal_length sepal_width petal_length petal_width iris_organism_code
1576
+ #> 0 0.051 0.035 0.014 0.002 0
1577
+ #> 1 0.049 0.030 0.014 0.002 0
1578
+ #> 2 0.047 0.032 0.013 0.002 0
1579
+ #> 3 0.046 0.031 0.015 0.002 0
1580
+ #> 4 0.050 0.036 0.014 0.002 0
1581
+ artifact = ln.Artifact.from_df(df, key="iris/result_batch1.parquet").save()
1560
1582
  """
1561
1583
  artifact = Artifact( # type: ignore
1562
1584
  data=df,
@@ -1599,12 +1621,12 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1599
1621
  :class:`~lamindb.Feature`
1600
1622
  Track features.
1601
1623
 
1602
- Examples:
1603
- >>> import bionty as bt
1604
- >>> bt.settings.organism = "human"
1605
- >>> adata = ln.core.datasets.anndata_with_obs()
1606
- >>> artifact = ln.Artifact.from_anndata(adata, description="mini anndata with obs")
1607
- >>> artifact.save()
1624
+ Example::
1625
+
1626
+ import lamindb as ln
1627
+
1628
+ adata = ln.core.datasets.anndata_with_obs()
1629
+ artifact = ln.Artifact.from_anndata(adata, key="mini_anndata_with_obs.h5ad").save()
1608
1630
  """
1609
1631
  if not data_is_anndata(adata):
1610
1632
  raise ValueError(
@@ -1661,12 +1683,12 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1661
1683
  :class:`~lamindb.Feature`
1662
1684
  Track features.
1663
1685
 
1664
- Examples:
1665
- >>> import bionty as bt
1666
- >>> bt.settings.organism = "human"
1667
- >>> mdata = ln.core.datasets.mudata_papalexi21_subset()
1668
- >>> artifact = ln.Artifact.from_mudata(mdata, description="a mudata object")
1669
- >>> artifact.save()
1686
+ Example::
1687
+
1688
+ import lamindb as ln
1689
+
1690
+ mdata = ln.core.datasets.mudata_papalexi21_subset()
1691
+ artifact = ln.Artifact.from_mudata(mdata, key="mudata_papalexi21_subset.h5mu").save()
1670
1692
  """
1671
1693
  if not data_is_mudata(mdata):
1672
1694
  raise ValueError("data has to be a MuData object or a path to MuData-like")
@@ -1711,8 +1733,11 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1711
1733
  :class:`~lamindb.Feature`
1712
1734
  Track features.
1713
1735
 
1714
- Examples:
1715
- >>> artifact = ln.Artifact.from_spatialdata(sdata, key="my_dataset.zarr")
1736
+ Example::
1737
+
1738
+ import lamindb as ln
1739
+
1740
+ artifact = ln.Artifact.from_spatialdata(sdata, key="my_dataset.zarr").save()
1716
1741
  """
1717
1742
  if not data_is_spatialdata(sdata):
1718
1743
  raise ValueError(
@@ -1753,9 +1778,11 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1753
1778
  revises: An old version of the artifact.
1754
1779
  run: The run that creates the artifact.
1755
1780
 
1756
- Examples:
1757
- >>> artifact = ln.Artifact.from_tiledbsoma("s3://mybucket/store.tiledbsoma", description="a tiledbsoma store")
1758
- >>> artifact.save()
1781
+ Example::
1782
+
1783
+ import lamindb as ln
1784
+
1785
+ artifact = ln.Artifact.from_tiledbsoma("s3://mybucket/store.tiledbsoma", description="a tiledbsoma store").save()
1759
1786
  """
1760
1787
  if UPath(path).suffix != ".tiledbsoma":
1761
1788
  raise ValueError(
@@ -1797,10 +1824,13 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1797
1824
  of a registered storage location, the inferred key defaults to `path.name`.
1798
1825
  run: A `Run` object.
1799
1826
 
1800
- Examples:
1801
- >>> dir_path = ln.core.datasets.generate_cell_ranger_files("sample_001", ln.settings.storage)
1802
- >>> artifacts = ln.Artifact.from_dir(dir_path)
1803
- >>> ln.save(artifacts)
1827
+ Example::
1828
+
1829
+ import lamindb as ln
1830
+
1831
+ dir_path = ln.core.datasets.generate_cell_ranger_files("sample_001", ln.settings.storage)
1832
+ artifacts = ln.Artifact.from_dir(dir_path)
1833
+ ln.save(artifacts)
1804
1834
  """
1805
1835
  from lamindb import settings
1806
1836
 
@@ -1987,6 +2017,10 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
1987
2017
  # no need to upload if new file is already in storage
1988
2018
  self._to_store = not check_path_in_storage
1989
2019
 
2020
+ # update old suffix with the new one so that checks in record pass
2021
+ # replace() supports changing the suffix
2022
+ self._old_suffix = self.suffix
2023
+
1990
2024
  def open(
1991
2025
  self, mode: str = "r", is_run_input: bool | None = None, **kwargs
1992
2026
  ) -> Union[
@@ -2005,19 +2039,24 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2005
2039
  Args:
2006
2040
  mode: can only be `"w"` (write mode) for `tiledbsoma` stores,
2007
2041
  otherwise should be always `"r"` (read-only mode).
2042
+ is_run_input: Whether to track this artifact as run input.
2043
+ **kwargs: Keyword arguments for the accessor, i.e. `h5py` or `zarr` connection,
2044
+ `pyarrow.dataset.dataset`.
2008
2045
 
2009
2046
  Notes:
2010
2047
  For more info, see tutorial: :doc:`/arrays`.
2011
2048
 
2012
- Examples:
2049
+ Example::
2050
+
2051
+ import lamindb as ln
2013
2052
 
2014
- Read AnnData in backed mode from cloud:
2053
+ # Read AnnData in backed mode from cloud
2015
2054
 
2016
- >>> artifact = ln.Artifact.get(key="lndb-storage/pbmc68k.h5ad")
2017
- >>> artifact.open()
2018
- AnnDataAccessor object with n_obs × n_vars = 70 × 765
2019
- constructed for the AnnData object pbmc68k.h5ad
2020
- ...
2055
+ artifact = ln.Artifact.get(key="lndb-storage/pbmc68k.h5ad")
2056
+ artifact.open()
2057
+ #> AnnDataAccessor object with n_obs × n_vars = 70 × 765
2058
+ #> constructed for the AnnData object pbmc68k.h5ad
2059
+ #> ...
2021
2060
  """
2022
2061
  if self._overwrite_versions and not self.is_latest:
2023
2062
  raise ValueError(INCONSISTENT_STATE_MSG)
@@ -2118,11 +2157,18 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2118
2157
  _track_run_input(self, is_run_input)
2119
2158
  return access
2120
2159
 
2121
- def load(self, is_run_input: bool | None = None, **kwargs) -> Any:
2160
+ def load(
2161
+ self, *, is_run_input: bool | None = None, mute: bool = False, **kwargs
2162
+ ) -> Any:
2122
2163
  """Cache and load into memory.
2123
2164
 
2124
2165
  See all :mod:`~lamindb.core.loaders`.
2125
2166
 
2167
+ Args:
2168
+ is_run_input: Whether to track this artifact as run input.
2169
+ mute: Silence logging of caching progress.
2170
+ **kwargs: Keyword arguments for the loader.
2171
+
2126
2172
  Examples:
2127
2173
 
2128
2174
  Load a `DataFrame`-like artifact:
@@ -2156,7 +2202,9 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2156
2202
  filepath, cache_key = filepath_cache_key_from_artifact(
2157
2203
  self, using_key=settings._using_key
2158
2204
  )
2159
- cache_path = _synchronize_cleanup_on_error(filepath, cache_key=cache_key)
2205
+ cache_path = _synchronize_cleanup_on_error(
2206
+ filepath, cache_key=cache_key, print_progress=not mute
2207
+ )
2160
2208
  try:
2161
2209
  # cache_path is local so doesn't trigger any sync in load_to_memory
2162
2210
  access_memory = load_to_memory(cache_path, **kwargs)
@@ -2177,26 +2225,33 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2177
2225
  cache_path.unlink(missing_ok=True)
2178
2226
  # download again and try to load into memory
2179
2227
  cache_path = _synchronize_cleanup_on_error(
2180
- filepath, cache_key=cache_key
2228
+ filepath, cache_key=cache_key, print_progress=not mute
2181
2229
  )
2182
2230
  access_memory = load_to_memory(cache_path, **kwargs)
2183
2231
  # only call if load is successfull
2184
2232
  _track_run_input(self, is_run_input)
2185
2233
  return access_memory
2186
2234
 
2187
- def cache(self, is_run_input: bool | None = None) -> Path:
2235
+ @doc_args(DEBUG_KWARGS_DOC)
2236
+ def cache(
2237
+ self, *, is_run_input: bool | None = None, mute: bool = False, **kwargs
2238
+ ) -> Path:
2188
2239
  """Download cloud artifact to local cache.
2189
2240
 
2190
2241
  Follows synching logic: only caches an artifact if it's outdated in the local cache.
2191
2242
 
2192
2243
  Returns a path to a locally cached on-disk object (say a `.jpg` file).
2193
2244
 
2194
- Examples:
2245
+ Args:
2246
+ mute: Silence logging of caching progress.
2247
+ is_run_input: Whether to track this artifact as run input.
2248
+ {}
2195
2249
 
2196
- Sync file from cloud and return the local path of the cache:
2250
+ Example::
2197
2251
 
2198
- >>> artifact.cache()
2199
- PosixPath('/home/runner/work/Caches/lamindb/lamindb-ci/lndb-storage/pbmc68k.h5ad')
2252
+ # Sync file from cloud and return the local path of the cache
2253
+ artifact.cache()
2254
+ #> PosixPath('/home/runner/work/Caches/lamindb/lamindb-ci/lndb-storage/pbmc68k.h5ad')
2200
2255
  """
2201
2256
  from lamindb import settings
2202
2257
 
@@ -2206,7 +2261,11 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2206
2261
  filepath, cache_key = filepath_cache_key_from_artifact(
2207
2262
  self, using_key=settings._using_key
2208
2263
  )
2209
- cache_path = _synchronize_cleanup_on_error(filepath, cache_key=cache_key)
2264
+ if mute:
2265
+ kwargs["print_progress"] = False
2266
+ cache_path = _synchronize_cleanup_on_error(
2267
+ filepath, cache_key=cache_key, **kwargs
2268
+ )
2210
2269
  # only call if sync is successfull
2211
2270
  _track_run_input(self, is_run_input)
2212
2271
  return cache_path
@@ -2231,18 +2290,19 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2231
2290
  permanent: Permanently delete the artifact (skip trash).
2232
2291
  storage: Indicate whether you want to delete the artifact in storage.
2233
2292
 
2234
- Examples:
2293
+ Example::
2235
2294
 
2236
- For an `Artifact` object `artifact`, call:
2295
+ import lamindb as ln
2237
2296
 
2238
- >>> artifact = ln.Artifact.filter(key="some.csv").one()
2239
- >>> artifact.delete() # delete a single file artifact
2297
+ # For an `Artifact` object `artifact`, call:
2298
+ artifact = ln.Artifact.get(key="some.csv")
2299
+ artifact.delete() # delete a single file artifact
2240
2300
 
2241
- >>> artifact = ln.Artifact.filter(key="some.tiledbsoma". is_latest=False).first()
2242
- >>> artiact.delete() # delete an old version, the data will not be deleted
2301
+ artifact = ln.Artifact.filter(key="some.tiledbsoma". is_latest=False).first()
2302
+ artiact.delete() # delete an old version, the data will not be deleted
2243
2303
 
2244
- >>> artifact = ln.Artifact.filter(key="some.tiledbsoma". is_latest=True).one()
2245
- >>> artiact.delete() # delete all versions, the data will be deleted or prompted for deletion.
2304
+ artifact = ln.Artifact.get(key="some.tiledbsoma". is_latest=True)
2305
+ artiact.delete() # delete all versions, the data will be deleted or prompted for deletion.
2246
2306
  """
2247
2307
  # this first check means an invalid delete fails fast rather than cascading through
2248
2308
  # database and storage permission errors
@@ -2330,15 +2390,19 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2330
2390
  if delete_msg != "did-not-delete":
2331
2391
  logger.success(f"deleted {colors.yellow(f'{path}')}")
2332
2392
 
2393
+ @doc_args(DEBUG_KWARGS_DOC)
2333
2394
  def save(self, upload: bool | None = None, **kwargs) -> Artifact:
2334
2395
  """Save to database & storage.
2335
2396
 
2336
2397
  Args:
2337
2398
  upload: Trigger upload to cloud storage in instances with hybrid storage mode.
2399
+ {}
2338
2400
 
2339
- Examples:
2340
- >>> artifact = ln.Artifact("./myfile.csv", description="myfile")
2341
- >>> artifact.save()
2401
+ Example::
2402
+
2403
+ import lamindb as ln
2404
+
2405
+ artifact = ln.Artifact("./myfile.csv", key="myfile.parquet").save()
2342
2406
  """
2343
2407
  state_was_adding = self._state.adding
2344
2408
  print_progress = kwargs.pop("print_progress", True)
@@ -2407,8 +2471,9 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2407
2471
  def restore(self) -> None:
2408
2472
  """Restore from trash.
2409
2473
 
2410
- Examples:
2411
- >>> artifact.restore()
2474
+ Example::
2475
+
2476
+ artifact.restore()
2412
2477
  """
2413
2478
  self._branch_code = 1
2414
2479
  self.save()
@@ -2416,8 +2481,9 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2416
2481
  def describe(self) -> None:
2417
2482
  """Describe relations of record.
2418
2483
 
2419
- Examples:
2420
- >>> artifact.describe()
2484
+ Example::
2485
+
2486
+ artifact.describe()
2421
2487
  """
2422
2488
  return describe_artifact_collection(self)
2423
2489
 
@@ -2427,11 +2493,12 @@ class Artifact(Record, IsVersioned, TracksRun, TracksUpdates):
2427
2493
 
2428
2494
  # can't really just call .cache in .load because of double tracking
2429
2495
  def _synchronize_cleanup_on_error(
2430
- filepath: UPath, cache_key: str | None = None
2496
+ filepath: UPath, cache_key: str | None = None, **kwargs
2431
2497
  ) -> UPath:
2432
2498
  try:
2499
+ print_progress = kwargs.pop("print_progress", True)
2433
2500
  cache_path = setup_settings.paths.cloud_to_local(
2434
- filepath, cache_key=cache_key, print_progress=True
2501
+ filepath, cache_key=cache_key, print_progress=print_progress, **kwargs
2435
2502
  )
2436
2503
  except Exception as e:
2437
2504
  if not isinstance(filepath, LocalPathClasses):
@@ -2477,8 +2544,9 @@ class ArtifactParamValue(BasicRecord, LinkORM, TracksRun):
2477
2544
 
2478
2545
 
2479
2546
  def _track_run_input(
2480
- data: Artifact
2481
- | Iterable[Artifact], # can also be Collection | Iterable[Collection]
2547
+ data: (
2548
+ Artifact | Iterable[Artifact]
2549
+ ), # can also be Collection | Iterable[Collection]
2482
2550
  is_run_input: bool | Run | None = None,
2483
2551
  run: Run | None = None,
2484
2552
  ):
@@ -2542,10 +2610,10 @@ def _track_run_input(
2542
2610
  if run is None:
2543
2611
  if settings.track_run_inputs:
2544
2612
  # here we check that this is not a read-only connection
2545
- # normally for our connection strings the read-only role name has _read in it
2613
+ # normally for our connection strings the read-only role name has "read" in it
2546
2614
  # not absolutely safe but the worst case is that the warning is not shown
2547
2615
  instance = setup_settings.instance
2548
- if instance.dialect != "postgresql" or "_read" not in instance.db:
2616
+ if instance.dialect != "postgresql" or "read" not in instance.db:
2549
2617
  logger.warning(WARNING_NO_INPUT)
2550
2618
  # assume we have a run record
2551
2619
  else: