@@ -377,3 +377,240 @@ For comparison the :class:`janus_core.processing.observables.Velocity` built-in'
377
377
378
378
def __call__(self, atoms: Atoms) -> list[float]:
379
379
return atoms.get_velocities()[self.atoms_slice, :][:, self._indices].flatten()
380
+
381
+
382
+ Deprecating a parameter
383
+ =======================
384
+
385
+ When deprecating a parameter, we aim to include a warning for at least one release cycle
386
+ before removing the original parameter, to minimise the impact on users.
387
+
388
+ This deprecation period will be extended following the release of v1.
389
+
390
+ Deprecation should be handled for both the CLI and Python interfaces,
391
+ as described below for the requirements in renaming ``model_path`` to ``model``,
392
+ as well as renaming ``filter_func`` to ``filter_class`` when these requirements differ.
393
+
394
+
395
+ Python interface
396
+ ----------------
397
+
398
+ 1. Update the parameters.
399
+
400
+ In addition to adding the new parameter, the old parameter default should be changed to ``None``:
401
+
402
+ .. code-block:: python
403
+
404
+ def __init__(
405
+ self,
406
+ ...
407
+ model: PathLike | None = None,
408
+ model_path: PathLike | None = None,
409
+ ...
410
+ filter_func: Callable | str | None = None,
411
+ filter_kwargs: dict[str, Any] | None = None,
412
+ )
413
+
414
+ All references to the old paramter must be updated, which may include cases where separate calculations interact:
415
+
416
+ .. code-block:: python
417
+
418
+ self.minimize_kwargs.setdefault("filter_class", None)
419
+
420
+
421
+ 2. Handle the duplicated parameters.
422
+
423
+ If both are specifed, we should raise an error if possible:
424
+
425
+ .. code-block:: python
426
+
427
+ if model_path:
428
+ # `model` is a new parameter, so there is no reason to be using both
429
+ if model:
430
+ raise ValueError(
431
+ "`model` has replaced `model_path`. Please only use `model`"
432
+ )
433
+ self.model = model_path
434
+
435
+
436
+ If the new parameter' s default is not ` ` None` ` , it' s usually acceptable to allow both,
437
+ prefering the old option, as this is only not ``None`` if it has been set explicitly:
438
+
439
+ .. code-block:: python
440
+
441
+ if filter_func:
442
+ filter_class = filter_func
443
+
444
+
445
+ 3. Raise a ``FutureWarning`` if the old parameter is set.
446
+
447
+ This usually needs to be later than the duplicate parameters are handled,
448
+ to ensure logging has been set up:
449
+
450
+ .. code-block:: python
451
+
452
+ if model_path:
453
+ warn(
454
+ "`model_path` has been deprecated. Please use `model`.",
455
+ FutureWarning,
456
+ stacklevel=2,
457
+ )
458
+
459
+
460
+ 4. Update the parameter docstrings:
461
+
462
+ .. code-block:: python
463
+
464
+ """"
465
+ Parameters
466
+ ----------
467
+ ...
468
+ model
469
+ MLIP model label, path to model, or loaded model. Default is `None`.
470
+ model_path
471
+ Deprecated. Please use `model`.
472
+ ...
473
+ """"
474
+
475
+
476
+ 5. Test that the old option still correctly sets the new parameter.
477
+
478
+ This should also check that a ``FutureWarning`` is raised:
479
+
480
+ .. code-block:: python
481
+
482
+ def test_deprecation_model_path():
483
+ """Test FutureWarning raised for model_path."""
484
+ skip_extras("mace")
485
+
486
+ with pytest.warns(FutureWarning, match="`model_path` has been deprecated"):
487
+ sp = SinglePoint(
488
+ arch="mace_mp",
489
+ model_path=MACE_PATH,
490
+ struct=DATA_PATH / "NaCl.cif",
491
+ )
492
+
493
+ assert sp.struct.calc.parameters["model"] == str(MACE_PATH.as_posix())
494
+
495
+
496
+ 6. Replace the old option in tests and documentation, including:
497
+
498
+ - README
499
+ - Python user guide
500
+ - Python tutorial notebooks
501
+ - Python tests
502
+
503
+
504
+ Command line
505
+ ------------
506
+
507
+ 1. Add the new parameter:
508
+
509
+ .. code-block:: python
510
+
511
+ Model = Annotated[
512
+ str | None,
513
+ Option(
514
+ help="MLIP model name, or path to model.", rich_help_panel="MLIP calculator"
515
+ ),
516
+ ]
517
+
518
+ 2. Add the ``deprecated_option`` callback to the old parameter,
519
+ which prints a warning if it is used, and hide the option:
520
+
521
+ .. code-block:: python
522
+
523
+ from janus_core.cli.utils import deprecated_option
524
+
525
+ ModelPath = Annotated[
526
+ str | None,
527
+ Option(
528
+ help="Deprecated. Please use --model",
529
+ rich_help_panel="MLIP calculator",
530
+ callback=deprecated_option,
531
+ hidden=True,
532
+ ),
533
+ ]
534
+
535
+ 3. Update the docstrings, with a deprecation note for consistency:
536
+
537
+ .. code-block:: python
538
+
539
+ """
540
+ Parameters
541
+ ----------
542
+ ...
543
+ model
544
+ Path to MLIP model or name of model. Default is `None`.
545
+ model_path
546
+ Deprecated. Please use `model`.
547
+ ...
548
+ """"
549
+
550
+
551
+ 4. Handle the duplicate values.
552
+
553
+ If the parameter is passed directly to the Python interface, and no other parameters depend on it,
554
+ it may be sufficient to pass both through and handle it within the Python interface, as is the case for ``model_path``:
555
+
556
+ .. code-block:: python
557
+
558
+ singlepoint_kwargs = {
559
+ ...
560
+ "model": model,
561
+ "model_path": model_path,
562
+ ...
563
+ }
564
+
565
+
566
+ However, it may be necessary to ensure only one value has been specified before the Python interface is reached,
567
+ as is the case for ``filter_func`` in ``geomopt``:
568
+
569
+ .. code-block:: python
570
+
571
+ if filter_func and filter_class:
572
+ raise ValueError("--filter-func is deprecated, please only use --filter")
573
+
574
+
575
+ 5. Test that the old option still correctly sets the new parameter.
576
+
577
+ Ideally, this would also check that a ``FutureWarning`` is raised,
578
+ but capture of this is inconsistent during tests, so we do not currently test against this:
579
+
580
+ .. code-block:: python
581
+
582
+ def test_model_path_deprecated(tmp_path):
583
+ """Test model_path sets model."""
584
+ file_prefix = tmp_path / "NaCl"
585
+ results_path = tmp_path / "NaCl-results.extxyz"
586
+ log_path = tmp_path / "test.log"
587
+
588
+ result = runner.invoke(
589
+ app,
590
+ [
591
+ "singlepoint",
592
+ "--struct",
593
+ DATA_PATH / "NaCl.cif",
594
+ "--arch",
595
+ "mace_mp",
596
+ "--model-path",
597
+ MACE_PATH,
598
+ "--log",
599
+ log_path,
600
+ "--file-prefix",
601
+ file_prefix,
602
+ "--no-tracker",
603
+ ],
604
+ )
605
+ assert result.exit_code == 0
606
+
607
+ atoms = read(results_path)
608
+ assert "model" in atoms.info
609
+ assert atoms.info["model"] == str(MACE_PATH.as_posix())
610
+
611
+ 6. Replace the old CLI option in tests and documentation, including:
612
+
613
+ - README
614
+ - CLI user guide
615
+ - CLI tutorial notebooks
616
+ - CLI tests
0 commit comments