cmdstanpy 1.3.0__tar.gz → 1.3.2__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 (59) hide show
  1. {cmdstanpy-1.3.0/cmdstanpy.egg-info → cmdstanpy-1.3.2}/PKG-INFO +3 -3
  2. cmdstanpy-1.3.2/cmdstanpy/_version.py +3 -0
  3. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/install_cxx_toolchain.py +5 -7
  4. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/model.py +29 -8
  5. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/progress.py +1 -2
  6. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/gq.py +1 -1
  7. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/laplace.py +2 -1
  8. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/mcmc.py +2 -1
  9. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/runset.py +18 -12
  10. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/utils/__init__.py +3 -5
  11. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/utils/cmdstan.py +4 -3
  12. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/utils/data_munging.py +2 -1
  13. cmdstanpy-1.3.2/cmdstanpy/utils/deprecation.py +58 -0
  14. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2/cmdstanpy.egg-info}/PKG-INFO +3 -3
  15. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/SOURCES.txt +2 -0
  16. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/requires.txt +2 -2
  17. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/pyproject.toml +2 -2
  18. cmdstanpy-1.3.2/test/test_deprecations.py +140 -0
  19. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_generate_quantities.py +4 -4
  20. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_logging.py +1 -0
  21. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_optimize.py +13 -13
  22. cmdstanpy-1.3.0/cmdstanpy/_version.py +0 -3
  23. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/LICENSE.md +0 -0
  24. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/MANIFEST.in +0 -0
  25. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/README.md +0 -0
  26. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/__init__.py +0 -0
  27. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/cmdstan_args.py +0 -0
  28. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/compilation.py +0 -0
  29. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/install_cmdstan.py +0 -0
  30. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/py.typed +0 -0
  31. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/__init__.py +0 -0
  32. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/metadata.py +0 -0
  33. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/mle.py +0 -0
  34. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/pathfinder.py +0 -0
  35. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/stanfit/vb.py +0 -0
  36. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/utils/command.py +0 -0
  37. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/utils/filesystem.py +0 -0
  38. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/utils/json.py +0 -0
  39. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/utils/logging.py +0 -0
  40. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy/utils/stancsv.py +0 -0
  41. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/dependency_links.txt +0 -0
  42. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/entry_points.txt +0 -0
  43. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/cmdstanpy.egg-info/top_level.txt +0 -0
  44. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/setup.cfg +0 -0
  45. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_cmdstan_args.py +0 -0
  46. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_compiler_opts.py +0 -0
  47. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_compliance.py +0 -0
  48. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_cxx_installation.py +0 -0
  49. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_install_cmdstan.py +0 -0
  50. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_laplace.py +0 -0
  51. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_log_prob.py +0 -0
  52. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_metadata.py +0 -0
  53. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_model.py +0 -0
  54. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_pathfinder.py +0 -0
  55. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_runset.py +0 -0
  56. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_sample.py +0 -0
  57. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_stancsv.py +0 -0
  58. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_utils.py +0 -0
  59. {cmdstanpy-1.3.0 → cmdstanpy-1.3.2}/test/test_variational.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmdstanpy
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: Python interface to CmdStan
5
5
  Author: Stan Dev Team
6
6
  License: BSD-3-Clause
@@ -34,8 +34,8 @@ Requires-Dist: mypy; extra == "test"
34
34
  Requires-Dist: xarray; extra == "test"
35
35
  Requires-Dist: polars>=1.8.2; extra == "test"
36
36
  Provides-Extra: docs
37
- Requires-Dist: sphinx<6,>5; extra == "docs"
38
- Requires-Dist: pydata-sphinx-theme<0.9; extra == "docs"
37
+ Requires-Dist: sphinx<9; extra == "docs"
38
+ Requires-Dist: pydata-sphinx-theme<0.17; extra == "docs"
39
39
  Requires-Dist: nbsphinx; extra == "docs"
40
40
  Requires-Dist: ipython; extra == "docs"
41
41
  Requires-Dist: ipykernel<7; extra == "docs"
@@ -0,0 +1,3 @@
1
+ """PyPi Version"""
2
+
3
+ __version__ = '1.3.2'
@@ -33,16 +33,14 @@ IS_64BITS = sys.maxsize > 2**32
33
33
 
34
34
  def usage() -> None:
35
35
  """Print usage."""
36
- print(
37
- """Arguments:
36
+ print("""Arguments:
38
37
  -v (--version) :CmdStan version
39
38
  -d (--dir) : install directory
40
39
  -s (--silent) : install with /VERYSILENT instead of /SILENT for RTools
41
40
  -m (--no-make) : don't install mingw32-make (Windows RTools 4.0 only)
42
41
  --progress : flag, when specified show progress bar for RTools download
43
42
  -h (--help) : this message
44
- """
45
- )
43
+ """)
46
44
 
47
45
 
48
46
  def get_config(dir: str, silent: bool) -> list[str]:
@@ -244,11 +242,11 @@ def get_url(version: str) -> str:
244
242
  if version == '4.0':
245
243
  # pylint: disable=line-too-long
246
244
  if IS_64BITS:
247
- url = 'https://cran.r-project.org/bin/windows/Rtools/rtools40-x86_64.exe' # noqa: disable=E501
245
+ url = 'https://cran-archive.r-project.org/bin/windows/Rtools/rtools40-x86_64.exe' # noqa: disable=E501
248
246
  else:
249
- url = 'https://cran.r-project.org/bin/windows/Rtools/rtools40-i686.exe' # noqa: disable=E501
247
+ url = 'https://cran-archive.r-project.org/bin/windows/Rtools/rtools40-i686.exe' # noqa: disable=E501
250
248
  elif version == '3.5':
251
- url = 'https://cran.r-project.org/bin/windows/Rtools/Rtools35.exe'
249
+ url = 'https://cran-archive.r-project.org/bin/windows/Rtools/Rtools35.exe' # noqa: disable=E501
252
250
  return url
253
251
 
254
252
 
@@ -55,6 +55,7 @@ from cmdstanpy.utils import (
55
55
  do_command,
56
56
  get_logger,
57
57
  )
58
+ from cmdstanpy.utils.deprecation import deprecate_positional_args
58
59
  from cmdstanpy.utils.filesystem import (
59
60
  temp_inits,
60
61
  temp_metrics,
@@ -468,9 +469,11 @@ class CmdStanModel:
468
469
  user_header=self._compiler_options.user_header,
469
470
  )
470
471
 
472
+ @deprecate_positional_args(last_deprecated="jacobian")
471
473
  def optimize(
472
474
  self,
473
475
  data: Union[Mapping[str, Any], str, os.PathLike, None] = None,
476
+ *,
474
477
  seed: Optional[int] = None,
475
478
  inits: Union[Mapping[str, Any], float, str, os.PathLike, None] = None,
476
479
  output_dir: OptionalPath = None,
@@ -492,7 +495,6 @@ class CmdStanModel:
492
495
  time_fmt: str = "%Y%m%d%H%M%S",
493
496
  timeout: Optional[float] = None,
494
497
  jacobian: bool = False,
495
- # would be nice to move this further up, but that's a breaking change
496
498
  ) -> CmdStanMLE:
497
499
  """
498
500
  Run the specified CmdStan optimize algorithm to produce a
@@ -660,9 +662,11 @@ class CmdStanModel:
660
662
  return mle
661
663
 
662
664
  # pylint: disable=too-many-arguments
665
+ @deprecate_positional_args(last_deprecated="timeout")
663
666
  def sample(
664
667
  self,
665
668
  data: Union[Mapping[str, Any], str, os.PathLike, None] = None,
669
+ *,
666
670
  chains: Optional[int] = None,
667
671
  parallel_chains: Optional[int] = None,
668
672
  threads_per_chain: Optional[int] = None,
@@ -693,14 +697,13 @@ class CmdStanModel:
693
697
  fixed_param: bool = False,
694
698
  output_dir: OptionalPath = None,
695
699
  sig_figs: Optional[int] = None,
696
- save_latent_dynamics: bool = False,
700
+ save_diagnostics: bool = False,
697
701
  save_profile: bool = False,
698
702
  show_progress: bool = True,
699
703
  show_console: bool = False,
700
704
  refresh: Optional[int] = None,
701
705
  time_fmt: str = "%Y%m%d%H%M%S",
702
706
  timeout: Optional[float] = None,
703
- *,
704
707
  force_one_process_per_chain: Optional[bool] = None,
705
708
  inv_metric: Union[
706
709
  str,
@@ -709,6 +712,7 @@ class CmdStanModel:
709
712
  list[Union[str, np.ndarray, Mapping[str, Any]]],
710
713
  None,
711
714
  ] = None,
715
+ save_latent_dynamics: Optional[bool] = None,
712
716
  ) -> CmdStanMCMC:
713
717
  """
714
718
  Run or more chains of the NUTS-HMC sampler to produce a set of draws
@@ -907,6 +911,13 @@ class CmdStanModel:
907
911
  if fixed_param is None:
908
912
  fixed_param = self._fixed_param
909
913
 
914
+ if save_latent_dynamics is not None:
915
+ get_logger().warning(
916
+ "Argument name `save_latent_dynamics` is deprecated, please "
917
+ "rename to `save_diagnostics`."
918
+ )
919
+ save_diagnostics = save_latent_dynamics # type: ignore
920
+
910
921
  if chains is None:
911
922
  chains = 4
912
923
  if chains < 1:
@@ -1092,7 +1103,7 @@ class CmdStanModel:
1092
1103
  inits=cmdstan_inits,
1093
1104
  output_dir=output_dir,
1094
1105
  sig_figs=sig_figs,
1095
- save_latent_dynamics=save_latent_dynamics,
1106
+ save_latent_dynamics=save_diagnostics,
1096
1107
  save_profile=save_profile,
1097
1108
  method_args=sampler_args,
1098
1109
  refresh=refresh,
@@ -1190,10 +1201,12 @@ class CmdStanModel:
1190
1201
  mcmc = CmdStanMCMC(runset)
1191
1202
  return mcmc
1192
1203
 
1204
+ @deprecate_positional_args(last_deprecated="timeout")
1193
1205
  def generate_quantities(
1194
1206
  self,
1195
1207
  data: Union[Mapping[str, Any], str, os.PathLike, None] = None,
1196
1208
  previous_fit: Union[Fit, list[str], None] = None,
1209
+ *,
1197
1210
  seed: Optional[int] = None,
1198
1211
  gq_output_dir: OptionalPath = None,
1199
1212
  sig_figs: Optional[int] = None,
@@ -1201,7 +1214,6 @@ class CmdStanModel:
1201
1214
  refresh: Optional[int] = None,
1202
1215
  time_fmt: str = "%Y%m%d%H%M%S",
1203
1216
  timeout: Optional[float] = None,
1204
- *,
1205
1217
  mcmc_sample: Union[CmdStanMCMC, list[str], None] = None,
1206
1218
  ) -> CmdStanGQ[Fit]:
1207
1219
  """
@@ -1383,14 +1395,16 @@ class CmdStanModel:
1383
1395
  quantities = CmdStanGQ(runset=runset, previous_fit=fit_object)
1384
1396
  return quantities
1385
1397
 
1398
+ @deprecate_positional_args(last_deprecated="timeout")
1386
1399
  def variational(
1387
1400
  self,
1388
1401
  data: Union[Mapping[str, Any], str, os.PathLike, None] = None,
1402
+ *,
1389
1403
  seed: Optional[int] = None,
1390
1404
  inits: Optional[float] = None,
1391
1405
  output_dir: OptionalPath = None,
1392
1406
  sig_figs: Optional[int] = None,
1393
- save_latent_dynamics: bool = False,
1407
+ save_diagnostics: bool = False,
1394
1408
  save_profile: bool = False,
1395
1409
  algorithm: Optional[str] = None,
1396
1410
  iter: Optional[int] = None,
@@ -1407,8 +1421,8 @@ class CmdStanModel:
1407
1421
  refresh: Optional[int] = None,
1408
1422
  time_fmt: str = "%Y%m%d%H%M%S",
1409
1423
  timeout: Optional[float] = None,
1410
- *,
1411
1424
  output_samples: Optional[int] = None,
1425
+ save_latent_dynamics: Optional[bool] = None,
1412
1426
  ) -> CmdStanVB:
1413
1427
  """
1414
1428
  Run CmdStan's variational inference algorithm to approximate
@@ -1520,6 +1534,13 @@ class CmdStanModel:
1520
1534
 
1521
1535
  draws = output_samples
1522
1536
 
1537
+ if save_latent_dynamics is not None:
1538
+ get_logger().warning(
1539
+ "Argument name `save_latent_dynamics` is deprecated, please "
1540
+ "rename to `save_diagnostics`."
1541
+ )
1542
+ save_diagnostics = save_latent_dynamics # type: ignore
1543
+
1523
1544
  variational_args = VariationalArgs(
1524
1545
  algorithm=algorithm,
1525
1546
  iter=iter,
@@ -1546,7 +1567,7 @@ class CmdStanModel:
1546
1567
  inits=_inits,
1547
1568
  output_dir=output_dir,
1548
1569
  sig_figs=sig_figs,
1549
- save_latent_dynamics=save_latent_dynamics,
1570
+ save_latent_dynamics=save_diagnostics,
1550
1571
  save_profile=save_profile,
1551
1572
  method_args=variational_args,
1552
1573
  refresh=refresh,
@@ -43,8 +43,7 @@ def wrap_callback(func): # type: ignore
43
43
 
44
44
  try:
45
45
  return func(*args, **kwargs)
46
- # pylint: disable=broad-except
47
- except Exception as e:
46
+ except Exception as e: # pylint: disable=broad-exception-caught
48
47
  _disable_progress(e)
49
48
  return callback
50
49
 
@@ -4,10 +4,10 @@ generate quantities (GQ) method
4
4
  """
5
5
 
6
6
  from collections import Counter
7
+ from collections.abc import Hashable
7
8
  from typing import (
8
9
  Any,
9
10
  Generic,
10
- Hashable,
11
11
  MutableMapping,
12
12
  NoReturn,
13
13
  Optional,
@@ -2,7 +2,8 @@
2
2
  Container for the result of running a laplace approximation.
3
3
  """
4
4
 
5
- from typing import Any, Hashable, MutableMapping, Optional, Union
5
+ from collections.abc import Hashable
6
+ from typing import Any, MutableMapping, Optional, Union
6
7
 
7
8
  import numpy as np
8
9
  import pandas as pd
@@ -4,8 +4,9 @@ Container for the result of running the sample (MCMC) method
4
4
 
5
5
  import math
6
6
  import os
7
+ from collections.abc import Hashable
7
8
  from io import StringIO
8
- from typing import Any, Hashable, MutableMapping, Optional, Sequence, Union
9
+ from typing import Any, MutableMapping, Optional, Sequence, Union
9
10
 
10
11
  import numpy as np
11
12
  import pandas as pd
@@ -162,23 +162,29 @@ class RunSet:
162
162
  return self._args.compose_command(
163
163
  idx,
164
164
  csv_file=self.csv_files[idx],
165
- diagnostic_file=self.diagnostic_files[idx]
166
- if self._args.save_latent_dynamics
167
- else None,
168
- profile_file=self.profile_files[idx]
169
- if self._args.save_profile
170
- else None,
165
+ diagnostic_file=(
166
+ self.diagnostic_files[idx]
167
+ if self._args.save_latent_dynamics
168
+ else None
169
+ ),
170
+ profile_file=(
171
+ self.profile_files[idx] if self._args.save_profile else None
172
+ ),
171
173
  )
172
174
  else:
173
175
  return self._args.compose_command(
174
176
  idx,
175
177
  csv_file=self.file_path('.csv'),
176
- diagnostic_file=self.file_path(".csv", extra="-diagnostic")
177
- if self._args.save_latent_dynamics
178
- else None,
179
- profile_file=self.file_path(".csv", extra="-profile")
180
- if self._args.save_profile
181
- else None,
178
+ diagnostic_file=(
179
+ self.file_path(".csv", extra="-diagnostic")
180
+ if self._args.save_latent_dynamics
181
+ else None
182
+ ),
183
+ profile_file=(
184
+ self.file_path(".csv", extra="-profile")
185
+ if self._args.save_profile
186
+ else None
187
+ ),
182
188
  )
183
189
 
184
190
  @property
@@ -42,7 +42,7 @@ def show_versions(output: bool = True) -> str:
42
42
 
43
43
  deps_info = []
44
44
  try:
45
- (sysname, _, release, _, machine, processor) = platform.uname()
45
+ sysname, _, release, _, machine, processor = platform.uname()
46
46
  deps_info.extend(
47
47
  [
48
48
  ("python", sys.version),
@@ -57,15 +57,13 @@ def show_versions(output: bool = True) -> str:
57
57
  ("LOCALE", f"{locale.getlocale()}"),
58
58
  ]
59
59
  )
60
- # pylint: disable=broad-except
61
- except Exception:
60
+ except Exception: # pylint: disable=broad-exception-caught
62
61
  pass
63
62
 
64
63
  try:
65
64
  deps_info.append(('cmdstan_folder', cmdstan_path()))
66
65
  deps_info.append(('cmdstan', str(cmdstan_version())))
67
- # pylint: disable=broad-except
68
- except Exception:
66
+ except Exception: # pylint: disable=broad-exception-caught
69
67
  deps_info.append(('cmdstan', 'NOT FOUND'))
70
68
 
71
69
  deps = ['cmdstanpy', 'pandas', 'xarray', 'tqdm', 'numpy']
@@ -14,6 +14,7 @@ from tqdm.auto import tqdm
14
14
  from cmdstanpy import _DOT_CMDSTAN
15
15
 
16
16
  from .. import progress as progbar
17
+ from .deprecation import deprecate_positional_args
17
18
  from .logging import get_logger
18
19
 
19
20
  EXTENSION = '.exe' if platform.system() == 'Windows' else ''
@@ -433,15 +434,16 @@ def cxx_toolchain_path(
433
434
  return compiler_path, tool_path
434
435
 
435
436
 
437
+ @deprecate_positional_args(last_deprecated="cores")
436
438
  def install_cmdstan(
437
439
  version: Optional[str] = None,
440
+ *,
438
441
  dir: Optional[str] = None,
439
442
  overwrite: bool = False,
440
443
  compiler: bool = False,
441
444
  progress: bool = False,
442
445
  verbose: bool = False,
443
446
  cores: int = 1,
444
- *,
445
447
  interactive: bool = False,
446
448
  ) -> bool:
447
449
  """
@@ -523,8 +525,7 @@ def install_cmdstan(
523
525
  cores=cores,
524
526
  )
525
527
  run_install(args)
526
- # pylint: disable=broad-except
527
- except Exception as e:
528
+ except Exception as e: # pylint: disable=broad-exception-caught
528
529
  logger.warning('CmdStan installation failed.\n%s', str(e))
529
530
  return False
530
531
 
@@ -2,7 +2,8 @@
2
2
  Common functions for reshaping numpy arrays
3
3
  """
4
4
 
5
- from typing import Hashable, MutableMapping
5
+ from collections.abc import Hashable
6
+ from typing import MutableMapping
6
7
 
7
8
  import numpy as np
8
9
  import stanio
@@ -0,0 +1,58 @@
1
+ """
2
+ Temporary helpers for deprecations in the 1.x series.
3
+ """
4
+
5
+ import functools
6
+ import inspect
7
+ from typing import Any, Callable, TypeVar
8
+
9
+ from .logging import get_logger
10
+
11
+ F = TypeVar('F', bound=Callable[..., Any])
12
+
13
+
14
+ def deprecate_positional_args(last_deprecated: str) -> Callable[[F], F]:
15
+ """
16
+ Allow keyword-only arguments up to and including ``last_deprecated``
17
+ to still be passed by position, logging a deprecation warning.
18
+
19
+ Positional use of these arguments becomes an error in CmdStanPy 2.0.
20
+ """
21
+
22
+ def decorator(func: F) -> F:
23
+ params = inspect.signature(func).parameters.values()
24
+ n_positional = sum(
25
+ p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD for p in params
26
+ )
27
+ kw_only = [
28
+ p.name for p in params if p.kind == inspect.Parameter.KEYWORD_ONLY
29
+ ]
30
+ deprecated = kw_only[: kw_only.index(last_deprecated) + 1]
31
+
32
+ @functools.wraps(func)
33
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
34
+ extra = args[n_positional:]
35
+ if not extra or len(extra) > len(deprecated):
36
+ # either nothing to do, or let Python raise its usual error
37
+ return func(*args, **kwargs)
38
+ names = deprecated[: len(extra)]
39
+ for name in names:
40
+ if name in kwargs:
41
+ raise TypeError(
42
+ f"{func.__qualname__}() got multiple values for "
43
+ f"argument '{name}'"
44
+ )
45
+ quoted = ", ".join("`" + name + "`" for name in names)
46
+ get_logger().warning(
47
+ "Passing %s to %s() by position is deprecated and will raise "
48
+ "an error in CmdStanPy 2.0. Please pass them as keyword "
49
+ "arguments instead.",
50
+ quoted,
51
+ func.__qualname__,
52
+ )
53
+ kwargs.update(zip(names, extra))
54
+ return func(*args[:n_positional], **kwargs)
55
+
56
+ return wrapper # type: ignore[return-value]
57
+
58
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmdstanpy
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: Python interface to CmdStan
5
5
  Author: Stan Dev Team
6
6
  License: BSD-3-Clause
@@ -34,8 +34,8 @@ Requires-Dist: mypy; extra == "test"
34
34
  Requires-Dist: xarray; extra == "test"
35
35
  Requires-Dist: polars>=1.8.2; extra == "test"
36
36
  Provides-Extra: docs
37
- Requires-Dist: sphinx<6,>5; extra == "docs"
38
- Requires-Dist: pydata-sphinx-theme<0.9; extra == "docs"
37
+ Requires-Dist: sphinx<9; extra == "docs"
38
+ Requires-Dist: pydata-sphinx-theme<0.17; extra == "docs"
39
39
  Requires-Dist: nbsphinx; extra == "docs"
40
40
  Requires-Dist: ipython; extra == "docs"
41
41
  Requires-Dist: ipykernel<7; extra == "docs"
@@ -30,6 +30,7 @@ cmdstanpy/utils/__init__.py
30
30
  cmdstanpy/utils/cmdstan.py
31
31
  cmdstanpy/utils/command.py
32
32
  cmdstanpy/utils/data_munging.py
33
+ cmdstanpy/utils/deprecation.py
33
34
  cmdstanpy/utils/filesystem.py
34
35
  cmdstanpy/utils/json.py
35
36
  cmdstanpy/utils/logging.py
@@ -38,6 +39,7 @@ test/test_cmdstan_args.py
38
39
  test/test_compiler_opts.py
39
40
  test/test_compliance.py
40
41
  test/test_cxx_installation.py
42
+ test/test_deprecations.py
41
43
  test/test_generate_quantities.py
42
44
  test/test_install_cmdstan.py
43
45
  test/test_laplace.py
@@ -8,8 +8,8 @@ xarray
8
8
  polars>=1.8.2
9
9
 
10
10
  [docs]
11
- sphinx<6,>5
12
- pydata-sphinx-theme<0.9
11
+ sphinx<9
12
+ pydata-sphinx-theme<0.17
13
13
  nbsphinx
14
14
  ipython
15
15
  ipykernel<7
@@ -52,8 +52,8 @@ test = [
52
52
  "polars>=1.8.2"
53
53
  ]
54
54
  docs = [
55
- "sphinx>5,<6",
56
- "pydata-sphinx-theme<0.9",
55
+ "sphinx<9",
56
+ "pydata-sphinx-theme<0.17",
57
57
  "nbsphinx",
58
58
  "ipython",
59
59
  "ipykernel<7",
@@ -0,0 +1,140 @@
1
+ """Tests for deprecated positional arguments to public functions"""
2
+
3
+ # positional calls below are deliberate
4
+ # pylint: disable=too-many-function-args
5
+
6
+ import inspect
7
+ import logging
8
+ import os
9
+ import re
10
+ import sys
11
+ from test import check_present
12
+ from typing import Any, Callable
13
+ from unittest import mock
14
+
15
+ import pytest
16
+
17
+ from cmdstanpy.model import CmdStanModel
18
+ from cmdstanpy.utils import install_cmdstan
19
+ from cmdstanpy.utils.deprecation import deprecate_positional_args
20
+
21
+ HERE = os.path.dirname(os.path.abspath(__file__))
22
+ DATAFILES_PATH = os.path.join(HERE, 'data')
23
+
24
+
25
+ class Dummy:
26
+ @deprecate_positional_args(last_deprecated="c")
27
+ def method(
28
+ self, a: Any = None, *, b: Any = None, c: Any = None, d: Any = None
29
+ ) -> tuple[Any, Any, Any, Any]:
30
+ return a, b, c, d
31
+
32
+
33
+ def test_keywords_do_not_warn(caplog: pytest.LogCaptureFixture) -> None:
34
+ with caplog.at_level(logging.WARNING):
35
+ assert Dummy().method(1, b=2, c=3, d=4) == (1, 2, 3, 4)
36
+ assert Dummy().method() == (None, None, None, None)
37
+ assert not caplog.records
38
+
39
+
40
+ def test_positional_maps_to_keywords(caplog: pytest.LogCaptureFixture) -> None:
41
+ with caplog.at_level(logging.WARNING):
42
+ assert Dummy().method(1, 2, 3, d=4) == (1, 2, 3, 4)
43
+ check_present(
44
+ caplog,
45
+ (
46
+ 'cmdstanpy',
47
+ 'WARNING',
48
+ re.compile(r"Passing `b`, `c` to Dummy\.method\(\) by position"),
49
+ ),
50
+ )
51
+
52
+
53
+ def test_positional_and_keyword_collision() -> None:
54
+ with pytest.raises(TypeError, match="multiple values for argument 'b'"):
55
+ Dummy().method(1, 2, b=3)
56
+
57
+
58
+ def test_too_many_positional() -> None:
59
+ # `d` was never positional, so it must not be filled by position
60
+ with pytest.raises(TypeError):
61
+ Dummy().method(1, 2, 3, 4)
62
+
63
+
64
+ def test_signature_is_keyword_only() -> None:
65
+ params = inspect.signature(Dummy.method).parameters
66
+ assert params['a'].kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
67
+ assert params['b'].kind == inspect.Parameter.KEYWORD_ONLY
68
+
69
+
70
+ @pytest.mark.parametrize(
71
+ 'func, positional, last_deprecated',
72
+ [
73
+ (CmdStanModel.sample, ['self', 'data'], 'timeout'),
74
+ (CmdStanModel.optimize, ['self', 'data'], 'jacobian'),
75
+ (CmdStanModel.variational, ['self', 'data'], 'timeout'),
76
+ (
77
+ CmdStanModel.generate_quantities,
78
+ ['self', 'data', 'previous_fit'],
79
+ 'timeout',
80
+ ),
81
+ (install_cmdstan, ['version'], 'cores'),
82
+ ],
83
+ )
84
+ def test_public_signatures(
85
+ func: Callable[..., Any], positional: list[str], last_deprecated: str
86
+ ) -> None:
87
+ params = inspect.signature(func).parameters
88
+ kinds = {name: p.kind for name, p in params.items()}
89
+ assert [
90
+ n
91
+ for n, k in kinds.items()
92
+ if k == inspect.Parameter.POSITIONAL_OR_KEYWORD
93
+ ] == positional
94
+ # guards against a rename silently shifting the positional mapping
95
+ assert kinds[last_deprecated] == inspect.Parameter.KEYWORD_ONLY
96
+
97
+
98
+ def test_optimize_positional_seed(caplog: pytest.LogCaptureFixture) -> None:
99
+ stan = os.path.join(DATAFILES_PATH, 'bernoulli.stan')
100
+ jdata = os.path.join(DATAFILES_PATH, 'bernoulli.data.json')
101
+ model = CmdStanModel(stan_file=stan)
102
+ with caplog.at_level(logging.WARNING):
103
+ mle_pos = model.optimize(jdata, 1234)
104
+ check_present(
105
+ caplog,
106
+ (
107
+ 'cmdstanpy',
108
+ 'WARNING',
109
+ re.compile(r"Passing `seed` to CmdStanModel\.optimize\(\)"),
110
+ ),
111
+ )
112
+ mle_kw = model.optimize(jdata, seed=1234)
113
+ assert mle_pos.metadata.cmdstan_config['seed'] == 1234
114
+ assert mle_pos.optimized_params_dict == mle_kw.optimized_params_dict
115
+
116
+
117
+ @pytest.mark.skipif(
118
+ sys.version_info < (3, 11),
119
+ reason="mock.patch resolves `cmdstanpy.install_cmdstan` to the "
120
+ "re-exported function, not the module, before Python 3.11",
121
+ )
122
+ def test_install_cmdstan_positional(caplog: pytest.LogCaptureFixture) -> None:
123
+ # stop right after argument handling; nothing is downloaded
124
+ with mock.patch(
125
+ 'cmdstanpy.install_cmdstan.run_install', side_effect=RuntimeError
126
+ ) as run_install:
127
+ with caplog.at_level(logging.WARNING):
128
+ install_cmdstan("2.36.0", "some_dir", True)
129
+ check_present(
130
+ caplog,
131
+ (
132
+ 'cmdstanpy',
133
+ 'WARNING',
134
+ re.compile(r"Passing `dir`, `overwrite` to install_cmdstan\(\)"),
135
+ ),
136
+ )
137
+ settings = run_install.call_args.args[0]
138
+ assert settings.version == "2.36.0"
139
+ assert settings.dir == "some_dir"
140
+ assert settings.overwrite
@@ -565,7 +565,7 @@ def test_from_optimization() -> None:
565
565
  assert os.path.exists(csv_file)
566
566
 
567
567
  assert bern_gqs.draws().shape == (1, 1, 10)
568
- assert bern_gqs.draws(inc_sample=True).shape == (1, 1, 12)
568
+ assert bern_gqs.draws(inc_sample=True).shape == (1, 1, 13)
569
569
 
570
570
  # draws_pd()
571
571
  assert bern_gqs.draws_pd().shape == (1, 13)
@@ -610,18 +610,18 @@ def test_opt_save_iterations(caplog: pytest.LogCaptureFixture):
610
610
  assert bern_gqs.draws(inc_warmup=True, inc_sample=True).shape == (
611
611
  iters,
612
612
  1,
613
- 12,
613
+ 13,
614
614
  )
615
615
 
616
616
  assert bern_gqs.draws(concat_chains=True).shape == (1, 10)
617
- assert bern_gqs.draws(concat_chains=True, inc_sample=True).shape == (1, 12)
617
+ assert bern_gqs.draws(concat_chains=True, inc_sample=True).shape == (1, 13)
618
618
  assert bern_gqs.draws(concat_chains=True, inc_warmup=True).shape == (
619
619
  iters,
620
620
  10,
621
621
  )
622
622
  assert bern_gqs.draws(
623
623
  concat_chains=True, inc_warmup=True, inc_sample=True
624
- ).shape == (iters, 12)
624
+ ).shape == (iters, 13)
625
625
 
626
626
  # stan_variable
627
627
  theta = bern_gqs.stan_variable(var='theta')
@@ -1,6 +1,7 @@
1
1
  """Logging control tests"""
2
2
 
3
3
  import logging
4
+
4
5
  import cmdstanpy
5
6
 
6
7
 
@@ -92,13 +92,13 @@ def test_rosenbrock(caplog: pytest.LogCaptureFixture) -> None:
92
92
  assert 'CmdStanMLE: model=rosenbrock' in repr(mle)
93
93
  assert 'method=optimize' in repr(mle)
94
94
  assert mle.converged
95
- assert mle.column_names == ('lp__', 'x', 'y')
95
+ assert mle.column_names == ('lp__', 'converged__', 'x', 'y')
96
96
  np.testing.assert_almost_equal(mle.stan_variable('x'), 1, decimal=3)
97
97
  np.testing.assert_almost_equal(mle.stan_variable('y'), 1, decimal=3)
98
98
  np.testing.assert_almost_equal(
99
99
  mle.optimized_params_pd['x'][0], 1, decimal=3
100
100
  )
101
- np.testing.assert_almost_equal(mle.optimized_params_np[1], 1, decimal=3)
101
+ np.testing.assert_almost_equal(mle.optimized_params_np[2], 1, decimal=3)
102
102
  np.testing.assert_almost_equal(mle.optimized_params_dict['x'], 1, decimal=3)
103
103
  with caplog.at_level(logging.WARNING):
104
104
  assert mle.optimized_iterations_np is None
@@ -128,8 +128,8 @@ def test_rosenbrock(caplog: pytest.LogCaptureFixture) -> None:
128
128
  np.testing.assert_almost_equal(mle.stan_variable('x'), 1, decimal=3)
129
129
  np.testing.assert_almost_equal(mle.stan_variable('y'), 1, decimal=3)
130
130
 
131
- assert mle.optimized_params_np.shape == (3,)
132
- np.testing.assert_almost_equal(mle.optimized_params_np[1], 1, decimal=3)
131
+ assert mle.optimized_params_np.shape == (4,)
132
+ np.testing.assert_almost_equal(mle.optimized_params_np[2], 1, decimal=3)
133
133
  np.testing.assert_almost_equal(
134
134
  mle.optimized_params_pd['x'][0], 1, decimal=3
135
135
  )
@@ -140,7 +140,7 @@ def test_rosenbrock(caplog: pytest.LogCaptureFixture) -> None:
140
140
  mle.optimized_iterations_np[0, 1]
141
141
  != mle.optimized_iterations_np[last_iter, 1]
142
142
  )
143
- for i in range(3):
143
+ for i in range(4):
144
144
  assert (
145
145
  mle.optimized_params_np[i]
146
146
  == mle.optimized_iterations_np[last_iter, i]
@@ -159,7 +159,7 @@ def test_eight_schools(caplog: pytest.LogCaptureFixture) -> None:
159
159
  assert 'method=optimize' in repr(mle)
160
160
  assert not mle.converged
161
161
  with caplog.at_level(logging.WARNING):
162
- assert mle.optimized_params_pd.shape == (1, 11)
162
+ assert mle.optimized_params_pd.shape == (1, 12)
163
163
  check_present(
164
164
  caplog,
165
165
  (
@@ -294,15 +294,15 @@ def test_optimize_good() -> None:
294
294
  # test numpy output
295
295
  assert isinstance(mle.optimized_params_np, np.ndarray)
296
296
  np.testing.assert_almost_equal(mle.optimized_params_np[0], -5, decimal=2)
297
- np.testing.assert_almost_equal(mle.optimized_params_np[1], 0.2, decimal=3)
297
+ np.testing.assert_almost_equal(mle.optimized_params_np[2], 0.2, decimal=3)
298
298
 
299
299
  # test pandas output
300
300
  assert mle.optimized_params_np[0] == mle.optimized_params_pd['lp__'][0]
301
- assert mle.optimized_params_np[1] == mle.optimized_params_pd['theta'][0]
301
+ assert mle.optimized_params_np[2] == mle.optimized_params_pd['theta'][0]
302
302
 
303
303
  # test dict output
304
304
  assert mle.optimized_params_np[0] == mle.optimized_params_dict['lp__']
305
- assert mle.optimized_params_np[1] == mle.optimized_params_dict['theta']
305
+ assert mle.optimized_params_np[2] == mle.optimized_params_dict['theta']
306
306
 
307
307
 
308
308
  def test_negative_parameter_values() -> None:
@@ -530,14 +530,14 @@ def test_optimize_good_dict() -> None:
530
530
  )
531
531
  # test numpy output
532
532
  np.testing.assert_almost_equal(mle.optimized_params_np[0], -5, decimal=2)
533
- np.testing.assert_almost_equal(mle.optimized_params_np[1], 0.2, decimal=3)
533
+ np.testing.assert_almost_equal(mle.optimized_params_np[2], 0.2, decimal=3)
534
534
 
535
535
 
536
536
  def test_optimize_rosenbrock() -> None:
537
537
  stan = os.path.join(DATAFILES_PATH, 'optimize', 'rosenbrock.stan')
538
538
  rose_model = CmdStanModel(stan_file=stan)
539
539
  mle = rose_model.optimize(seed=1239812093, inits=None, algorithm='BFGS')
540
- assert mle.column_names == ('lp__', 'x', 'y')
540
+ assert mle.column_names == ('lp__', 'converged__', 'x', 'y')
541
541
  np.testing.assert_almost_equal(mle.optimized_params_dict['x'], 1, decimal=3)
542
542
  np.testing.assert_almost_equal(mle.optimized_params_dict['y'], 1, decimal=3)
543
543
 
@@ -546,7 +546,7 @@ def test_optimize_no_data() -> None:
546
546
  stan = os.path.join(DATAFILES_PATH, 'optimize', 'no_data.stan')
547
547
  rose_model = CmdStanModel(stan_file=stan)
548
548
  mle = rose_model.optimize(seed=1239812093)
549
- assert mle.column_names == ('lp__', 'a')
549
+ assert mle.column_names == ('lp__', 'converged__', 'a')
550
550
  np.testing.assert_almost_equal(mle.optimized_params_dict['a'], 0, decimal=3)
551
551
 
552
552
 
@@ -600,7 +600,7 @@ def test_exe_only() -> None:
600
600
  jdata = os.path.join(DATAFILES_PATH, 'bernoulli.data.json')
601
601
  mle = bern2_model.optimize(data=jdata)
602
602
  assert mle.optimized_params_np[0] == mle.optimized_params_dict['lp__']
603
- assert mle.optimized_params_np[1] == mle.optimized_params_dict['theta']
603
+ assert mle.optimized_params_np[2] == mle.optimized_params_dict['theta']
604
604
 
605
605
 
606
606
  def test_complex_output() -> None:
@@ -1,3 +0,0 @@
1
- """PyPi Version"""
2
-
3
- __version__ = '1.3.0'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes