pipelime-python 2.0.0__tar.gz → 2.2.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 (120) hide show
  1. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/PKG-INFO +1 -1
  2. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/__init__.py +1 -1
  3. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/utils.py +4 -2
  4. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/interfaces.py +127 -1
  5. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/piper.py +8 -3
  6. pipelime_python-2.2.0/pipelime/piper/progress/listener/callbacks/loguru.py +79 -0
  7. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/factory.py +5 -2
  8. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/.gitignore +0 -0
  9. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/LICENSE +0 -0
  10. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/README.md +0 -0
  11. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/__init__.py +0 -0
  12. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/ast/__init__.py +0 -0
  13. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/ast/nodes.py +0 -0
  14. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/ast/parser.py +0 -0
  15. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/utils/__init__.py +0 -0
  16. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/utils/common.py +0 -0
  17. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/utils/imports.py +0 -0
  18. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/utils/io.py +0 -0
  19. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/utils/rand.py +0 -0
  20. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/visitors/__init__.py +0 -0
  21. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/visitors/decoder.py +0 -0
  22. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/visitors/inspector.py +0 -0
  23. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/visitors/processor.py +0 -0
  24. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/visitors/processor_ui.py +0 -0
  25. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/visitors/unparser.py +0 -0
  26. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/visitors/walker.py +0 -0
  27. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/choixe/xconfig.py +0 -0
  28. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/__init__.py +0 -0
  29. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/main.py +0 -0
  30. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/parser.py +0 -0
  31. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/pretty_print.py +0 -0
  32. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/subcommands.py +0 -0
  33. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/tui/__init__.py +0 -0
  34. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/tui/tui.css +0 -0
  35. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/tui/tui.py +0 -0
  36. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/cli/tui/utils.py +0 -0
  37. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/__init__.py +0 -0
  38. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/general.py +0 -0
  39. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/resume.py +0 -0
  40. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/shell.py +0 -0
  41. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/split_ops.py +0 -0
  42. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/tempman.py +0 -0
  43. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/commands/toy_dataset.py +0 -0
  44. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/items/__init__.py +0 -0
  45. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/items/base.py +0 -0
  46. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/items/binary_item.py +0 -0
  47. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/items/image_item.py +0 -0
  48. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/items/metadata_item.py +0 -0
  49. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/items/model3d_item.py +0 -0
  50. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/items/numpy_item.py +0 -0
  51. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/items/pickle_item.py +0 -0
  52. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/__init__.py +0 -0
  53. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/checkpoint.py +0 -0
  54. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/drawing/__init__.py +0 -0
  55. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/drawing/base.py +0 -0
  56. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/drawing/factory.py +0 -0
  57. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/drawing/graphviz.py +0 -0
  58. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/drawing/mermaid.py +0 -0
  59. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/executors/__init__.py +0 -0
  60. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/executors/base.py +0 -0
  61. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/executors/factory.py +0 -0
  62. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/executors/naive.py +0 -0
  63. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/graph.py +0 -0
  64. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/model.py +0 -0
  65. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/parsers/__init__.py +0 -0
  66. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/parsers/base.py +0 -0
  67. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/parsers/choixe_parser.py +0 -0
  68. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/parsers/factory.py +0 -0
  69. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/__init__.py +0 -0
  70. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/estimator/__init__.py +0 -0
  71. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/estimator/base.py +0 -0
  72. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/estimator/factory.py +0 -0
  73. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/estimator/naive.py +0 -0
  74. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/__init__.py +0 -0
  75. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/base.py +0 -0
  76. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/callbacks/__init__.py +0 -0
  77. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/callbacks/file.py +0 -0
  78. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/callbacks/rich_table.py +0 -0
  79. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/callbacks/tqdm_bars.py +0 -0
  80. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/receiver/__init__.py +0 -0
  81. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/listener/receiver/zmq.py +0 -0
  82. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/model.py +0 -0
  83. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/tracker/__init__.py +0 -0
  84. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/tracker/base.py +0 -0
  85. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/tracker/direct.py +0 -0
  86. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/tracker/factory.py +0 -0
  87. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/tracker/loguru.py +0 -0
  88. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/piper/progress/tracker/zmq.py +0 -0
  89. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/__init__.py +0 -0
  90. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/direct_access.py +0 -0
  91. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/grabber.py +0 -0
  92. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/pipes/__init__.py +0 -0
  93. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/pipes/base.py +0 -0
  94. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/pipes/mapping.py +0 -0
  95. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/pipes/operations.py +0 -0
  96. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/pipes/validation.py +0 -0
  97. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/pipes/writers.py +0 -0
  98. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/sample.py +0 -0
  99. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/samples_sequence.py +0 -0
  100. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/sources/__init__.py +0 -0
  101. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/sources/from_callable.py +0 -0
  102. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/sources/raw.py +0 -0
  103. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/sources/readers.py +0 -0
  104. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/sources/toy_dataset.py +0 -0
  105. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/torch.py +0 -0
  106. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/sequences/utils.py +0 -0
  107. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/stages/__init__.py +0 -0
  108. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/stages/augmentations.py +0 -0
  109. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/stages/base.py +0 -0
  110. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/stages/entities.py +0 -0
  111. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/stages/item_info.py +0 -0
  112. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/stages/item_replacement.py +0 -0
  113. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/stages/item_sources.py +0 -0
  114. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/stages/key_transformations.py +0 -0
  115. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/utils/__init__.py +0 -0
  116. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/utils/context_managers.py +0 -0
  117. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/utils/deprecated.py +0 -0
  118. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/utils/inspection.py +0 -0
  119. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pipelime/utils/pydantic_types.py +0 -0
  120. {pipelime_python-2.0.0 → pipelime_python-2.2.0}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipelime-python
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Summary: Data workflows, cli and dataflow automation.
5
5
  Project-URL: Source, https://github.com/eyecan-ai/pipelime-python
6
6
  Project-URL: Issues, https://github.com/eyecan-ai/pipelime-python/issues
@@ -2,4 +2,4 @@
2
2
 
3
3
  __author__ = "Eyecan.ai"
4
4
  __email__ = "info@eyecan.ai"
5
- __version__ = "2.0.0"
5
+ __version__ = "2.2.0"
@@ -816,7 +816,9 @@ def get_user_input(text: str) -> t.Any:
816
816
  Returns:
817
817
  The parsed user input.
818
818
  """
819
- live = get_console()._live
819
+ # if there is only one live, pause it to avoid messing the prompt
820
+ live_stack = get_console()._live_stack
821
+ live = live_stack[0] if len(live_stack) == 1 else None
820
822
  if live is not None:
821
823
  live.stop()
822
824
 
@@ -878,7 +880,7 @@ class PipelimeUserAppDir:
878
880
  for i in reversed(range(1, cls.MAX_CHECKPOINTS)):
879
881
  ckpt = base_ckpt.with_name(base_ckpt.name + f"-{i}")
880
882
  if ckpt.exists():
881
- ckpt.rename(base_ckpt.with_name(base_ckpt.name + f"-{i+1}"))
883
+ ckpt.rename(base_ckpt.with_name(base_ckpt.name + f"-{i + 1}"))
882
884
 
883
885
  ckpt = base_ckpt.with_name(base_ckpt.name + "-1")
884
886
  ckpt.mkdir(parents=True, exist_ok=False)
@@ -1,11 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  import typing as t
4
5
  import uuid
5
6
  from pathlib import Path
6
- from urllib.parse import ParseResult
7
7
 
8
8
  import pydantic.v1 as pyd
9
+ from pydantic.v1.generics import GenericModel
9
10
 
10
11
  from pipelime.piper import PiperPortType
11
12
  from pipelime.utils.pydantic_types import ItemType, SampleValidationInterface, YamlInput
@@ -835,3 +836,128 @@ class ExtendedInterval(Interval):
835
836
  raise ValueError(f"Invalid interval: {value}")
836
837
 
837
838
  return cls(**data)
839
+
840
+
841
+ ValueType = t.TypeVar("ValueType")
842
+
843
+
844
+ class OutputValueInterface(
845
+ PydanticFieldNoDefaultMixin,
846
+ GenericModel,
847
+ t.Generic[ValueType],
848
+ extra="forbid",
849
+ copy_on_model_validation="none",
850
+ underscore_attrs_are_private=True,
851
+ ):
852
+ """Interface that allows to store a value in a file.
853
+
854
+ Examples:
855
+ How to use it in your command::
856
+
857
+ class EasyCommand(PipelimeCommand, title="easy"):
858
+ output_value: OutputValueInterface[int] = OutputValueInterface.pyd_field()
859
+
860
+ def run(self):
861
+ # this will write the value 42 to the given file
862
+ self.output_value.set(42)
863
+ """
864
+
865
+ _default_type_description: t.ClassVar[t.Optional[str]] = "An output value."
866
+ _compact_form: t.ClassVar[t.Optional[str]] = "<file>[,<exists_ok>]"
867
+ _default_port_type: t.ClassVar[PiperPortType] = PiperPortType.OUTPUT
868
+ _data: ValueType
869
+
870
+ file: Path = pyd.Field(
871
+ description=(
872
+ "The output file that will store the value. If the file exists, it will"
873
+ "be overwritten if `exists_ok` is set to `True`."
874
+ ),
875
+ )
876
+ exists_ok: bool = pyd.Field(
877
+ False,
878
+ description=(
879
+ "If `True`, the output file will be overwritten if it exists. "
880
+ "If `False`, an error will be raised."
881
+ ),
882
+ )
883
+
884
+ @pyd.validator("file")
885
+ def resolve_file(cls, v: Path):
886
+ # see https://bugs.python.org/issue38671
887
+ return v.resolve().absolute()
888
+
889
+ @classmethod
890
+ def validate(cls, value):
891
+ if isinstance(value, OutputValueInterface):
892
+ return value
893
+
894
+ if isinstance(value, (str, bytes, Path)):
895
+ data = {}
896
+ raw_data = str(value).split(",")
897
+ data["file"] = raw_data[0]
898
+ if len(raw_data) > 1:
899
+ # NB: the value is left as-is when it is not True nor False
900
+ # to raise an error on validation
901
+ data["exists_ok"] = (
902
+ True
903
+ if raw_data[1].lower() == "true"
904
+ else (False if raw_data[1].lower() == "false" else raw_data[1])
905
+ )
906
+ value = data
907
+
908
+ if isinstance(value, t.Mapping):
909
+ return OutputValueInterface(**value)
910
+
911
+ raise ValueError("Invalid OutputValueInterface definition.")
912
+
913
+ @classmethod
914
+ def __get_validators__(cls):
915
+ yield cls.validate
916
+
917
+ @pyd.validator("exists_ok", always=True)
918
+ def _check_file_exists(cls, v: bool, values: t.Mapping[str, t.Any]) -> bool:
919
+ if "file" not in values:
920
+ # the "file" validator already failed
921
+ return v
922
+
923
+ if v is True:
924
+ if values["file"].is_dir():
925
+ raise ValueError(
926
+ f"Cannot overwrite the directory: `{values['file']}`. "
927
+ "Please provide a file path."
928
+ )
929
+
930
+ if v is False:
931
+ if values["file"].is_dir():
932
+ raise ValueError(
933
+ f"Trying to overwrite the directory: `{values['file']}`. "
934
+ "Please provide a file path and use `exists_ok=True` to overwrite."
935
+ )
936
+
937
+ if values["file"].exists():
938
+ raise ValueError(
939
+ f"Trying to overwrite an existing file: `{values['file']}`. "
940
+ "Please use `exists_ok=True` to overwrite."
941
+ )
942
+
943
+ return v
944
+
945
+ def get(self) -> ValueType:
946
+ """Return the stored value."""
947
+ return self._data
948
+
949
+ def set(self, value: ValueType) -> None:
950
+ """Writes the value to the output file.
951
+
952
+ Args:
953
+ value: The value to write.
954
+ """
955
+ self._data = value
956
+ with open(self.file, "w") as f:
957
+ json.dump(self._data, f)
958
+
959
+ def __repr__(self) -> str:
960
+ return self.__piper_repr__()
961
+
962
+ def __piper_repr__(self) -> str:
963
+ return self.file.as_posix()
@@ -25,11 +25,13 @@ class WatcherBackend(Enum):
25
25
 
26
26
  RICH = "rich"
27
27
  TQDM = "tqdm"
28
+ LOGURU = "loguru"
28
29
 
29
30
  def listener_key(self) -> str:
30
31
  return {
31
32
  WatcherBackend.RICH: "RICH_TABLE",
32
33
  WatcherBackend.TQDM: "TQDM_BARS",
34
+ WatcherBackend.LOGURU: "LOGURU",
33
35
  }[self]
34
36
 
35
37
 
@@ -254,8 +256,10 @@ class RunCommandBase(GraphPortForwardingCommand):
254
256
 
255
257
  PipelimeCommand._track_callback = DirectTrackCallback(callback)
256
258
 
257
- # disable annoying logging
258
- logger.disable("pipelime")
259
+ # disable annoying logging (only if we don't need it for the watcher)
260
+ if watch != WatcherBackend.LOGURU:
261
+ logger.disable("pipelime")
262
+
259
263
  exit_stack.callback(logger.enable, "pipelime")
260
264
  exit_stack.callback(PipelimeCommand._track_callback.stop_callbacks)
261
265
 
@@ -456,7 +460,8 @@ class WatchCommand(PipelimeCommand, title="watch"):
456
460
  from pipelime.utils.context_managers import CatchSignals
457
461
 
458
462
  receiver = ProgressReceiverFactory.get_receiver(
459
- self.token, **({"port": self.port} if self.port else {}) # type: ignore
463
+ self.token,
464
+ **({"port": self.port} if self.port else {}), # type: ignore
460
465
  )
461
466
  if isinstance(self.watcher, WatcherBackend):
462
467
  callback = ListenerCallbackFactory.get_callback(
@@ -0,0 +1,79 @@
1
+ import ast
2
+ import json
3
+ import os
4
+ from typing import ClassVar
5
+
6
+ from loguru import logger
7
+
8
+ from pipelime.piper.progress.listener.base import ListenerCallback
9
+ from pipelime.piper.progress.model import ProgressUpdate
10
+
11
+
12
+ class LoguruListenerCallback(ListenerCallback):
13
+ """Listener callback that sends progress updates to loguru.
14
+
15
+ Args:
16
+ show_token: Whether to include the token in the logged data.
17
+ log_extras: Extra fields to bind to the logger. Can also be set via
18
+ environment variables with the prefix PIPELIME_PROGRESS_LOG_EXTRA_
19
+ (e.g., PIPELIME_PROGRESS_LOG_EXTRA_key=value). Environment variables
20
+ take precedence over this argument.
21
+ level: Logging level to use. Can also be set via the environment variable
22
+ PIPELIME_PROGRESS_LOG_LEVEL. Defaults to "DEBUG".
23
+ """
24
+
25
+ LOG_LEVEL_ENV_VAR: ClassVar[str] = "PIPELIME_PROGRESS_LOG_LEVEL"
26
+ LOG_EXTRA_ENV_VAR_PREFIX: ClassVar[str] = "PIPELIME_PROGRESS_LOG_EXTRA_"
27
+
28
+ @classmethod
29
+ def update_data(cls, data: dict, prog: ProgressUpdate, show_token: bool) -> None:
30
+ """Update the data dictionary with the progress update"""
31
+ root = data.setdefault(prog.op_info.token, dict()) if show_token else data
32
+
33
+ chunk_data = root.setdefault(prog.op_info.node, dict()).setdefault(
34
+ str(prog.op_info.chunk), dict()
35
+ )
36
+ chunk_data["message"] = prog.op_info.message
37
+ chunk_data["total"] = prog.op_info.total
38
+ chunk_data["progress"] = prog.progress
39
+ chunk_data["finished"] = prog.finished
40
+
41
+ def __init__(
42
+ self,
43
+ show_token: bool = False,
44
+ log_extras: dict | None = None,
45
+ level: str = "DEBUG",
46
+ ) -> None:
47
+ super().__init__(show_token)
48
+
49
+ self._data: dict = dict()
50
+
51
+ # collect log extras from environment variables
52
+ env_extras = {}
53
+ for key, value in os.environ.items():
54
+ if key.startswith(self.LOG_EXTRA_ENV_VAR_PREFIX):
55
+ extra_key = key[len(self.LOG_EXTRA_ENV_VAR_PREFIX) :].lower()
56
+ try:
57
+ env_extras[extra_key] = ast.literal_eval(value)
58
+ except (ValueError, SyntaxError):
59
+ env_extras[extra_key] = value
60
+
61
+ # merge env extras with provided extras (env vars take precedence)
62
+ log_extras = log_extras or {}
63
+ self._log_extras = {**log_extras, **env_extras}
64
+
65
+ # determine log level (env var takes precedence)
66
+ self._level = os.getenv(self.LOG_LEVEL_ENV_VAR, level).upper()
67
+
68
+ def _log_data(self) -> None:
69
+ if self._data:
70
+ logger.bind(**self._log_extras).log(self._level, json.dumps(self._data))
71
+
72
+ def on_update(self, prog: ProgressUpdate) -> None:
73
+ """Called when a progress update is received"""
74
+ LoguruListenerCallback.update_data(self._data, prog, self.show_token)
75
+ self._log_data()
76
+
77
+ def on_stop(self) -> None:
78
+ """Called when the listener stops - ensures final progress is sent"""
79
+ self._log_data()
@@ -1,13 +1,15 @@
1
1
  from typing import Optional
2
+
2
3
  from pipelime.piper.progress.listener.base import ListenerCallback, ProgressReceiver
3
- from pipelime.piper.progress.listener.receiver.zmq import ZMQProgressReceiver
4
+ from pipelime.piper.progress.listener.callbacks.file import FileListenerCallback
5
+ from pipelime.piper.progress.listener.callbacks.loguru import LoguruListenerCallback
4
6
  from pipelime.piper.progress.listener.callbacks.rich_table import (
5
7
  RichTableListenerCallback,
6
8
  )
7
9
  from pipelime.piper.progress.listener.callbacks.tqdm_bars import (
8
10
  TqdmBarsListenerCallback,
9
11
  )
10
- from pipelime.piper.progress.listener.callbacks.file import FileListenerCallback
12
+ from pipelime.piper.progress.listener.receiver.zmq import ZMQProgressReceiver
11
13
 
12
14
 
13
15
  class ProgressReceiverFactory:
@@ -35,6 +37,7 @@ class ListenerCallbackFactory:
35
37
  "RICH_TABLE": RichTableListenerCallback,
36
38
  "TQDM_BARS": TqdmBarsListenerCallback,
37
39
  "FILE": FileListenerCallback,
40
+ "LOGURU": LoguruListenerCallback,
38
41
  }
39
42
 
40
43
  @classmethod
File without changes