pipelime-python 1.8.1__tar.gz → 1.9.1__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 (124) hide show
  1. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/PKG-INFO +2 -2
  2. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/README.md +1 -1
  3. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/__init__.py +1 -1
  4. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/utils/imports.py +49 -11
  5. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/utils/io.py +4 -3
  6. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/visitors/processor.py +45 -19
  7. pipelime_python-1.9.1/pipelime/choixe/visitors/processor_ui.py +64 -0
  8. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/xconfig.py +27 -8
  9. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/main.py +148 -62
  10. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/pretty_print.py +5 -3
  11. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/subcommands.py +1 -5
  12. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/tui/tui.py +3 -3
  13. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/tui/utils.py +1 -35
  14. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/utils.py +150 -9
  15. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/general.py +29 -13
  16. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/interfaces.py +150 -40
  17. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/piper.py +3 -1
  18. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/resume.py +28 -5
  19. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/shell.py +4 -4
  20. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/split_ops.py +3 -3
  21. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/tempman.py +2 -2
  22. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/checkpoint.py +3 -2
  23. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/drawing/mermaid.py +1 -1
  24. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/executors/base.py +2 -0
  25. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/executors/factory.py +1 -1
  26. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/model.py +24 -11
  27. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/remotes/s3_remote.py +1 -1
  28. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/pipes/operations.py +203 -10
  29. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/samples_sequence.py +63 -51
  30. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/torch.py +1 -1
  31. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/stages/base.py +7 -1
  32. pipelime_python-1.9.1/pipelime/utils/deprecated.py +51 -0
  33. pipelime_python-1.8.1/pipelime/cli/wizard.py +0 -452
  34. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/.gitignore +0 -0
  35. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/LICENSE +0 -0
  36. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/__init__.py +0 -0
  37. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/ast/__init__.py +0 -0
  38. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/ast/nodes.py +0 -0
  39. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/ast/parser.py +0 -0
  40. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/utils/__init__.py +0 -0
  41. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/utils/common.py +0 -0
  42. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/utils/rand.py +0 -0
  43. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/visitors/__init__.py +0 -0
  44. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/visitors/decoder.py +0 -0
  45. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/visitors/inspector.py +0 -0
  46. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/visitors/unparser.py +0 -0
  47. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/choixe/visitors/walker.py +0 -0
  48. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/__init__.py +0 -0
  49. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/parser.py +0 -0
  50. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/tui/__init__.py +0 -0
  51. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/cli/tui/tui.css +0 -0
  52. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/__init__.py +0 -0
  53. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/commands/toy_dataset.py +0 -0
  54. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/items/__init__.py +0 -0
  55. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/items/base.py +0 -0
  56. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/items/binary_item.py +0 -0
  57. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/items/image_item.py +0 -0
  58. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/items/metadata_item.py +0 -0
  59. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/items/model3d_item.py +0 -0
  60. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/items/numpy_item.py +0 -0
  61. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/items/pickle_item.py +0 -0
  62. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/__init__.py +0 -0
  63. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/drawing/__init__.py +0 -0
  64. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/drawing/base.py +0 -0
  65. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/drawing/factory.py +0 -0
  66. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/drawing/graphviz.py +0 -0
  67. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/executors/__init__.py +0 -0
  68. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/executors/naive.py +0 -0
  69. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/graph.py +0 -0
  70. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/parsers/__init__.py +0 -0
  71. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/parsers/base.py +0 -0
  72. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/parsers/choixe_parser.py +0 -0
  73. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/parsers/factory.py +0 -0
  74. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/__init__.py +0 -0
  75. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/estimator/__init__.py +0 -0
  76. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/estimator/base.py +0 -0
  77. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/estimator/factory.py +0 -0
  78. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/estimator/naive.py +0 -0
  79. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/__init__.py +0 -0
  80. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/base.py +0 -0
  81. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/callbacks/__init__.py +0 -0
  82. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/callbacks/file.py +0 -0
  83. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/callbacks/rich_table.py +0 -0
  84. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/callbacks/tqdm_bars.py +0 -0
  85. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/factory.py +0 -0
  86. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/receiver/__init__.py +0 -0
  87. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/listener/receiver/zmq.py +0 -0
  88. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/model.py +0 -0
  89. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/tracker/__init__.py +0 -0
  90. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/tracker/base.py +0 -0
  91. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/tracker/direct.py +0 -0
  92. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/tracker/factory.py +0 -0
  93. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/tracker/loguru.py +0 -0
  94. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/piper/progress/tracker/zmq.py +0 -0
  95. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/remotes/__init__.py +0 -0
  96. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/remotes/base.py +0 -0
  97. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/remotes/shared_folder_remote.py +0 -0
  98. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/__init__.py +0 -0
  99. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/direct_access.py +0 -0
  100. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/grabber.py +0 -0
  101. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/pipes/__init__.py +0 -0
  102. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/pipes/base.py +0 -0
  103. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/pipes/mapping.py +0 -0
  104. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/pipes/validation.py +0 -0
  105. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/pipes/writers.py +0 -0
  106. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/sample.py +0 -0
  107. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/sources/__init__.py +0 -0
  108. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/sources/from_callable.py +0 -0
  109. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/sources/raw.py +0 -0
  110. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/sources/readers.py +0 -0
  111. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/sources/toy_dataset.py +0 -0
  112. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/sequences/utils.py +0 -0
  113. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/stages/__init__.py +0 -0
  114. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/stages/augmentations.py +0 -0
  115. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/stages/entities.py +0 -0
  116. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/stages/item_info.py +0 -0
  117. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/stages/item_replacement.py +0 -0
  118. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/stages/item_sources.py +0 -0
  119. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/stages/key_transformations.py +0 -0
  120. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/utils/__init__.py +0 -0
  121. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/utils/context_managers.py +0 -0
  122. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/utils/inspection.py +0 -0
  123. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pipelime/utils/pydantic_types.py +0 -0
  124. {pipelime_python-1.8.1 → pipelime_python-1.9.1}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pipelime-python
3
- Version: 1.8.1
3
+ Version: 1.9.1
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
@@ -88,7 +88,7 @@ Maybe too much for you? No worries, `pipelime` is **modular** and you can just t
88
88
  - **easy dataflow**: `Piper` can manage and execute directed acyclic graphs (DAGs), giving back feedback on the progress through sockets or custom callbacks.
89
89
  - **configuration management**: `Choixe` is a simple and intuitive mini scripting language designed to ease the creation of configuration files with the help of variables, symbol importing, for loops, switch statements, parameter sweeps and more.
90
90
  - **command line interface**: `pipelime` can remove all the boilerplate code needed to create a beautiful CLI for you scripts and packages. You focus on *what matters* and we provide input parsing, advanced interfaces for complex arguments, automatic help generation, configuration management. Also, any `PipelimeCommand` can be used as a node in a dataflow for free!
91
- - **pydantic tools**: most of the classes in `pipelime` derive from [`pydantic.BaseModel`](https://docs.pydantic.dev/), so we have built some useful tools to, e.g., inspect their structure, auto-generate human-friendly documentation and more (including a wizard to help you writing input data to [deserialize](https://docs.pydantic.dev/usage/models/#helper-functions) any pydantic model).
91
+ - **pydantic tools**: most of the classes in `pipelime` derive from [`pydantic.BaseModel`](https://docs.pydantic.dev/), so we have built some useful tools to, e.g., inspect their structure, auto-generate human-friendly documentation and more (including a TUI to help you writing input data to [deserialize](https://docs.pydantic.dev/usage/models/#helper-functions) any pydantic model).
92
92
 
93
93
  ---
94
94
 
@@ -20,7 +20,7 @@ Maybe too much for you? No worries, `pipelime` is **modular** and you can just t
20
20
  - **easy dataflow**: `Piper` can manage and execute directed acyclic graphs (DAGs), giving back feedback on the progress through sockets or custom callbacks.
21
21
  - **configuration management**: `Choixe` is a simple and intuitive mini scripting language designed to ease the creation of configuration files with the help of variables, symbol importing, for loops, switch statements, parameter sweeps and more.
22
22
  - **command line interface**: `pipelime` can remove all the boilerplate code needed to create a beautiful CLI for you scripts and packages. You focus on *what matters* and we provide input parsing, advanced interfaces for complex arguments, automatic help generation, configuration management. Also, any `PipelimeCommand` can be used as a node in a dataflow for free!
23
- - **pydantic tools**: most of the classes in `pipelime` derive from [`pydantic.BaseModel`](https://docs.pydantic.dev/), so we have built some useful tools to, e.g., inspect their structure, auto-generate human-friendly documentation and more (including a wizard to help you writing input data to [deserialize](https://docs.pydantic.dev/usage/models/#helper-functions) any pydantic model).
23
+ - **pydantic tools**: most of the classes in `pipelime` derive from [`pydantic.BaseModel`](https://docs.pydantic.dev/), so we have built some useful tools to, e.g., inspect their structure, auto-generate human-friendly documentation and more (including a TUI to help you writing input data to [deserialize](https://docs.pydantic.dev/usage/models/#helper-functions) any pydantic model).
24
24
 
25
25
  ---
26
26
 
@@ -2,4 +2,4 @@
2
2
 
3
3
  __author__ = "Eyecan.ai"
4
4
  __email__ = "info@eyecan.ai"
5
- __version__ = "1.8.1"
5
+ __version__ = "1.9.1"
@@ -103,13 +103,15 @@ def import_module_from_file(
103
103
  Args:
104
104
  module_file_path (Union[str, Path]): the path to the `.py` module file.
105
105
  cwd (Optional[Path], optional): the folder to use for relative module import.
106
- If None, the file parent folder will be used. Defaults to None.
106
+ Defaults to None.
107
+ register_pl_module (bool, optional): whether to register the module with
108
+ PipelimeSymbolsHelper. Defaults to True.
107
109
 
108
110
  Raises:
109
- ImportError: _description_
111
+ ImportError: if the module cannot be imported.
110
112
 
111
113
  Returns:
112
- ModuleType: _description_
114
+ ModuleType: the imported module.
113
115
  """
114
116
  from pipelime.cli.utils import PipelimeSymbolsHelper
115
117
 
@@ -130,6 +132,16 @@ def import_module_from_file(
130
132
  def import_module_from_class_path(
131
133
  module_class_path: str, register_pl_module: bool = True
132
134
  ) -> ModuleType:
135
+ """Import a python module from a python class path.
136
+
137
+ Args:
138
+ module_class_path (str): the dot-separated path to the module class.
139
+ register_pl_module (bool, optional): whether to register the module with
140
+ PipelimeSymbolsHelper. Defaults to True.
141
+
142
+ Returns:
143
+ ModuleType: the imported module.
144
+ """
133
145
  from pipelime.cli.utils import PipelimeSymbolsHelper
134
146
 
135
147
  m = importlib.import_module(module_class_path)
@@ -141,6 +153,19 @@ def import_module_from_class_path(
141
153
  def import_module_from_code(
142
154
  module_code: str, register_pl_module: bool = True
143
155
  ) -> ModuleType:
156
+ """Dynamically imports a Python module from its source code.
157
+
158
+ Args:
159
+ module_code (str): the python source code of the module to import.
160
+ register_pl_module (bool, optional): whether to register the module with
161
+ PipelimeSymbolsHelper. Defaults to True.
162
+
163
+ Raises:
164
+ ImportError: if the module cannot be imported.
165
+
166
+ Returns:
167
+ ModuleType: the imported module.
168
+ """
144
169
  import hashlib
145
170
 
146
171
  from pipelime.cli.utils import PipelimeSymbolsHelper
@@ -181,7 +206,23 @@ def import_module(
181
206
  cwd: Optional[Path] = None,
182
207
  register_pl_module: bool = True,
183
208
  ) -> ModuleType:
184
- err_msgs = []
209
+ """Import a module from a file, a class path, or its source code.
210
+
211
+ Args:
212
+ module_file_or_class_path_or_code (str): the path to the module file, the
213
+ python class path, or its python code.
214
+ cwd (Optional[Path], optional): the current working directory for relative
215
+ imports. Defaults to None.
216
+ register_pl_module (bool, optional): whether to register the module with
217
+ PipelimeSymbolsHelper. Defaults to True.
218
+
219
+ Raises:
220
+ ImportError: If the module cannot be imported.
221
+
222
+ Returns:
223
+ ModuleType: The imported module.
224
+ """
225
+ err_msgs = [""]
185
226
 
186
227
  if module_file_or_class_path_or_code.endswith(".py"):
187
228
  try:
@@ -189,28 +230,25 @@ def import_module(
189
230
  module_file_or_class_path_or_code, cwd, register_pl_module
190
231
  )
191
232
  except Exception as e:
192
- err_msgs.append(str(e))
233
+ err_msgs.append(f"from file: {e}")
193
234
  try:
194
235
  return import_module_from_class_path(
195
236
  module_file_or_class_path_or_code, register_pl_module
196
237
  )
197
238
  except Exception as e:
198
- err_msgs.append(str(e))
239
+ err_msgs.append(f"from classpath: {e}")
199
240
 
200
241
  try:
201
242
  return import_module_from_code(
202
243
  module_file_or_class_path_or_code, register_pl_module
203
244
  )
204
245
  except Exception as e:
205
- err_msgs.append(str(e))
246
+ err_msgs.append(f"from code: {e}")
206
247
 
207
248
  raise ImportError(
208
249
  "Cannot import:\n"
209
250
  f" `{module_file_or_class_path_or_code}`\n"
210
- "Errors:\n"
211
- f" from file: {err_msgs[0]}\n"
212
- f" from classpath: {err_msgs[1]}\n"
213
- f" from code: {err_msgs[2]}"
251
+ "Possible causes:" + "\n ".join(err_msgs)
214
252
  )
215
253
 
216
254
 
@@ -111,9 +111,9 @@ class PipelimeTmp:
111
111
 
112
112
  user_dirs: Dict[str, List[str]] = {}
113
113
  for d in iglob(str(PipelimeTmp.base_dir() / (PipelimeTmp.prefix() + "*"))):
114
- if os.path.isdir(d):
114
+ if os.path.isdir(d): # pragma: no branch
115
115
  names = os.path.basename(d).split("-")
116
- if len(names) >= 3:
116
+ if len(names) >= 3: # pragma: no branch
117
117
  user_dirs.setdefault(names[2], []).append(d)
118
118
  return user_dirs
119
119
 
@@ -135,7 +135,8 @@ class PipelimeTemporaryDirectory:
135
135
  import shutil
136
136
 
137
137
  def onerror(func, path, exc_info):
138
- if issubclass(exc_info[0], PermissionError):
138
+ # this code comes from TemporaryDirectory
139
+ if issubclass(exc_info[0], PermissionError): # pragma: no cover
139
140
 
140
141
  def resetperms(path):
141
142
  try:
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  import uuid
3
- from copy import deepcopy
3
+ from copy import copy
4
4
  from dataclasses import dataclass
5
5
  from datetime import datetime
6
6
  from itertools import product
@@ -12,7 +12,7 @@ import pydash as py_
12
12
  import pipelime.choixe.ast.nodes as ast
13
13
  from pipelime.choixe.ast.parser import parse
14
14
  from pipelime.choixe.utils.imports import import_symbol
15
- from pipelime.choixe.utils.io import load, PipelimeTmp
15
+ from pipelime.choixe.utils.io import PipelimeTmp, load
16
16
  from pipelime.choixe.utils.rand import rand
17
17
  from pipelime.choixe.visitors.unparser import unparse
18
18
 
@@ -62,11 +62,37 @@ class Processor(ast.NodeVisitor):
62
62
  return list(product(*branches))
63
63
 
64
64
  def _repeat(self, data: Any, n: int) -> List[Any]:
65
+ # Avoid repeating if n is 1, skipping all the copy/deepcopy logic
66
+ if n == 1:
67
+ return data
68
+
65
69
  new_data = []
66
70
  for _ in range(n):
67
- new_data.extend(deepcopy(data))
71
+ #! This used to work, but it fails when some data in the tree is not
72
+ #! serializable. The proposed workaround might break stuff in the future,
73
+ #! So I will leave this here for now.
74
+ # new_data.extend(deepcopy(data))
75
+
76
+ #! Simply copying the list e.g. `new_data.extend(copy(data))`
77
+ #! does not work because we need a true copy of the inner containers as
78
+ #! well, otherwise, any modification to the inner containers will be
79
+ #! reflected in all the branches.
80
+
81
+ #! The following code is a workaround that works for now, but it might
82
+ #! break in the future. It duplicates the data only for the first level,
83
+ #! switching to shallow copy for the inner containers.
84
+ new_data.extend([copy(x) for x in data])
68
85
  return new_data
69
86
 
87
+ def _handle_missing_var(self, varname: str) -> Any:
88
+ raise ChoixeProcessingError(f"Variable `{varname}` not found")
89
+
90
+ def _handle_missing_for(self, varname: str) -> Any:
91
+ raise ChoixeProcessingError(f"Loop variable `{varname}` not found")
92
+
93
+ def _handle_missing_switch(self, varname: str) -> Any:
94
+ raise ChoixeProcessingError(f"Switch variable `{varname}` not found")
95
+
70
96
  def visit_dict(self, node: ast.DictNode) -> List[Dict]:
71
97
  data = [{}]
72
98
  for k, v in node.nodes.items():
@@ -135,7 +161,7 @@ class Processor(ast.NodeVisitor):
135
161
  found = True
136
162
 
137
163
  if not found:
138
- raise ChoixeProcessingError(f"Variable not found: `{id_}`")
164
+ var_value = self._handle_missing_var(id_)
139
165
 
140
166
  # Recursively process the variable value
141
167
  re_parsed = parse(var_value)
@@ -192,7 +218,10 @@ class Processor(ast.NodeVisitor):
192
218
 
193
219
  def visit_for(self, node: ast.ForNode) -> List[Any]:
194
220
  if isinstance(node.iterable.data, str):
195
- iterable = py_.get(self._context, node.iterable.data)
221
+ if not py_.has(self._context, node.iterable.data):
222
+ iterable = self._handle_missing_for(node.iterable.data)
223
+ else:
224
+ iterable = py_.get(self._context, node.iterable.data)
196
225
  else:
197
226
  iterable = node.iterable.data
198
227
 
@@ -200,15 +229,10 @@ class Processor(ast.NodeVisitor):
200
229
  iterable = list(range(iterable))
201
230
 
202
231
  if not isinstance(iterable, Iterable):
203
- if isinstance(node.iterable.data, str) and not py_.has(
204
- self._context, node.iterable.data
205
- ):
206
- raise ChoixeProcessingError(
207
- f"Loop variable `{node.iterable.data}` not found in context"
208
- )
209
232
  raise ChoixeProcessingError(
210
233
  f"Loop variable `{node.iterable.data}` is not iterable"
211
234
  )
235
+
212
236
  id_ = uuid.uuid1().hex if node.identifier is None else str(node.identifier.data)
213
237
  prev_loop = self._current_loop
214
238
  self._current_loop = id_
@@ -254,13 +278,10 @@ class Processor(ast.NodeVisitor):
254
278
  for branch in all_branches:
255
279
  varname = branch[0]
256
280
 
257
- # If the variable is not in the context, raise an error
258
281
  if not py_.has(self._context, varname):
259
- msg = f"Switch variable `{varname}` not found in context"
260
- raise ChoixeProcessingError(msg)
261
-
262
- # Get the value of the variable
263
- value = py_.get(self._context, varname)
282
+ value = self._handle_missing_switch(varname)
283
+ else:
284
+ value = py_.get(self._context, varname)
264
285
 
265
286
  # Match the value to the correct case
266
287
  for i in range(len(node.cases)):
@@ -275,7 +296,8 @@ class Processor(ast.NodeVisitor):
275
296
  branches.append(branch[i + 1 + len(node.cases)])
276
297
  break
277
298
 
278
- # If no case matched, use the default if available, otherwise raise an error
299
+ # If no case matched, use the default if available
300
+ # otherwise raise an error
279
301
  else:
280
302
  if node.default is not None:
281
303
  branches.append(branch[-1])
@@ -372,5 +394,9 @@ def process(
372
394
  Any: The list of all possible outcomes. If branching is disabled, the list will
373
395
  have length 1.
374
396
  """
375
- processor = Processor(context=context, cwd=cwd, allow_branching=allow_branching)
397
+ processor = Processor(
398
+ context=context,
399
+ cwd=cwd,
400
+ allow_branching=allow_branching,
401
+ )
376
402
  return node.accept(processor)
@@ -0,0 +1,64 @@
1
+ from pathlib import Path
2
+ from typing import Any, Dict, Optional
3
+
4
+ from pipelime.choixe.visitors.processor import ChoixeProcessingError, Processor
5
+ from pipelime.cli.utils import get_user_input
6
+
7
+
8
+ class ProcessorUi(Processor):
9
+ """Processor that asks the user to fill the variables missing in the context."""
10
+
11
+ def __init__(
12
+ self,
13
+ context: Optional[Dict[str, Any]] = None,
14
+ cwd: Optional[Path] = None,
15
+ allow_branching: bool = True,
16
+ ask_missing_vars: bool = False,
17
+ ) -> None:
18
+ """Constructor for `ProcessorUi`
19
+
20
+ Args:
21
+ context (Optional[Dict[str, Any]], optional): A data structure containing
22
+ the values that will replace the variable nodes. Defaults to None.
23
+ cwd (Optional[Path], optional): current working directory used for relative
24
+ imports. If set to None, the `os.getcwd()` will be used.
25
+ Defaults to None.
26
+ allow_branching (bool, optional): Set to False to disable processing on
27
+ branching nodes, like sweeps. All branching nodes will be simply
28
+ unparsed. Defaults to True.
29
+ ask_missing_vars (bool, optional): Set to True to allow the user to fill
30
+ the variables missing in the context.
31
+ """
32
+ super().__init__(context, cwd, allow_branching)
33
+ self._ask_missing_vars = ask_missing_vars
34
+ self._user_defined_vars: Dict[str, Any] = {}
35
+
36
+ def _handle_missing_var(self, varname: str) -> Any:
37
+ if self._ask_missing_vars:
38
+ msg = f"Enter value for [yellow]{varname}[/yellow]"
39
+ value = get_user_input(msg)
40
+ self._user_defined_vars[varname] = value
41
+ self._context[varname] = value
42
+ return value
43
+ else:
44
+ raise ChoixeProcessingError(f"Variable not found: `{varname}`")
45
+
46
+ def _handle_missing_for(self, varname: str) -> Any:
47
+ if self._ask_missing_vars:
48
+ msg = f"Enter value for [yellow]{varname}[/yellow]"
49
+ iterable = get_user_input(msg)
50
+ self._user_defined_vars[varname] = iterable
51
+ self._context[varname] = iterable
52
+ return iterable
53
+ else:
54
+ raise ChoixeProcessingError(f"Loop variable `{varname}` not found")
55
+
56
+ def _handle_missing_switch(self, varname: str) -> Any:
57
+ if self._ask_missing_vars:
58
+ msg = f"Enter value for [yellow]{varname}[/yellow]"
59
+ value = get_user_input(msg)
60
+ self._user_defined_vars[varname] = value
61
+ self._context[varname] = value
62
+ return value
63
+ else:
64
+ raise ChoixeProcessingError(f"Switch variable `{varname}` not found")
@@ -9,8 +9,8 @@ from schema import Schema
9
9
 
10
10
  from pipelime.choixe.ast.nodes import Node
11
11
  from pipelime.choixe.ast.parser import parse
12
- from pipelime.choixe.utils.io import dump, load
13
12
  from pipelime.choixe.utils.common import deep_set_
13
+ from pipelime.choixe.utils.io import dump, load
14
14
  from pipelime.choixe.visitors import Inspection, decode, inspect, process, walk
15
15
 
16
16
 
@@ -224,15 +224,22 @@ class XConfig(Box):
224
224
  return walk(self.parse())
225
225
 
226
226
  def _process(
227
- self, context: Optional[Dict[str, Any]] = None, allow_branching: bool = True
227
+ self,
228
+ context: Optional[Dict[str, Any]] = None,
229
+ allow_branching: bool = True,
228
230
  ) -> List[XConfig]:
229
- data = self._unsafe_process(context=context, allow_branching=allow_branching)
231
+ data = self._unsafe_process(
232
+ context=context,
233
+ allow_branching=allow_branching,
234
+ )
230
235
  return [
231
236
  XConfig(data=x, cwd=self.get_cwd(), schema=self.get_schema()) for x in data
232
237
  ]
233
238
 
234
239
  def _unsafe_process(
235
- self, context: Optional[Dict[str, Any]] = None, allow_branching: bool = True
240
+ self,
241
+ context: Optional[Dict[str, Any]] = None,
242
+ allow_branching: bool = True,
236
243
  ) -> List[Any]:
237
244
  return process(
238
245
  self.parse(),
@@ -251,7 +258,10 @@ class XConfig(Box):
251
258
  Returns:
252
259
  XConfig: The processed XConfig.
253
260
  """
254
- return self._process(context=context, allow_branching=False)[0]
261
+ return self._process(
262
+ context=context,
263
+ allow_branching=False,
264
+ )[0]
255
265
 
256
266
  def unsafe_process(self, context: Optional[Dict[str, Any]] = None) -> Any:
257
267
  """Process this XConfig without branching and return the result, without
@@ -267,7 +277,10 @@ class XConfig(Box):
267
277
  Returns:
268
278
  Any: The processed result.
269
279
  """
270
- return self._unsafe_process(context=context, allow_branching=False)[0]
280
+ return self._unsafe_process(
281
+ context=context,
282
+ allow_branching=False,
283
+ )[0]
271
284
 
272
285
  def process_all(self, context: Optional[Dict[str, Any]] = None) -> List[XConfig]:
273
286
  """Process this XConfig with branching.
@@ -279,7 +292,10 @@ class XConfig(Box):
279
292
  Returns:
280
293
  List[XConfig]: A list of all processing outcomes.
281
294
  """
282
- return self._process(context=context, allow_branching=True)
295
+ return self._process(
296
+ context=context,
297
+ allow_branching=True,
298
+ )
283
299
 
284
300
  def unsafe_process_all(self, context: Optional[Dict[str, Any]] = None) -> List[Any]:
285
301
  """Process this XConfig with branching and return the results, without
@@ -295,7 +311,10 @@ class XConfig(Box):
295
311
  Returns:
296
312
  List[Any]: A list of all processing outcomes.
297
313
  """
298
- return self._unsafe_process(context=context, allow_branching=True)
314
+ return self._unsafe_process(
315
+ context=context,
316
+ allow_branching=True,
317
+ )
299
318
 
300
319
  def inspect(self) -> Inspection:
301
320
  return inspect(self.parse(), cwd=self.get_cwd())