pastastore 1.11.0__tar.gz → 1.12.1__tar.gz

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.
Files changed (37) hide show
  1. {pastastore-1.11.0 → pastastore-1.12.1}/PKG-INFO +4 -4
  2. {pastastore-1.11.0 → pastastore-1.12.1}/docs/conf.py +10 -97
  3. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/base.py +171 -37
  4. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/connectors.py +151 -25
  5. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/datasets.py +0 -4
  6. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/store.py +17 -5
  7. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/styling.py +4 -2
  8. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/util.py +3 -0
  9. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/validator.py +53 -3
  10. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/version.py +2 -2
  11. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore.egg-info/PKG-INFO +4 -4
  12. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore.egg-info/SOURCES.txt +2 -1
  13. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore.egg-info/requires.txt +2 -2
  14. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore.egg-info/top_level.txt +0 -1
  15. {pastastore-1.11.0 → pastastore-1.12.1}/pyproject.toml +3 -4
  16. pastastore-1.12.1/tests/conftest.py +262 -0
  17. {pastastore-1.11.0 → pastastore-1.12.1}/tests/test_003_pastastore.py +3 -1
  18. pastastore-1.12.1/tests/test_009_parallel.py +393 -0
  19. pastastore-1.11.0/tests/conftest.py +0 -160
  20. {pastastore-1.11.0 → pastastore-1.12.1}/LICENSE +0 -0
  21. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/__init__.py +0 -0
  22. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/extensions/__init__.py +0 -0
  23. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/extensions/accessor.py +0 -0
  24. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/extensions/hpd.py +0 -0
  25. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/plotting.py +0 -0
  26. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/typing.py +0 -0
  27. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore/yaml_interface.py +0 -0
  28. {pastastore-1.11.0 → pastastore-1.12.1}/pastastore.egg-info/dependency_links.txt +0 -0
  29. {pastastore-1.11.0 → pastastore-1.12.1}/readme.md +0 -0
  30. {pastastore-1.11.0 → pastastore-1.12.1}/setup.cfg +0 -0
  31. {pastastore-1.11.0 → pastastore-1.12.1}/tests/test_001_import.py +0 -0
  32. {pastastore-1.11.0 → pastastore-1.12.1}/tests/test_002_connectors.py +0 -0
  33. {pastastore-1.11.0 → pastastore-1.12.1}/tests/test_004_yaml.py +0 -0
  34. {pastastore-1.11.0 → pastastore-1.12.1}/tests/test_005_maps_plots.py +0 -0
  35. {pastastore-1.11.0 → pastastore-1.12.1}/tests/test_006_benchmark.py +0 -0
  36. {pastastore-1.11.0 → pastastore-1.12.1}/tests/test_007_hpdextension.py +0 -0
  37. {pastastore-1.11.0 → pastastore-1.12.1}/tests/test_008_stressmodels.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pastastore
3
- Version: 1.11.0
3
+ Version: 1.12.1
4
4
  Summary: Tools for managing Pastas time series models.
5
5
  Author: D.A. Brakenhoff
6
6
  Maintainer-email: "D.A. Brakenhoff" <d.brakenhoff@artesia-water.nl>, "R. Calje" <r.calje@artesia-water.nl>, "M.A. Vonk" <m.vonk@artesia-water.nl>
@@ -46,13 +46,14 @@ Classifier: Programming Language :: Python :: 3.12
46
46
  Classifier: Programming Language :: Python :: 3.13
47
47
  Classifier: Programming Language :: Python :: 3 :: Only
48
48
  Classifier: Topic :: Scientific/Engineering :: Hydrology
49
- Requires-Python: >=3.10
49
+ Requires-Python: >=3.11
50
50
  Description-Content-Type: text/markdown
51
51
  License-File: LICENSE
52
52
  Requires-Dist: pastas>=0.13
53
53
  Requires-Dist: tqdm>=4.36
54
54
  Requires-Dist: pyyaml
55
55
  Requires-Dist: colorama
56
+ Requires-Dist: pandas<3.0
56
57
  Provides-Extra: full
57
58
  Requires-Dist: pastastore[arcticdb,optional]; extra == "full"
58
59
  Requires-Dist: hydropandas; extra == "full"
@@ -79,8 +80,7 @@ Requires-Dist: pastastore[optional]; extra == "docs"
79
80
  Requires-Dist: sphinx_rtd_theme; extra == "docs"
80
81
  Requires-Dist: Ipython; extra == "docs"
81
82
  Requires-Dist: ipykernel; extra == "docs"
82
- Requires-Dist: nbsphinx; extra == "docs"
83
- Requires-Dist: nbsphinx_link; extra == "docs"
83
+ Requires-Dist: myst_nb; extra == "docs"
84
84
  Provides-Extra: test
85
85
  Requires-Dist: pastastore[arcticdb,optional,pytest]; extra == "test"
86
86
  Requires-Dist: hydropandas[full]; extra == "test"
@@ -53,8 +53,7 @@ extensions = [
53
53
  "sphinx.ext.viewcode",
54
54
  "IPython.sphinxext.ipython_console_highlighting", # lowercase didn't work
55
55
  "sphinx.ext.autosectionlabel",
56
- "nbsphinx",
57
- "nbsphinx_link",
56
+ "myst_nb",
58
57
  ]
59
58
 
60
59
  # Add any paths that contain templates here, relative to this directory.
@@ -114,102 +113,16 @@ html_theme_options = {
114
113
  # Add any paths that contain custom static files (such as style sheets) here,
115
114
  # relative to this directory. They are copied after the builtin static files,
116
115
  # so a file named "default.css" will overwrite the builtin "default.css".
117
- html_static_path = ["_static"]
116
+ # html_static_path = ["_static"]
118
117
 
119
- # Custom sidebar templates, must be a dictionary that maps document names
120
- # to template names.
121
- #
122
- # The default sidebars (for documents that don't match any pattern) are
123
- # defined by theme itself. Builtin themes are using these templates by
124
- # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
125
- # 'searchbox.html']``.
126
- #
127
- # html_sidebars = {}
128
-
129
- # -- Options for HTMLHelp output ---------------------------------------------
130
-
131
- # Output file base name for HTML help builder.
132
- htmlhelp_basename = "pastastoredoc"
133
-
134
-
135
- # -- Options for LaTeX output ------------------------------------------------
136
-
137
- latex_elements = {
138
- # The paper size ('letterpaper' or 'a4paper').
139
- #
140
- # 'papersize': 'letterpaper',
141
- # The font size ('10pt', '11pt' or '12pt').
142
- #
143
- # 'pointsize': '10pt',
144
- # Additional stuff for the LaTeX preamble.
145
- #
146
- "preamble": r"""\makeatletter
147
- \def\UTFviii@defined#1{%
148
- \ifx#1\relax
149
- -%
150
- \else\expandafter
151
- #1%
152
- \fi
153
- }
154
-
155
- \makeatother""",
156
- # Latex figure (float) alignment
157
- #
158
- # 'figure_align': 'htbp',
159
- }
160
-
161
- # Grouping the document tree into LaTeX files. List of tuples
162
- # (source start file, target name, title,
163
- # author, documentclass [howto, manual, or own class]).
164
- latex_documents = [
165
- (
166
- master_doc,
167
- "pastastore.tex",
168
- "pastastore Documentation",
169
- "D.A. Brakenhoff",
170
- "manual",
171
- ),
172
- ]
173
-
174
-
175
- # -- Options for manual page output ------------------------------------------
176
-
177
- # One entry per manual page. List of tuples
178
- # (source start file, name, description, authors, manual section).
179
- man_pages = [(master_doc, "pastastore", "pastastore Documentation", [author], 1)]
180
118
 
119
+ # -- myst_nb options ------------------------------------------------------------------
181
120
 
182
- # -- Options for Texinfo output ----------------------------------------------
183
-
184
- # Grouping the document tree into Texinfo files. List of tuples
185
- # (source start file, target name, title, author,
186
- # dir menu entry, description, category)
187
- texinfo_documents = [
188
- (
189
- master_doc,
190
- "pastastore",
191
- "pastastore Documentation",
192
- author,
193
- "pastastore",
194
- "Tools for managing time series and Pastas models",
195
- "Miscellaneous",
196
- ),
197
- ]
198
-
199
-
200
- # -- Options for Epub output -------------------------------------------------
201
-
202
- # Bibliographic Dublin Core info.
203
- epub_title = project
204
-
205
- # The unique identifier of the text. This can be a ISBN number
206
- # or the project homepage.
207
- #
208
- # epub_identifier = ''
209
-
210
- # A unique identification for the text.
211
- #
212
- # epub_uid = ''
121
+ nb_execution_allow_errors = True # Allow errors in notebooks, to see the error online
122
+ nb_execution_mode = "off"
123
+ nb_merge_streams = True
213
124
 
214
- # A list of files that should not be packed into the epub file.
215
- epub_exclude_files = ["search.html"]
125
+ myst_enable_extensions = ["dollarmath", "amsmath"]
126
+ myst_dmath_double_inline = True
127
+ nb_render_markdown_format = "myst" # Enable MyST markdown parsing in notebooks
128
+ nb_render_text_lexer = "myst-ansi" # Better rendering of ANSI output
@@ -164,7 +164,7 @@ class ConnectorUtil:
164
164
  for stress in ts["stress"]:
165
165
  if "series" not in stress:
166
166
  name = str(stress["name"])
167
- if name in self.stresses.index:
167
+ if self._item_exists("stresses", name):
168
168
  stress["series"] = self.get_stresses(name).squeeze()
169
169
  # update tmin/tmax from time series
170
170
  if update_ts_settings:
@@ -179,7 +179,7 @@ class ConnectorUtil:
179
179
  for stress in ts["stress"] if PASFILE_LEQ_022 else [ts["stress"]]:
180
180
  if "series" not in stress:
181
181
  name = str(stress["name"])
182
- if name in self.stresses.index:
182
+ if self._item_exists("stresses", name):
183
183
  stress["series"] = self.get_stresses(name).squeeze()
184
184
  # update tmin/tmax from time series
185
185
  if update_ts_settings:
@@ -195,7 +195,7 @@ class ConnectorUtil:
195
195
  for stress in [ts["prec"], ts["evap"]]:
196
196
  if "series" not in stress:
197
197
  name = str(stress["name"])
198
- if name in self.stresses.index:
198
+ if self._item_exists("stresses", name):
199
199
  stress["series"] = self.get_stresses(name).squeeze()
200
200
  # update tmin/tmax from time series
201
201
  if update_ts_settings:
@@ -250,6 +250,29 @@ class BaseConnector(ABC, ConnectorUtil):
250
250
  _conn_type: Optional[str] = None
251
251
  _validator: Optional[Validator] = None
252
252
  name = None
253
+ _added_models = [] # internal list of added models used for updating links
254
+
255
+ def __getstate__(self):
256
+ """Replace Manager proxies with simple values for pickling.
257
+
258
+ Manager proxies cannot be pickled, so we convert them to simple booleans.
259
+ This allows connectors to be pickled for multiprocessing.
260
+ """
261
+ state = self.__dict__.copy()
262
+ # Replace unpicklable Manager proxies with their values
263
+ state["_oseries_links_need_update"] = self._oseries_links_need_update.value
264
+ state["_stresses_links_need_update"] = self._stresses_links_need_update.value
265
+ return state
266
+
267
+ def __setstate__(self, state):
268
+ """Replace Manager proxies with simple booleans after unpickling.
269
+
270
+ After unpickling, use simple booleans instead of recreating Manager.
271
+ This works because worker processes don't need shared memory - they
272
+ work on independent copies of the connector.
273
+ """
274
+ self.__dict__.update(state)
275
+ # Flags are already simple booleans from __getstate__
253
276
 
254
277
  def __repr__(self):
255
278
  """Representation string of the object."""
@@ -390,6 +413,10 @@ class BaseConnector(ABC, ConnectorUtil):
390
413
  def _list_symbols(self, libname: AllLibs) -> List[str]:
391
414
  """Return list of symbol names in library."""
392
415
 
416
+ @abstractmethod
417
+ def _item_exists(self, libname: AllLibs, name: str) -> bool:
418
+ """Return True if item present in library, else False."""
419
+
393
420
  @property
394
421
  def oseries_names(self):
395
422
  """List of oseries names.
@@ -420,6 +447,7 @@ class BaseConnector(ABC, ConnectorUtil):
420
447
 
421
448
  Property must be overridden by subclass.
422
449
  """
450
+ self._trigger_links_update_if_needed()
423
451
  return self._list_symbols("oseries_models")
424
452
 
425
453
  @property
@@ -428,6 +456,7 @@ class BaseConnector(ABC, ConnectorUtil):
428
456
 
429
457
  Property must be overridden by subclass.
430
458
  """
459
+ self._trigger_links_update_if_needed()
431
460
  return self._list_symbols("stresses_models")
432
461
 
433
462
  @abstractmethod
@@ -552,6 +581,7 @@ class BaseConnector(ABC, ConnectorUtil):
552
581
  dictionary with oseries names as keys and list of model names as
553
582
  values
554
583
  """
584
+ self._trigger_links_update_if_needed()
555
585
  d = {}
556
586
  for onam in self.oseries_with_models:
557
587
  d[onam] = self._get_item("oseries_models", onam)
@@ -568,6 +598,7 @@ class BaseConnector(ABC, ConnectorUtil):
568
598
  dictionary with stress names as keys and list of model names as
569
599
  values
570
600
  """
601
+ self._trigger_links_update_if_needed()
571
602
  d = {}
572
603
  for stress_name in self.stresses_with_models:
573
604
  d[stress_name] = self._get_item("stresses_models", stress_name)
@@ -608,6 +639,8 @@ class BaseConnector(ABC, ConnectorUtil):
608
639
  """
609
640
  if not isinstance(name, str):
610
641
  name = str(name)
642
+ if metadata:
643
+ self.validator.validate_metadata(metadata)
611
644
  self.validator.validate_input_series(series)
612
645
  series = self.validator.set_series_name(series, name)
613
646
  if self.validator.pastas_validation_status(validate):
@@ -631,8 +664,8 @@ class BaseConnector(ABC, ConnectorUtil):
631
664
  if name not in in_store or overwrite:
632
665
  self._add_item(libname, series, name, metadata=metadata)
633
666
  self._clear_cache(libname)
634
- elif (libname == "oseries" and name in self.oseries_models) or (
635
- libname == "stresses" and name in self.stresses_models
667
+ elif (libname == "oseries" and self._item_exists("oseries_models", name)) or (
668
+ libname == "stresses" and self._item_exists("stresses_models", name)
636
669
  ):
637
670
  raise SeriesUsedByModel(
638
671
  f"Time series with name '{name}' is used by a model! "
@@ -881,7 +914,7 @@ class BaseConnector(ABC, ConnectorUtil):
881
914
  raise TypeError("Expected pastas.Model or dict!")
882
915
  if not isinstance(name, str):
883
916
  name = str(name)
884
- if name not in self.model_names or overwrite:
917
+ if not self._item_exists("models", name) or overwrite:
885
918
  # check if stressmodels supported
886
919
  self.validator.check_stressmodels_supported(ml)
887
920
  # check oseries and stresses names and if they exist in store
@@ -891,8 +924,20 @@ class BaseConnector(ABC, ConnectorUtil):
891
924
  # write model to store
892
925
  self._add_item("models", mldict, name, metadata=metadata)
893
926
  self._clear_cache("_modelnames_cache")
894
- self._add_oseries_model_links(str(mldict["oseries"]["name"]), name)
895
- self._add_stresses_model_links(self._get_model_stress_names(mldict), name)
927
+ # avoid updating links so parallel operations do not simultaneously
928
+ # access the same object. Indicate that these links need updating and
929
+ # clear existing caches. Handle both Manager proxies and booleans
930
+ if hasattr(self._oseries_links_need_update, "value"):
931
+ self._oseries_links_need_update.value = True
932
+ self._stresses_links_need_update.value = True
933
+ # this won't update main instance in parallel
934
+ self._added_models.append(name)
935
+ else:
936
+ self._oseries_links_need_update = True
937
+ self._stresses_links_need_update = True
938
+ self._added_models.append(name)
939
+ self._clear_cache("oseries_models")
940
+ self._clear_cache("stresses_models")
896
941
  else:
897
942
  raise ItemInLibraryException(
898
943
  f"Model with name '{name}' already in 'models' library! "
@@ -1072,8 +1117,12 @@ class BaseConnector(ABC, ConnectorUtil):
1072
1117
  mldict = self.get_models(n, return_dict=True)
1073
1118
  oname = mldict["oseries"]["name"]
1074
1119
  self._del_item("models", n)
1075
- self._del_oseries_model_link(oname, n)
1076
- self._del_stress_model_link(self._get_model_stress_names(mldict), n)
1120
+ # delete reference to added model if present
1121
+ if n in self._added_models:
1122
+ self._added_models.remove(n)
1123
+ else:
1124
+ self._del_oseries_model_link(oname, n)
1125
+ self._del_stress_model_link(self._get_model_stress_names(mldict), n)
1077
1126
  self._clear_cache("_modelnames_cache")
1078
1127
  if verbose:
1079
1128
  logger.info("Deleted %d model(s) from database.", len(names))
@@ -1125,7 +1174,7 @@ class BaseConnector(ABC, ConnectorUtil):
1125
1174
  )
1126
1175
  self.del_models(modelnames, verbose=verbose)
1127
1176
  if verbose:
1128
- logger.info("Deleted %d models(s) from database.", len(modelnames))
1177
+ logger.info("Deleted %d model(s) from database.", len(modelnames))
1129
1178
 
1130
1179
  def del_stress(
1131
1180
  self,
@@ -1160,7 +1209,7 @@ class BaseConnector(ABC, ConnectorUtil):
1160
1209
  )
1161
1210
  self.del_models(modelnames, verbose=verbose)
1162
1211
  if verbose:
1163
- logger.info("Deleted %d models(s) from database.", len(modelnames))
1212
+ logger.info("Deleted %d model(s) from database.", len(modelnames))
1164
1213
 
1165
1214
  def _get_series(
1166
1215
  self,
@@ -1485,8 +1534,8 @@ class BaseConnector(ABC, ConnectorUtil):
1485
1534
  return
1486
1535
 
1487
1536
  if libname == "models":
1488
- # also delete linked modelnames linked to oseries
1489
- libs = ["models", "oseries_models"]
1537
+ # also delete linked modelnames linked to oseries and stresses
1538
+ libs = ["models", "oseries_models", "stresses_models"]
1490
1539
  else:
1491
1540
  libs = [libname]
1492
1541
 
@@ -1583,7 +1632,10 @@ class BaseConnector(ABC, ConnectorUtil):
1583
1632
  yield self.get_models(mlnam, return_dict=return_dict, progressbar=False)
1584
1633
 
1585
1634
  def _add_oseries_model_links(
1586
- self, oseries_name: str, model_names: Union[str, List[str]]
1635
+ self,
1636
+ oseries_name: str,
1637
+ model_names: Union[str, List[str]],
1638
+ _clear_cache: bool = True,
1587
1639
  ):
1588
1640
  """Add model name to stored list of models per oseries.
1589
1641
 
@@ -1594,9 +1646,12 @@ class BaseConnector(ABC, ConnectorUtil):
1594
1646
  model_names : Union[str, List[str]]
1595
1647
  model name or list of model names for an oseries with name
1596
1648
  oseries_name.
1649
+ _clear_cache : bool, optional
1650
+ whether to clear the cache after adding, by default True.
1651
+ Set to False during bulk operations to improve performance.
1597
1652
  """
1598
1653
  # get stored list of model names
1599
- if str(oseries_name) in self.oseries_with_models:
1654
+ if self._item_exists("oseries_models", oseries_name):
1600
1655
  modellist = self._get_item("oseries_models", oseries_name)
1601
1656
  else:
1602
1657
  # else empty list
@@ -1610,9 +1665,12 @@ class BaseConnector(ABC, ConnectorUtil):
1610
1665
  if iml not in modellist:
1611
1666
  modellist.append(iml)
1612
1667
  self._add_item("oseries_models", modellist, oseries_name)
1613
- self._clear_cache("oseries_models")
1668
+ if _clear_cache:
1669
+ self._clear_cache("oseries_models")
1614
1670
 
1615
- def _add_stresses_model_links(self, stress_names, model_names):
1671
+ def _add_stresses_model_links(
1672
+ self, stress_names, model_names, _clear_cache: bool = True
1673
+ ):
1616
1674
  """Add model name to stored list of models per stress.
1617
1675
 
1618
1676
  Parameters
@@ -1621,13 +1679,16 @@ class BaseConnector(ABC, ConnectorUtil):
1621
1679
  names of stresses
1622
1680
  model_names : Union[str, List[str]]
1623
1681
  model name or list of model names for a stress with name
1682
+ _clear_cache : bool, optional
1683
+ whether to clear the cache after adding, by default True.
1684
+ Set to False during bulk operations to improve performance.
1624
1685
  """
1625
1686
  # if one model name, make list for loop
1626
1687
  if isinstance(model_names, str):
1627
1688
  model_names = [model_names]
1628
1689
  for snam in stress_names:
1629
1690
  # get stored list of model names
1630
- if str(snam) in self.stresses_with_models:
1691
+ if self._item_exists("stresses_models", str(snam)):
1631
1692
  modellist = self._get_item("stresses_models", snam)
1632
1693
  else:
1633
1694
  # else empty list
@@ -1638,7 +1699,8 @@ class BaseConnector(ABC, ConnectorUtil):
1638
1699
  if iml not in modellist:
1639
1700
  modellist.append(iml)
1640
1701
  self._add_item("stresses_models", modellist, snam)
1641
- self._clear_cache("stresses_models")
1702
+ if _clear_cache:
1703
+ self._clear_cache("stresses_models")
1642
1704
 
1643
1705
  def _del_oseries_model_link(self, onam, mlnam):
1644
1706
  """Delete model name from stored list of models per oseries.
@@ -1677,31 +1739,102 @@ class BaseConnector(ABC, ConnectorUtil):
1677
1739
  self._add_item("stresses_models", modellist, stress_name)
1678
1740
  self._clear_cache("stresses_models")
1679
1741
 
1680
- def _update_time_series_model_links(self):
1742
+ def _update_time_series_model_links(
1743
+ self,
1744
+ libraries: list[str] = None,
1745
+ modelnames: Optional[List[str]] = None,
1746
+ recompute: bool = True,
1747
+ progressbar: bool = False,
1748
+ ):
1681
1749
  """Add all model names to reverse lookup time series dictionaries.
1682
1750
 
1683
1751
  Used for old PastaStore versions, where relationship between time series and
1684
1752
  models was not stored. If there are any models in the database and if the
1685
1753
  oseries_models or stresses_models libraries are empty, loop through all models
1686
1754
  to determine which time series are used in each model.
1755
+
1756
+ Parameters
1757
+ ----------
1758
+ libraries : list of str, optional
1759
+ list of time series libraries to update model links for,
1760
+ by default None which will update both 'oseries' and 'stresses'
1761
+ modelnames : Optional[List[str]], optional
1762
+ list of model names to update links for, by default None
1763
+ recompute : bool, optional
1764
+ Indicate operation is an update/recompute of existing links,
1765
+ by default False
1766
+ progressbar : bool, optional
1767
+ show progressbar, by default True
1687
1768
  """
1688
1769
  # get oseries_models and stresses_models libraries,
1689
1770
  # if empty add all time series -> model links.
1771
+ if libraries is None:
1772
+ libraries = ["oseries", "stresses"]
1690
1773
  if self.n_models > 0:
1691
- if len(self.oseries_models) == 0 or len(self.stresses_models) == 0:
1692
- links = self._get_time_series_model_links()
1693
- for k in ["oseries", "stresses"]:
1694
- for name, model_links in tqdm(
1695
- links[k].items(),
1696
- desc=f"Store models per {k}",
1697
- total=len(links[k]),
1698
- ):
1699
- if k == "oseries":
1700
- self._add_oseries_model_links(name, model_links)
1701
- elif k == "stresses":
1702
- self._add_stresses_model_links(name, model_links)
1703
-
1704
- def _get_time_series_model_links(self):
1774
+ logger.debug("Updating time series -> models links in store.")
1775
+ links = self._get_time_series_model_links(
1776
+ modelnames=modelnames, recompute=recompute, progressbar=progressbar
1777
+ )
1778
+ for k in libraries:
1779
+ if recompute:
1780
+ desc = f"Updating {k}-models links"
1781
+ else:
1782
+ desc = f"Storing {k}-models links"
1783
+ for name, model_links in tqdm(
1784
+ links[k].items(),
1785
+ desc=desc,
1786
+ total=len(links[k]),
1787
+ disable=not progressbar,
1788
+ ):
1789
+ if k == "oseries":
1790
+ self._add_oseries_model_links(
1791
+ name, model_links, _clear_cache=False
1792
+ )
1793
+ elif k == "stresses":
1794
+ self._add_stresses_model_links(
1795
+ [name], model_links, _clear_cache=False
1796
+ )
1797
+ # Clear caches after all updates are complete
1798
+ if "oseries" in libraries:
1799
+ self._clear_cache("oseries_models")
1800
+ if "stresses" in libraries:
1801
+ self._clear_cache("stresses_models")
1802
+
1803
+ def _trigger_links_update_if_needed(
1804
+ self, modelnames: Optional[list[str]] = None, progressbar: bool = False
1805
+ ):
1806
+ # Check if time series-> model links need updating
1807
+ # Handle both Manager proxies (main) and booleans (worker after pickle)
1808
+ needs_update = (
1809
+ self._oseries_links_need_update.value
1810
+ if hasattr(self._oseries_links_need_update, "value")
1811
+ else self._oseries_links_need_update
1812
+ )
1813
+ if needs_update:
1814
+ self._clear_cache("_modelnames_cache")
1815
+ # Set BOTH flags to False BEFORE updating to prevent recursion
1816
+ # (update always recomputes both oseries and stresses links)
1817
+ if hasattr(self._oseries_links_need_update, "value"):
1818
+ self._oseries_links_need_update.value = False
1819
+ self._stresses_links_need_update.value = False
1820
+ else:
1821
+ self._oseries_links_need_update = False
1822
+ self._stresses_links_need_update = False
1823
+ modelnames = self._added_models
1824
+ if modelnames is None or len(modelnames) > 0:
1825
+ self._update_time_series_model_links(
1826
+ modelnames=modelnames, recompute=True, progressbar=progressbar
1827
+ )
1828
+ self._added_models = [] # reset list of added models
1829
+ else:
1830
+ self._added_models = [] # reset list of added models
1831
+
1832
+ def _get_time_series_model_links(
1833
+ self,
1834
+ modelnames: Optional[list[str]] = None,
1835
+ recompute: bool = False,
1836
+ progressbar: bool = True,
1837
+ ) -> dict:
1705
1838
  """Get model names per oseries and stresses time series in a dictionary.
1706
1839
 
1707
1840
  Returns
@@ -1714,9 +1847,10 @@ class BaseConnector(ABC, ConnectorUtil):
1714
1847
  oseries_links = {}
1715
1848
  stresses_links = {}
1716
1849
  for mldict in tqdm(
1717
- self.iter_models(return_dict=True),
1850
+ self.iter_models(modelnames=modelnames, return_dict=True),
1718
1851
  total=self.n_models,
1719
- desc="Get models per time series",
1852
+ desc=f"{'Recompute' if recompute else 'Get'} models per time series",
1853
+ disable=not progressbar,
1720
1854
  ):
1721
1855
  mlnam = mldict["name"]
1722
1856
  # oseries