pastastore 1.10.2__py3-none-any.whl → 1.12.0__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.
@@ -6,6 +6,7 @@ import os
6
6
  import tempfile
7
7
  from contextlib import contextmanager
8
8
  from copy import deepcopy
9
+ from pathlib import Path
9
10
  from typing import Any, Dict, List, Optional, Union
10
11
 
11
12
  import numpy as np
@@ -13,8 +14,6 @@ import pandas as pd
13
14
  import pastas as ps
14
15
  import yaml
15
16
 
16
- from pastastore.version import PASTAS_LEQ_022
17
-
18
17
  logger = logging.getLogger(__name__)
19
18
 
20
19
 
@@ -38,9 +37,9 @@ def _convert_dict_dtypes_for_yaml(d: Dict[str, Any]):
38
37
  elif isinstance(v, datetime.datetime):
39
38
  d[k] = pd.to_datetime(v).strftime("%Y-%m-%d %H:%M:%S")
40
39
  elif isinstance(v, pd.Timedelta):
41
- d[k] = v.to_timedelta64().__str__()
40
+ d[k] = str(v.to_timedelta64())
42
41
  elif isinstance(v, datetime.timedelta):
43
- d[k] = pd.to_timedelta(v).to_timedelta64().__str__()
42
+ d[k] = str(pd.to_timedelta(v).to_timedelta64())
44
43
  elif isinstance(v, np.int64):
45
44
  d[k] = int(v)
46
45
  elif isinstance(v, np.float64):
@@ -109,7 +108,7 @@ def reduce_to_minimal_dict(d, keys=None):
109
108
  "stress",
110
109
  "prec",
111
110
  "evap",
112
- "stressmodel" if PASTAS_LEQ_022 else "class",
111
+ "class",
113
112
  ]
114
113
 
115
114
  # also keep stressmodels by adding names to keys list
@@ -133,7 +132,7 @@ def temporary_yaml_from_str(yaml):
133
132
  temp.write(yaml.encode("utf-8"))
134
133
  temp.close()
135
134
  try:
136
- yield temp.name
135
+ yield Path(temp.name)
137
136
  finally:
138
137
  os.unlink(temp.name)
139
138
 
@@ -230,7 +229,7 @@ class PastastoreYAML:
230
229
  else:
231
230
  kind = "prec"
232
231
  pnam = self.pstore.get_nearest_stresses(onam, kind=kind).iloc[0, 0]
233
- logger.info(f" | using nearest stress with kind='{kind}': '{pnam}'")
232
+ logger.info(" | using nearest stress with kind='%s': '%s'", kind, pnam)
234
233
  p, pmeta = self.pstore.get_stresses(pnam, return_metadata=True)
235
234
  prec = {
236
235
  "name": pnam,
@@ -266,7 +265,7 @@ class PastastoreYAML:
266
265
  else:
267
266
  kind = "evap"
268
267
  enam = self.pstore.get_nearest_stresses(onam, kind=kind).iloc[0, 0]
269
- logger.info(f" | using nearest stress with kind='{kind}': '{enam}'")
268
+ logger.info(" | using nearest stress with kind='%s': '%s'", kind, enam)
270
269
  e, emeta = self.pstore.get_stresses(enam, return_metadata=True)
271
270
  evap = {
272
271
  "name": enam,
@@ -291,11 +290,11 @@ class PastastoreYAML:
291
290
  if "rfunc" not in d:
292
291
  logger.info(" | no 'rfunc' provided, using 'Exponential'")
293
292
  # for pastas >= 0.23.0, convert rfunc value to dictionary with 'class' key
294
- elif not isinstance(d["rfunc"], dict) and not PASTAS_LEQ_022:
293
+ elif not isinstance(d["rfunc"], dict):
295
294
  d["rfunc"] = {"class": d["rfunc"]}
296
295
 
297
296
  # stressmodel
298
- classkey = "stressmodel" if PASTAS_LEQ_022 else "class"
297
+ classkey = "class"
299
298
  if classkey not in d:
300
299
  d[classkey] = "RechargeModel"
301
300
 
@@ -303,7 +302,7 @@ class PastastoreYAML:
303
302
  if ("recharge" not in d) and (d[classkey] == "RechargeModel"):
304
303
  logger.info(" | no 'recharge' type provided, using 'Linear'")
305
304
  # if pastas >= 0.23.0, recharge value must be dict with class key
306
- elif not isinstance(d["recharge"], dict) and not PASTAS_LEQ_022:
305
+ elif not isinstance(d["recharge"], dict):
307
306
  d["recharge"] = {"class": d["recharge"]}
308
307
 
309
308
  # tarsomodel logic
@@ -314,7 +313,8 @@ class PastastoreYAML:
314
313
  if ((dmin is None) or (dmax is None)) and (oseries is None):
315
314
  logger.info(
316
315
  " | no 'dmin/dmax' or 'oseries' provided,"
317
- f" filling in 'oseries': '{onam}'"
316
+ " filling in 'oseries': '%s'",
317
+ onam,
318
318
  )
319
319
  d["oseries"] = onam
320
320
 
@@ -365,7 +365,7 @@ class PastastoreYAML:
365
365
  snam = self.pstore.get_nearest_oseries(onam).iloc[0, 0]
366
366
  else:
367
367
  snam = self.pstore.get_nearest_stresses(onam, kind=kind).iloc[0, 0]
368
- logger.info(f" | using nearest stress with kind='{kind}': {snam}")
368
+ logger.info(" | using nearest stress with kind='%s': %s", kind, snam)
369
369
 
370
370
  s, smeta = self.pstore.get_stresses(snam, return_metadata=True)
371
371
  s = {
@@ -374,7 +374,7 @@ class PastastoreYAML:
374
374
  "metadata": smeta,
375
375
  "series": s.squeeze(),
376
376
  }
377
- d["stress"] = [s] if PASTAS_LEQ_022 else s
377
+ d["stress"] = s
378
378
 
379
379
  # use stress name if not provided
380
380
  if "name" not in d:
@@ -383,9 +383,9 @@ class PastastoreYAML:
383
383
  # rfunc
384
384
  if "rfunc" not in d:
385
385
  logger.info(" | no 'rfunc' provided, using 'Gamma'")
386
- d["rfunc"] = "Gamma" if PASTAS_LEQ_022 else {"class": "Gamma"}
386
+ d["rfunc"] = {"class": "Gamma"}
387
387
  # for pastas >= 0.23.0, convert rfunc value to dictionary with 'class' key
388
- elif not isinstance(d["rfunc"], dict) and not PASTAS_LEQ_022:
388
+ elif not isinstance(d["rfunc"], dict):
389
389
  d["rfunc"] = {"class": d["rfunc"]}
390
390
 
391
391
  return d
@@ -441,7 +441,10 @@ class PastastoreYAML:
441
441
  .values
442
442
  )
443
443
  logger.info(
444
- f" | using {n} nearest stress(es) with kind='{kind}': {snames}"
444
+ " | using %d nearest stress(es) with kind='%s': %s",
445
+ n,
446
+ kind,
447
+ snames,
445
448
  )
446
449
  else:
447
450
  snames = [snames]
@@ -472,11 +475,9 @@ class PastastoreYAML:
472
475
  # rfunc
473
476
  if "rfunc" not in d:
474
477
  logger.info(" | no 'rfunc' provided, using 'HantushWellModel'")
475
- d["rfunc"] = (
476
- "HantushWellModel" if PASTAS_LEQ_022 else {"class": "HantushWellModel"}
477
- )
478
+ d["rfunc"] = {"class": "HantushWellModel"}
478
479
  # for pastas >= 0.23.0, convert rfunc value to dictionary with 'class' key
479
- elif not isinstance(d["rfunc"], dict) and not PASTAS_LEQ_022:
480
+ elif not isinstance(d["rfunc"], dict):
480
481
  d["rfunc"] = {"class": d["rfunc"]}
481
482
 
482
483
  if "up" not in d:
@@ -510,7 +511,7 @@ class PastastoreYAML:
510
511
  else:
511
512
  onam = str(mlyml.pop("oseries"))
512
513
 
513
- logger.info(f"Building model '{mlnam}' for oseries '{onam}'")
514
+ logger.info("Building model '%s' for oseries '%s'", mlnam, onam)
514
515
  o, ometa = self.pstore.get_oseries(onam, return_metadata=True)
515
516
 
516
517
  # create model to obtain default model settings
@@ -528,14 +529,11 @@ class PastastoreYAML:
528
529
  name = smyml.get("name", smnam)
529
530
  else:
530
531
  name = smnam
531
- logger.info(f"| parsing stressmodel: '{name}'")
532
+ logger.info("| parsing stressmodel: '%s'", name)
532
533
 
533
534
  # check whether smtyp is defined
534
- classkey = "stressmodel" if PASTAS_LEQ_022 else "class"
535
+ classkey = "class"
535
536
  if smyml is not None:
536
- if PASTAS_LEQ_022:
537
- if "class" in smyml:
538
- smyml["stressmodel"] = smyml.pop("class")
539
537
  if classkey in smyml:
540
538
  smtyp = True
541
539
  else:
@@ -637,10 +635,10 @@ class PastastoreYAML:
637
635
  """
638
636
  if "\n" in fyaml or "\r" in fyaml:
639
637
  with temporary_yaml_from_str(fyaml) as fyaml:
640
- with open(fyaml, "r") as f:
638
+ with fyaml.open("r", encoding="utf-8") as f:
641
639
  yml = yaml.load(f, Loader=yaml.CFullLoader)
642
- elif os.path.exists(fyaml):
643
- with open(fyaml, "r") as f:
640
+ elif Path(fyaml).exists():
641
+ with Path(fyaml).open("r", encoding="utf-8") as f:
644
642
  yml = yaml.load(f, Loader=yaml.CFullLoader)
645
643
  else:
646
644
  raise ValueError(
@@ -655,8 +653,8 @@ class PastastoreYAML:
655
653
 
656
654
  mldict = self.construct_mldict(mlyml, mlnam)
657
655
 
658
- # load model
659
- ml = ps.io.base._load_model(mldict)
656
+ # Use pastas' internal _load_model - required for model reconstruction
657
+ ml = ps.io.base._load_model(mldict) # noqa: SLF001
660
658
  models.append(ml)
661
659
 
662
660
  return models
@@ -664,7 +662,7 @@ class PastastoreYAML:
664
662
  def export_stored_models_per_oseries(
665
663
  self,
666
664
  oseries: Optional[Union[List[str], str]] = None,
667
- outdir: Optional[str] = ".",
665
+ outdir: Optional[Path | str] = ".",
668
666
  minimal_yaml: Optional[bool] = False,
669
667
  use_nearest: Optional[bool] = False,
670
668
  ):
@@ -690,7 +688,7 @@ class PastastoreYAML:
690
688
  the time series are actually the nearest ones! Only used
691
689
  when minimal_yaml=True. Default is False.
692
690
  """
693
- onames = self.pstore.conn._parse_names(oseries, "oseries")
691
+ onames = self.pstore.conn.parse_names(oseries, "oseries")
694
692
 
695
693
  for onam in onames:
696
694
  try:
@@ -716,7 +714,7 @@ class PastastoreYAML:
716
714
  name = d.pop("name")
717
715
  model_dicts[name] = d
718
716
 
719
- with open(os.path.join(outdir, f"{onam}.yaml"), "w") as f:
717
+ with (Path(outdir) / f"{onam}.yaml").open("w", encoding="utf-8") as f:
720
718
  yaml.dump(model_dicts, f, Dumper=yaml.CDumper)
721
719
 
722
720
  def export_models(
@@ -758,7 +756,7 @@ class PastastoreYAML:
758
756
  filename for YAML file, only used if `split=False`
759
757
  """
760
758
  if models is None:
761
- modelnames = self.pstore.conn._parse_names(modelnames, "models")
759
+ modelnames = self.pstore.conn.parse_names(modelnames, "models")
762
760
  model_list = self.pstore.get_models(
763
761
  modelnames, return_dict=True, squeeze=False
764
762
  )
@@ -783,13 +781,13 @@ class PastastoreYAML:
783
781
  name = d.pop("name")
784
782
  model_dicts[name] = d
785
783
 
786
- with open(os.path.join(outdir, filename), "w") as f:
784
+ with (Path(outdir) / filename).open("w", encoding="utf-8") as f:
787
785
  yaml.dump(model_dicts, f, Dumper=yaml.CDumper)
788
786
 
789
787
  @staticmethod
790
788
  def export_model(
791
789
  ml: Union[ps.Model, dict],
792
- outdir: Optional[str] = ".",
790
+ outdir: Optional[Path | str] = ".",
793
791
  minimal_yaml: Optional[bool] = False,
794
792
  use_nearest: Optional[bool] = False,
795
793
  ):
@@ -816,7 +814,7 @@ class PastastoreYAML:
816
814
  name = ml["name"]
817
815
  else:
818
816
  name = ml.name
819
- with open(os.path.join(outdir, f"{name}.yaml"), "w") as f:
817
+ with (Path(outdir) / f"{name}.yaml").open("w", encoding="utf-8") as f:
820
818
  if isinstance(ml, ps.Model):
821
819
  mldict = deepcopy(ml.to_dict(series=False))
822
820
  elif isinstance(ml, dict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pastastore
3
- Version: 1.10.2
3
+ Version: 1.12.0
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,12 +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
+ Requires-Dist: colorama
56
+ Requires-Dist: pandas<3.0
55
57
  Provides-Extra: full
56
58
  Requires-Dist: pastastore[arcticdb,optional]; extra == "full"
57
59
  Requires-Dist: hydropandas; extra == "full"
@@ -63,7 +65,6 @@ Requires-Dist: pyproj; extra == "optional"
63
65
  Requires-Dist: adjustText; extra == "optional"
64
66
  Provides-Extra: arcticdb
65
67
  Requires-Dist: arcticdb; extra == "arcticdb"
66
- Requires-Dist: protobuf<6,>=3.5.0.post1; extra == "arcticdb"
67
68
  Provides-Extra: lint
68
69
  Requires-Dist: ruff; extra == "lint"
69
70
  Provides-Extra: pytest
@@ -74,16 +75,17 @@ Requires-Dist: pytest-cov; extra == "pytest"
74
75
  Requires-Dist: pytest-dependency; extra == "pytest"
75
76
  Requires-Dist: pytest-benchmark; extra == "pytest"
76
77
  Requires-Dist: codacy-coverage; extra == "pytest"
77
- Provides-Extra: test
78
- Requires-Dist: pastastore[arcticdb,lint,optional,pytest]; extra == "test"
79
- Requires-Dist: hydropandas[full]; extra == "test"
80
78
  Provides-Extra: docs
81
79
  Requires-Dist: pastastore[optional]; extra == "docs"
82
80
  Requires-Dist: sphinx_rtd_theme; extra == "docs"
83
81
  Requires-Dist: Ipython; extra == "docs"
84
82
  Requires-Dist: ipykernel; extra == "docs"
85
- Requires-Dist: nbsphinx; extra == "docs"
86
- Requires-Dist: nbsphinx_link; extra == "docs"
83
+ Requires-Dist: myst_nb; extra == "docs"
84
+ Provides-Extra: test
85
+ Requires-Dist: pastastore[arcticdb,optional,pytest]; extra == "test"
86
+ Requires-Dist: hydropandas[full]; extra == "test"
87
+ Provides-Extra: dev
88
+ Requires-Dist: pastastore[docs,lint,test]; extra == "dev"
87
89
  Dynamic: license-file
88
90
 
89
91
  ![pastastore](https://github.com/pastas/pastastore/workflows/pastastore/badge.svg)
@@ -106,8 +108,12 @@ left off without having to reload everything.
106
108
 
107
109
  Install the module with `pip install pastastore`.
108
110
 
109
- For installing in development mode, clone the repository and install by typing
110
- `pip install -e .` from the module root directory.
111
+ For development, clone the repository and install all development, testing, and
112
+ documentation dependencies with:
113
+
114
+ ```sh
115
+ pip install -e .[dev]
116
+ ```
111
117
 
112
118
  For plotting background maps, the `contextily` and `pyproj` packages are
113
119
  required. For a full install, including optional dependencies for plotting and
@@ -180,4 +186,4 @@ pstore.to_zip("my_backup.zip")
180
186
  ```
181
187
 
182
188
  For more elaborate examples, refer to the
183
- [Notebooks](https://pastastore.readthedocs.io/latest/examples.html#example-notebooks).
189
+ [Notebooks](https://pastastore.readthedocs.io/en/latest/examples.html#example-notebooks).
@@ -0,0 +1,31 @@
1
+ docs/conf.py,sha256=RFWVsGSGtqXc-N0ett9Z5XVrTKDzj3ukbXPQZM9wQIM,4186
2
+ pastastore/__init__.py,sha256=It_5KcVu7tMdo0QL9DXUNWtMKUDSLNfjzcuomkRdnUE,547
3
+ pastastore/base.py,sha256=KCfv13XnY7ryXqZ1Oz02ROvitJ_NndxhkrtXL5FINSQ,72407
4
+ pastastore/connectors.py,sha256=ArR053kp8ogwbtRgSCOC8f5wyMw16WNSLRzb_Kx1b6k,35291
5
+ pastastore/datasets.py,sha256=0ZjeXRX5uRTAbZPfAVoLjfR2sh0MDpMSatfKu7Imeg0,6007
6
+ pastastore/plotting.py,sha256=Zvesn7yY_z4LXdTLNT5TDui1YJ1wcm_yTvgqw6h2iQg,55697
7
+ pastastore/store.py,sha256=ymXP9UyXPqARlJ6lNPasucFe1qURf3bn5Sc1oBZx6xY,70004
8
+ pastastore/styling.py,sha256=aw7rTrllLyL574EcLqMSwNTuQaT3jpFAK92MyJzhLL8,2423
9
+ pastastore/typing.py,sha256=wsn50wH_LV3J6WXNpAUcRnSJppmGqYsX6kGCg49uGzY,376
10
+ pastastore/util.py,sha256=VrtJUYaFGnYApbcSBZZbc2NRdy8S93v-FyuT1bq3fjY,35578
11
+ pastastore/validator.py,sha256=Kwl0QoLCOYk1eLbBhaaZJNq0XMtsRwM2Uo1qlWu_f5w,20236
12
+ pastastore/version.py,sha256=Af4ycb4-UpzBjuM58vVI_e7Nw5K5IZJovwNmfp4B3xg,1148
13
+ pastastore/yaml_interface.py,sha256=x05edO_N-F9ia-nn5LlKzGdGATFHaGs9rwQAGvuDrnM,30577
14
+ pastastore/extensions/__init__.py,sha256=pHZQha6yhq3fwsoDWvXW-lYEbUUmlfCcHMrYoK_1Hxs,505
15
+ pastastore/extensions/accessor.py,sha256=kftQM6dqMDoySbyTKcvmkjC5gJRp465KA18G4NVXUO0,367
16
+ pastastore/extensions/hpd.py,sha256=bTX3UebPKAVZikIgDfHh6xrK7sHVAT2EbiImh1e7_OE,29728
17
+ pastastore-1.12.0.dist-info/licenses/LICENSE,sha256=MB_6p4kXDCUsYNjslcMByBu6i7wMNRKPC36JnhzpN4o,1087
18
+ tests/conftest.py,sha256=5fko9oJ7vsFkxhvldHSRHa0XA6TzUgYOHZMSSn9f6ho,7943
19
+ tests/test_001_import.py,sha256=o31MWpMNxngnytJeEnPz_0UTcT-XbG1JqggvrWLvSUY,238
20
+ tests/test_002_connectors.py,sha256=o5uXrylGUiHvOAaQ-IqhM8LJ4WC1ktHBecmZ0TUreqg,9442
21
+ tests/test_003_pastastore.py,sha256=7Ebtk-f963b4QAOG8TAJxipM5IEpxfFuLbaG0i0MMlA,11456
22
+ tests/test_004_yaml.py,sha256=3hMNjb9s0S2rbmpyEjW6FDRAxfUZS_U1qoPl4wB-cCo,4440
23
+ tests/test_005_maps_plots.py,sha256=HVfkTQhtpONl8N8Uv39nJMrkLYOmMYy9T2AjpzYmUp4,2152
24
+ tests/test_006_benchmark.py,sha256=hEk2kpYKpghI7RDQaSuWkSzkF9lNphm8VgBxouVtWAU,4940
25
+ tests/test_007_hpdextension.py,sha256=QKXct8sBKIypAYmI7URypYPaAtCJgM464u7D3yJMNow,4304
26
+ tests/test_008_stressmodels.py,sha256=733fyCvuzjKcaLjvSMt5dTTLp-T4alzNJAToSxTIUug,4003
27
+ tests/test_009_parallel.py,sha256=aDFEcLSz9cwbgKXpbRmNwIKA2IQ3rAPLYnA-U1nDZ1o,15039
28
+ pastastore-1.12.0.dist-info/METADATA,sha256=qHAscjDQ4XPmM2gVLRDBqYtHLuo8OPkRrFwc5qGZbXA,7696
29
+ pastastore-1.12.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
30
+ pastastore-1.12.0.dist-info/top_level.txt,sha256=1bgyMk1p23f04RK83Jju2_YAQBwyoQD_fInxoPB4YRw,22
31
+ pastastore-1.12.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
tests/conftest.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # ruff: noqa: D100 D103
2
2
  import importlib
3
+ import inspect
3
4
  from importlib import metadata
4
5
 
5
6
  import pandas as pd
@@ -8,54 +9,94 @@ import pytest
8
9
 
9
10
  import pastastore as pst
10
11
 
11
- params = ["dict", "pas", "arcticdb"]
12
+ params = [
13
+ "dict",
14
+ "pas",
15
+ "arcticdb",
16
+ ]
17
+
18
+
19
+ @pytest.fixture(scope="module")
20
+ def data1():
21
+ d = {
22
+ "oseries1": pd.read_csv("./tests/data/obs.csv", index_col=0, parse_dates=True),
23
+ "oseries1_meta": {"x": 165000, "y": 424000},
24
+ "prec1": pd.read_csv("./tests/data/rain.csv", index_col=0, parse_dates=True),
25
+ "prec1_meta": {"x": 165050, "y": 424050},
26
+ "evap1": pd.read_csv("./tests/data/evap.csv", index_col=0, parse_dates=True),
27
+ "evap1_meta": {"x": 164500, "y": 424000},
28
+ }
29
+ return d
30
+
31
+
32
+ @pytest.fixture(scope="module")
33
+ def data2():
34
+ d = {
35
+ "oseries2": pd.read_csv(
36
+ "./tests/data/head_nb1.csv", index_col=0, parse_dates=True
37
+ ),
38
+ "oseries2_meta": {"x": 164000, "y": 423000},
39
+ "prec2": pd.read_csv(
40
+ "./tests/data/rain_nb1.csv", index_col=0, parse_dates=True
41
+ ),
42
+ "prec2_meta": {"x": 164010, "y": 423000},
43
+ "evap2": pd.read_csv(
44
+ "./tests/data/evap_nb1.csv", index_col=0, parse_dates=True
45
+ ),
46
+ "evap2_meta": {"x": 164000, "y": 423030},
47
+ }
48
+ return d
49
+
50
+
51
+ @pytest.fixture(scope="module")
52
+ def data3():
53
+ w = pd.read_csv("./tests/data/well_month_end.csv", index_col=0, parse_dates=True)
54
+ w = ps.ts.timestep_weighted_resample(
55
+ w,
56
+ pd.date_range(w.index[0] - pd.offsets.MonthBegin(), w.index[-1], freq="D"),
57
+ ).bfill()
58
+
59
+ d = {
60
+ "oseries3": pd.read_csv(
61
+ "./tests/data/gw_obs.csv", index_col=0, parse_dates=True
62
+ ),
63
+ "oseries3_meta": {"x": 165554, "y": 422685},
64
+ "well1": w,
65
+ "well1_meta": {"x": 164691, "y": 423579},
66
+ "well2": w + 10,
67
+ "well2_meta": {"x": 164691 + 2000, "y": 423579 + 2000}, # far away
68
+ }
69
+ return d
70
+
71
+
72
+ def initialize_project(conn, data1, data2, data3):
73
+ pstore = pst.PastaStore(conn, "test_project")
12
74
 
75
+ # dataset 1
76
+ pstore.add_oseries(data1["oseries1"], "oseries1", metadata=data1["oseries1_meta"])
77
+ pstore.add_stress(
78
+ data1["prec1"], "prec1", kind="prec", metadata=data1["prec1_meta"]
79
+ )
80
+ pstore.add_stress(
81
+ data1["evap1"], "evap1", kind="evap", metadata=data1["evap1_meta"]
82
+ )
13
83
 
14
- def initialize_project(conn):
15
- pstore = pst.PastaStore(conn, "test_project")
84
+ # dataset 2
85
+ pstore.add_oseries(data2["oseries2"], "oseries2", metadata=data2["oseries2_meta"])
86
+ pstore.add_stress(
87
+ data2["prec2"], "prec2", kind="prec", metadata=data2["prec2_meta"]
88
+ )
89
+ pstore.add_stress(
90
+ data2["evap2"], "evap2", kind="evap", metadata=data2["evap2_meta"]
91
+ )
16
92
 
17
- # oseries 1
18
- o = pd.read_csv("./tests/data/obs.csv", index_col=0, parse_dates=True)
19
- pstore.add_oseries(o, "oseries1", metadata={"x": 165000, "y": 424000})
20
-
21
- # oseries 2
22
- o = pd.read_csv("./tests/data/head_nb1.csv", index_col=0, parse_dates=True)
23
- pstore.add_oseries(o, "oseries2", metadata={"x": 164000, "y": 423000})
24
-
25
- # oseries 3
26
- o = pd.read_csv("./tests/data/gw_obs.csv", index_col=0, parse_dates=True)
27
- pstore.add_oseries(o, "oseries3", metadata={"x": 165554, "y": 422685})
28
-
29
- # prec 1
30
- s = pd.read_csv("./tests/data/rain.csv", index_col=0, parse_dates=True)
31
- pstore.add_stress(s, "prec1", kind="prec", metadata={"x": 165050, "y": 424050})
32
-
33
- # prec 2
34
- s = pd.read_csv("./tests/data/rain_nb1.csv", index_col=0, parse_dates=True)
35
- pstore.add_stress(s, "prec2", kind="prec", metadata={"x": 164010, "y": 423000})
36
-
37
- # evap 1
38
- s = pd.read_csv("./tests/data/evap.csv", index_col=0, parse_dates=True)
39
- pstore.add_stress(s, "evap1", kind="evap", metadata={"x": 164500, "y": 424000})
40
-
41
- # evap 2
42
- s = pd.read_csv("./tests/data/evap_nb1.csv", index_col=0, parse_dates=True)
43
- pstore.add_stress(s, "evap2", kind="evap", metadata={"x": 164000, "y": 423030})
44
-
45
- # well 1
46
- s = pd.read_csv("./tests/data/well_month_end.csv", index_col=0, parse_dates=True)
47
- try:
48
- s = ps.ts.timestep_weighted_resample(
49
- s,
50
- pd.date_range(s.index[0] - pd.offsets.MonthBegin(), s.index[-1], freq="D"),
51
- ).bfill()
52
- except AttributeError:
53
- # pastas<=0.22.0
54
- pass
55
- pstore.add_stress(s, "well1", kind="well", metadata={"x": 164691, "y": 423579})
56
- # add second well
93
+ # dataset 3
94
+ pstore.add_oseries(data3["oseries3"], "oseries3", metadata=data3["oseries3_meta"])
95
+ pstore.add_stress(
96
+ data3["well1"], "well1", kind="well", metadata=data3["well1_meta"]
97
+ )
57
98
  pstore.add_stress(
58
- s + 10, "well2", kind="well", metadata={"x": 164691 + 200, "y": 423579_200}
99
+ data3["well2"], "well2", kind="well", metadata=data3["well2_meta"]
59
100
  )
60
101
 
61
102
  return pstore
@@ -67,7 +108,7 @@ def conn(request):
67
108
  name = f"test_{request.param}"
68
109
  # connect to dbase
69
110
  if request.param == "arcticdb":
70
- uri = "lmdb://./arctic_db/"
111
+ uri = "lmdb://./tests/data/arcticdb/"
71
112
  conn = pst.ArcticDBConnector(name, uri)
72
113
  elif request.param == "dict":
73
114
  conn = pst.DictConnector(name)
@@ -80,33 +121,25 @@ def conn(request):
80
121
 
81
122
 
82
123
  @pytest.fixture(scope="module", params=params)
83
- def pstore(request):
124
+ def pstore(request, data1, data2, data3):
84
125
  if request.param == "arcticdb":
85
- name = "test_project"
86
- uri = "lmdb://./arctic_db/"
126
+ name = "testdb"
127
+ uri = "lmdb://./tests/data/arcticdb/"
87
128
  connector = pst.ArcticDBConnector(name, uri)
88
129
  elif request.param == "dict":
89
- name = "test_project"
130
+ name = "testdb"
90
131
  connector = pst.DictConnector(name)
91
132
  elif request.param == "pas":
92
- name = "test_project"
133
+ name = "testdb"
93
134
  connector = pst.PasConnector(name, "./tests/data/pas")
94
135
  else:
95
136
  raise ValueError("Unrecognized parameter!")
96
- pstore = initialize_project(connector)
137
+ pstore = initialize_project(connector, data1, data2, data3)
97
138
  pstore.type = request.param # added here for defining test dependencies
98
139
  yield pstore
99
140
  pst.util.delete_pastastore(pstore)
100
141
 
101
142
 
102
- def delete_arcticdb_test_db():
103
- connstr = "lmdb://./arctic_db/"
104
- name = "test_project"
105
- connector = pst.ArcticDBConnector(name, connstr)
106
- pst.util.delete_arcticdb_connector(connector)
107
- print("ArcticDBConnector 'test_project' deleted.")
108
-
109
-
110
143
  _has_pkg_cache = {}
111
144
 
112
145
 
@@ -163,3 +196,67 @@ def requires_pkg(*pkgs):
163
196
  reason=f"missing package{'s' if len(missing) != 1 else ''}: "
164
197
  + ", ".join(missing),
165
198
  )
199
+
200
+
201
+ def for_connectors(connectors=None):
202
+ """Decorate to run tests only for specified connector types.
203
+
204
+ Parameters
205
+ ----------
206
+ connectors : list of str, optional
207
+ List of connector types for which the test should run.
208
+ If None, defaults to all connector types: ["dict", "pas", "arcticdb"].
209
+
210
+ Returns
211
+ -------
212
+ function
213
+ Decorated test function that runs only for specified connectors.
214
+ """
215
+ if connectors is None:
216
+ connectors = ["dict", "pas", "arcticdb"]
217
+
218
+ def decorator(func):
219
+ def wrapper(self, *args, **kwargs):
220
+ # Get the connector type from fixtures passed
221
+ # Could be in args or kwargs depending on how pytest passes it
222
+ store = None
223
+
224
+ # Check kwargs first (fixture passed by name)
225
+ for fixture_name in ["pstore_with_models", "pstore", "conn"]:
226
+ if fixture_name in kwargs:
227
+ store = kwargs[fixture_name]
228
+ break
229
+
230
+ # If not in kwargs, check first positional arg
231
+ if store is None and len(args) > 0:
232
+ store = args[0]
233
+
234
+ if store is None:
235
+ # Call without checking (shouldn't happen)
236
+ return func(self, *args, **kwargs)
237
+
238
+ # Check if we should skip based on connector type
239
+ if hasattr(store, "conn"):
240
+ conn_type = store.conn.conn_type
241
+ elif hasattr(store, "conn_type"):
242
+ conn_type = store.conn_type
243
+ else:
244
+ # If we can't determine connector type, just run the test
245
+ return func(self, *args, **kwargs)
246
+
247
+ if conn_type not in connectors:
248
+ pytest.skip(
249
+ f"Test skipped for {conn_type} connector "
250
+ f"(only runs for: {connectors})"
251
+ )
252
+
253
+ return func(self, *args, **kwargs)
254
+
255
+ # Preserve the original function signature and metadata for pytest
256
+ wrapper.__signature__ = inspect.signature(func)
257
+ wrapper.__name__ = func.__name__
258
+ wrapper.__doc__ = func.__doc__
259
+
260
+ return wrapper
261
+
262
+ return decorator
tests/test_001_import.py CHANGED
@@ -5,4 +5,5 @@ import warnings
5
5
  def test_import():
6
6
  with warnings.catch_warnings():
7
7
  warnings.simplefilter(action="ignore", category=FutureWarning)
8
- import pastastore # noqa: F401
8
+ import pastastore
9
+ pastastore.show_versions(optional=True)