calkit-python 0.8.5__tar.gz → 0.9.0__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. {calkit_python-0.8.5 → calkit_python-0.9.0}/PKG-INFO +3 -2
  2. {calkit_python-0.8.5 → calkit_python-0.9.0}/README.md +2 -1
  3. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/__init__.py +1 -1
  4. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/main.py +114 -11
  5. {calkit_python-0.8.5 → calkit_python-0.9.0}/.github/FUNDING.yml +0 -0
  6. {calkit_python-0.8.5 → calkit_python-0.9.0}/.github/workflows/publish-test.yml +0 -0
  7. {calkit_python-0.8.5 → calkit_python-0.9.0}/.github/workflows/publish.yml +0 -0
  8. {calkit_python-0.8.5 → calkit_python-0.9.0}/.gitignore +0 -0
  9. {calkit_python-0.8.5 → calkit_python-0.9.0}/LICENSE +0 -0
  10. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/__init__.py +0 -0
  11. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/config.py +0 -0
  12. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/core.py +0 -0
  13. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/import_.py +0 -0
  14. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/list.py +0 -0
  15. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/new.py +0 -0
  16. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/notebooks.py +0 -0
  17. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cli/office.py +0 -0
  18. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/cloud.py +0 -0
  19. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/conda.py +0 -0
  20. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/config.py +0 -0
  21. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/core.py +0 -0
  22. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/data.py +0 -0
  23. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/docker.py +0 -0
  24. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/dvc.py +0 -0
  25. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/git.py +0 -0
  26. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/gui.py +0 -0
  27. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/jupyter.py +0 -0
  28. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/magics.py +0 -0
  29. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/models.py +0 -0
  30. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/office.py +0 -0
  31. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/server.py +0 -0
  32. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/__init__.py +0 -0
  33. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/core.py +0 -0
  34. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/latex/__init__.py +0 -0
  35. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/latex/article/paper.tex +0 -0
  36. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/latex/core.py +0 -0
  37. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/latex/jfm/jfm.bst +0 -0
  38. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/latex/jfm/jfm.cls +0 -0
  39. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  40. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/latex/jfm/paper.tex +0 -0
  41. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/templates/latex/jfm/upmath.sty +0 -0
  42. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/__init__.py +0 -0
  43. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/cli/__init__.py +0 -0
  44. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/cli/test_list.py +0 -0
  45. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/cli/test_main.py +0 -0
  46. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/cli/test_new.py +0 -0
  47. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/test_conda.py +0 -0
  48. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/test_core.py +0 -0
  49. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/test_dvc.py +0 -0
  50. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/test_jupyter.py +0 -0
  51. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/test_magics.py +0 -0
  52. {calkit_python-0.8.5 → calkit_python-0.9.0}/calkit/tests/test_templates.py +0 -0
  53. {calkit_python-0.8.5 → calkit_python-0.9.0}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  54. {calkit_python-0.8.5 → calkit_python-0.9.0}/docs/tutorials/conda-envs.md +0 -0
  55. {calkit_python-0.8.5 → calkit_python-0.9.0}/docs/tutorials/img/run-proc.png +0 -0
  56. {calkit_python-0.8.5 → calkit_python-0.9.0}/docs/tutorials/notebook-pipeline.md +0 -0
  57. {calkit_python-0.8.5 → calkit_python-0.9.0}/docs/tutorials/procedures.md +0 -0
  58. {calkit_python-0.8.5 → calkit_python-0.9.0}/pyproject.toml +0 -0
  59. {calkit_python-0.8.5 → calkit_python-0.9.0}/test/pipeline.ipynb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: calkit-python
3
- Version: 0.8.5
3
+ Version: 0.9.0
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://github.com/calkit/calkit
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -46,7 +46,8 @@ Our goal is to make reproducibility easier so it becomes more common.
46
46
  To do this, we try to make it easy for users to follow two simple rules:
47
47
 
48
48
  1. **Keep everything in version control.** This includes large files like
49
- datasets, enabled by DVC. The [Calkit cloud](https://calkit.io)
49
+ datasets, enabled by DVC.
50
+ The [Calkit cloud](https://github.com/calkit/calkit-cloud)
50
51
  serves as a simple default DVC remote storage location for those who do not
51
52
  want to manage their own infrastructure.
52
53
  2. **Generate all important artifacts with a single pipeline.** There should be
@@ -15,7 +15,8 @@ Our goal is to make reproducibility easier so it becomes more common.
15
15
  To do this, we try to make it easy for users to follow two simple rules:
16
16
 
17
17
  1. **Keep everything in version control.** This includes large files like
18
- datasets, enabled by DVC. The [Calkit cloud](https://calkit.io)
18
+ datasets, enabled by DVC.
19
+ The [Calkit cloud](https://github.com/calkit/calkit-cloud)
19
20
  serves as a simple default DVC remote storage location for those who do not
20
21
  want to manage their own infrastructure.
21
22
  2. **Generate all important artifacts with a single pipeline.** There should be
@@ -1,4 +1,4 @@
1
- __version__ = "0.8.5"
1
+ __version__ = "0.9.0"
2
2
 
3
3
  from .core import *
4
4
  from . import git
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import csv
6
+ import functools
6
7
  import hashlib
7
8
  import json
8
9
  import os
@@ -387,7 +388,10 @@ def run_dvc_repro(
387
388
  args += ["--pipeline", pipeline]
388
389
  if downstream is not None:
389
390
  args += downstream
390
- subprocess.check_call(["dvc", "repro"] + args)
391
+ try:
392
+ subprocess.check_call(["dvc", "repro"] + args)
393
+ except subprocess.CalledProcessError:
394
+ raise_error("DVC pipeline failed")
391
395
  # Now parse stage metadata for calkit objects
392
396
  if not os.path.isfile("dvc.yaml"):
393
397
  raise_error("No dvc.yaml file found")
@@ -504,6 +508,20 @@ def run_in_env(
504
508
  ),
505
509
  ),
506
510
  ] = None,
511
+ no_check: Annotated[
512
+ bool,
513
+ typer.Option(
514
+ "--no-check",
515
+ help="Don't check the environment is valid before running in it.",
516
+ ),
517
+ ] = False,
518
+ relaxed_check: Annotated[
519
+ bool,
520
+ typer.Option(
521
+ "--relaxed",
522
+ help="Check the environment in a relaxed way, if applicable.",
523
+ ),
524
+ ] = False,
507
525
  verbose: Annotated[
508
526
  bool, typer.Option("--verbose", "-v", help="Print verbose output.")
509
527
  ] = False,
@@ -529,6 +547,8 @@ def run_in_env(
529
547
  env_name = default_env_name
530
548
  if env_name is None:
531
549
  raise_error("Environment must be specified if there are multiple")
550
+ if env_name not in envs:
551
+ raise_error(f"Environment '{env_name}' does not exist")
532
552
  env = envs[env_name]
533
553
  if wdir is not None:
534
554
  cwd = os.path.abspath(wdir)
@@ -539,6 +559,15 @@ def run_in_env(
539
559
  shell = env.get("shell", "sh")
540
560
  platform = env.get("platform")
541
561
  if env["kind"] == "docker":
562
+ if "image" not in env:
563
+ raise_error("Image must be defined for Docker environments")
564
+ if "path" in env and not no_check:
565
+ check_docker_env(
566
+ tag=env["image"],
567
+ fpath=env["path"],
568
+ platform=env.get("platform"),
569
+ quiet=True,
570
+ )
542
571
  shell_cmd = " ".join(cmd)
543
572
  docker_cmd = [
544
573
  "docker",
@@ -560,14 +589,75 @@ def run_in_env(
560
589
  ]
561
590
  if verbose:
562
591
  typer.echo(f"Running command: {docker_cmd}")
563
- subprocess.check_call(docker_cmd, cwd=wdir)
592
+ try:
593
+ subprocess.check_call(docker_cmd, cwd=wdir)
594
+ except subprocess.CalledProcessError:
595
+ raise_error("Failed to run in Docker environment")
564
596
  elif env["kind"] == "conda":
565
597
  with open(env["path"]) as f:
566
598
  conda_env = calkit.ryaml.load(f)
599
+ if not no_check:
600
+ check_conda_env(
601
+ env_fpath=env["path"], relaxed=relaxed_check, quiet=True
602
+ )
567
603
  cmd = ["conda", "run", "-n", conda_env["name"]] + cmd
568
604
  if verbose:
569
605
  typer.echo(f"Running command: {cmd}")
570
- subprocess.check_call(cmd, cwd=wdir)
606
+ try:
607
+ subprocess.check_call(cmd, cwd=wdir)
608
+ except subprocess.CalledProcessError:
609
+ raise_error("Failed to run in Conda environment")
610
+ elif env["kind"] in ["pixi", "uv"]:
611
+ env_cmd = []
612
+ if "name" in env:
613
+ env_cmd = ["--environment", env["name"]]
614
+ cmd = [env["kind"], "run"] + env_cmd + cmd
615
+ if verbose:
616
+ typer.echo(f"Running command: {cmd}")
617
+ try:
618
+ subprocess.check_call(cmd, cwd=wdir)
619
+ except subprocess.CalledProcessError:
620
+ raise_error(f"Failed to run in {env['kind']} environment")
621
+ elif env["kind"] == "uv-venv":
622
+ # TODO: This doesn't work on Windows
623
+ if "prefix" not in env:
624
+ raise_error("uv-venv environments require a prefix")
625
+ if "path" not in env:
626
+ raise_error("uv-venv environments require a path")
627
+ prefix = env["prefix"]
628
+ path = env["path"]
629
+ shell_cmd = " ".join(cmd)
630
+ # Check environment
631
+ if not no_check:
632
+ if not os.path.isdir(prefix):
633
+ if verbose:
634
+ typer.echo(f"Creating uv-venv at {prefix}")
635
+ try:
636
+ subprocess.check_call(["uv", "venv", prefix], cwd=wdir)
637
+ except subprocess.CalledProcessError:
638
+ raise_error(f"Failed to create uv-venv at {prefix}")
639
+ fname, ext = os.path.splitext(path)
640
+ lock_fpath = fname + "-lock" + ext
641
+ check_cmd = (
642
+ f". {prefix}/bin/activate "
643
+ f"&& uv pip install -q -r {path} "
644
+ f"&& uv pip freeze > {lock_fpath} "
645
+ "&& deactivate"
646
+ )
647
+ try:
648
+ if verbose:
649
+ typer.echo(f"Running command: {check_cmd}")
650
+ subprocess.check_output(check_cmd, shell=True, cwd=wdir)
651
+ except subprocess.CalledProcessError:
652
+ raise_error("Failed to check uv-venv")
653
+ # Now run the command
654
+ cmd = f". {prefix}/bin/activate && {shell_cmd} && deactivate"
655
+ if verbose:
656
+ typer.echo(f"Running command: {cmd}")
657
+ try:
658
+ subprocess.check_call(cmd, shell=True, cwd=wdir)
659
+ except subprocess.CalledProcessError:
660
+ raise_error("Failed to run in uv-venv")
571
661
  else:
572
662
  raise_error("Environment kind not supported")
573
663
 
@@ -605,7 +695,7 @@ def check_call(
605
695
  name="build-docker",
606
696
  help="Build Docker image if missing or different from lock file.",
607
697
  )
608
- def build_docker(
698
+ def check_docker_env(
609
699
  tag: Annotated[str, typer.Argument(help="Image tag.")],
610
700
  fpath: Annotated[
611
701
  str, typer.Option("-i", "--input", help="Path to input Dockerfile.")
@@ -613,6 +703,9 @@ def build_docker(
613
703
  platform: Annotated[
614
704
  str, typer.Option("--platform", help="Which platform(s) to build for.")
615
705
  ] = None,
706
+ quiet: Annotated[
707
+ bool, typer.Option("--quiet", "-q", help="Be quiet.")
708
+ ] = False,
616
709
  ):
617
710
  def get_docker_inspect():
618
711
  out = json.loads(
@@ -626,28 +719,31 @@ def build_docker(
626
719
  _ = out[0].pop("DockerVersion")
627
720
  return out
628
721
 
629
- typer.echo(f"Checking for existing image with tag {tag}")
722
+ outfile = open(os.devnull, "w") if quiet else None
723
+ typer.echo(f"Checking for existing image with tag {tag}", file=outfile)
630
724
  # First call Docker inspect
631
725
  try:
632
726
  inspect = get_docker_inspect()
633
727
  except subprocess.CalledProcessError:
634
- typer.echo(f"No image with tag {tag} found locally")
728
+ typer.echo(f"No image with tag {tag} found locally", file=outfile)
635
729
  inspect = []
636
- typer.echo(f"Reading Dockerfile from {fpath}")
730
+ typer.echo(f"Reading Dockerfile from {fpath}", file=outfile)
637
731
  with open(fpath) as f:
638
732
  dockerfile = f.read()
639
733
  dockerfile_md5 = hashlib.md5(dockerfile.encode()).hexdigest()
640
734
  lock_fpath = fpath + "-lock.json"
641
735
  rebuild = True
642
736
  if os.path.isfile(lock_fpath):
643
- typer.echo(f"Reading lock file: {lock_fpath}")
737
+ typer.echo(f"Reading lock file: {lock_fpath}", file=outfile)
644
738
  with open(lock_fpath) as f:
645
739
  lock = json.load(f)
646
740
  else:
647
- typer.echo(f"Lock file ({lock_fpath}) does not exist")
741
+ typer.echo(f"Lock file ({lock_fpath}) does not exist", file=outfile)
648
742
  lock = None
649
743
  if inspect and lock:
650
- typer.echo("Checking image and Dockerfile against lock file")
744
+ typer.echo(
745
+ "Checking image and Dockerfile against lock file", file=outfile
746
+ )
651
747
  rebuild = inspect[0]["RootFS"]["Layers"] != lock[0]["RootFS"][
652
748
  "Layers"
653
749
  ] or dockerfile_md5 != lock[0].get("DockerfileMD5")
@@ -833,10 +929,17 @@ def check_conda_env(
833
929
  "--relaxed", help="Treat conda and pip dependencies as equivalent."
834
930
  ),
835
931
  ] = False,
932
+ quiet: Annotated[
933
+ bool, typer.Option("--quiet", "-q", help="Be quiet.")
934
+ ] = False,
836
935
  ):
936
+ if quiet:
937
+ log_func = functools.partial(typer.echo, file=open(os.devnull, "w"))
938
+ else:
939
+ log_func = typer.echo
837
940
  calkit.conda.check_env(
838
941
  env_fpath=env_fpath,
839
942
  output_fpath=output_fpath,
840
- log_func=typer.echo,
943
+ log_func=log_func,
841
944
  relaxed=relaxed,
842
945
  )
File without changes
File without changes