rootlp 0.1.7__py3-none-any.whl
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.
- rootlp/__init__.py +23 -0
- rootlp/modules/Section_LP/Section.py +177 -0
- rootlp/modules/Section_LP/__init__.py +0 -0
- rootlp/modules/Section_LP/test_Section.py +44 -0
- rootlp/modules/__init__.py +0 -0
- rootlp/modules/getfunction_LP/__init__.py +0 -0
- rootlp/modules/getfunction_LP/getfunction.py +97 -0
- rootlp/modules/getfunction_LP/test_getfunction.py +79 -0
- rootlp/modules/main_LP/__init__.py +0 -0
- rootlp/modules/main_LP/main.py +437 -0
- rootlp/modules/main_LP/test_main.py +74 -0
- rootlp/modules/menu_LP/__init__.py +0 -0
- rootlp/modules/menu_LP/menu.py +78 -0
- rootlp/modules/menu_LP/test_menu.py +79 -0
- rootlp/modules/mo_LP/__init__.py +0 -0
- rootlp/modules/mo_LP/mo.py +23 -0
- rootlp/modules/mo_LP/test_mo.py +79 -0
- rootlp/modules/print_LP/__init__.py +0 -0
- rootlp/modules/print_LP/print.py +337 -0
- rootlp/modules/print_LP/test_print.py +109 -0
- rootlp/modules/project_server_LP/__init__.py +0 -0
- rootlp/modules/project_server_LP/project_server.py +86 -0
- rootlp/modules/project_server_LP/test_project_server.py +79 -0
- rootlp/modules/readme_string_LP/__init__.py +0 -0
- rootlp/modules/readme_string_LP/readme_string.py +53 -0
- rootlp/modules/readme_string_LP/test_readme_string.py +79 -0
- rootlp/modules/user_inputs_LP/__init__.py +0 -0
- rootlp/modules/user_inputs_LP/test_user_inputs.py +46 -0
- rootlp/modules/user_inputs_LP/user_inputs.py +103 -0
- rootlp/modules.json +56 -0
- rootlp/py.typed +0 -0
- rootlp/scripts/__init__.py +0 -0
- rootlp/scripts.json +1 -0
- rootlp-0.1.7.dist-info/METADATA +41 -0
- rootlp-0.1.7.dist-info/RECORD +36 -0
- rootlp-0.1.7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Date : 2025-08-28
|
|
4
|
+
# Author : Lancelot PINCET
|
|
5
|
+
# GitHub : https://github.com/LancelotPincet
|
|
6
|
+
# Library : rootLP
|
|
7
|
+
# Module : main
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
This function can decorate the main function of a script.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# %% Libraries
|
|
16
|
+
from corelp import folder, selfkwargs, kwargsself, icon, Path
|
|
17
|
+
from rootlp import print, Section, user_inputs
|
|
18
|
+
import time
|
|
19
|
+
import functools
|
|
20
|
+
import os
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
import tkinter as tk
|
|
23
|
+
from tkinter import filedialog
|
|
24
|
+
import types
|
|
25
|
+
import marimo as mo
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# %% Function
|
|
30
|
+
def main() :
|
|
31
|
+
'''
|
|
32
|
+
This function can decorate the main function of a script.
|
|
33
|
+
User inputs parameters shoud be put in the beginning of the main file, and the decorated function will recognize them.
|
|
34
|
+
Decorated function can change the values of these parameters with keyword arguments when called.
|
|
35
|
+
Section can be created bellow the mainfunction.
|
|
36
|
+
|
|
37
|
+
Global parameters
|
|
38
|
+
-----------------
|
|
39
|
+
import_path : Path or str or None
|
|
40
|
+
Path where to import script data to process.
|
|
41
|
+
If None, will manually ask user to select it.
|
|
42
|
+
If not existent, will be ignored.
|
|
43
|
+
export_path : Path or str or None
|
|
44
|
+
Path where to export script data to process.
|
|
45
|
+
A new folder will be created inside at the call time as name.
|
|
46
|
+
If None, will save in import_path. If not existent, will be ignored.
|
|
47
|
+
If a previous call was already made in this same folder, and new is False, will try to reload data from this last folder.
|
|
48
|
+
new : bool
|
|
49
|
+
Overrides Decorator new parameter.
|
|
50
|
+
bulk : function
|
|
51
|
+
function(import_path) that returns a dictionnary of {import_subfolder:export_subfolder} for multiple decorated function run.
|
|
52
|
+
If bulk is not None, the decorated function will run with import_subfolder, export_subfolder instead of import_path, export_path (see below).
|
|
53
|
+
The import_subfolders and export_subfolder are defined from import_path and export_path respectively (they are not absolute from root path).
|
|
54
|
+
overnight : bool
|
|
55
|
+
If True and exception occurs, will skip and pass to the next run in bulk processing. To use for example for overnight bulk processing.
|
|
56
|
+
|
|
57
|
+
Examples
|
|
58
|
+
--------
|
|
59
|
+
>>> from root import main
|
|
60
|
+
...
|
|
61
|
+
>>> import_path = None # will be asked via a GUI
|
|
62
|
+
>>> export_path = None # will create inside import_path
|
|
63
|
+
>>> new = False # True to create a new export folder, False to reload precalculated data
|
|
64
|
+
>>> bulk = None # function(import_path) that returns a dictionnary of {import_subfolder:export_subfolder} for multiple decorated function run.
|
|
65
|
+
>>> overnight= False # If True and exception occurs, will skip and pass to the next run in bulk processing.
|
|
66
|
+
>>> main_string = "Hello from main!" # User input parameter
|
|
67
|
+
...
|
|
68
|
+
>>> @main(new=True) # if previous new is not defined, new is defined here
|
|
69
|
+
... def myscript() :
|
|
70
|
+
... print(main_string) # By default prints "Hello from main!"
|
|
71
|
+
... result = mysection() # Section defined bellow, result can be reloaded from previous run
|
|
72
|
+
... return result
|
|
73
|
+
...
|
|
74
|
+
... @main.section()
|
|
75
|
+
... def mysection() :
|
|
76
|
+
... print("Hello from section!")
|
|
77
|
+
... return True # Will be saved into export_path and can be reuploaded at next run with same inputs
|
|
78
|
+
...
|
|
79
|
+
>>> # Launch
|
|
80
|
+
>>> if __name__ == "__main__" :
|
|
81
|
+
... myscript() # prints "Hello from main!"
|
|
82
|
+
... myscript(main_string = "Hello changed!!") # prints "Hello changed!!" and loads section result from first run
|
|
83
|
+
'''
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def decorator(func) :
|
|
88
|
+
name = func.__name__
|
|
89
|
+
|
|
90
|
+
# Get globals around function definition
|
|
91
|
+
definition_globals = func.__globals__
|
|
92
|
+
|
|
93
|
+
@functools.wraps(func)
|
|
94
|
+
def wrapper(**overrides) -> None :
|
|
95
|
+
|
|
96
|
+
# Creates new globals
|
|
97
|
+
exec_globals = definition_globals.copy()
|
|
98
|
+
exec_globals.update(overrides)
|
|
99
|
+
_new = exec_globals.get("new", False)
|
|
100
|
+
_overnight = exec_globals.get("overnight", False)
|
|
101
|
+
|
|
102
|
+
# Creates new function
|
|
103
|
+
new_func = types.FunctionType(
|
|
104
|
+
func.__code__,
|
|
105
|
+
exec_globals,
|
|
106
|
+
name=name,
|
|
107
|
+
argdefs=func.__defaults__,
|
|
108
|
+
closure=func.__closure__,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Getting paths
|
|
112
|
+
ipath = exec_globals.get('import_path', "None")
|
|
113
|
+
if ipath is None :
|
|
114
|
+
root = tk.Tk()
|
|
115
|
+
root.title("Select import path")
|
|
116
|
+
img = tk.PhotoImage(file=icon)
|
|
117
|
+
root.iconphoto(True, img)
|
|
118
|
+
root._icon_img = img # keep reference
|
|
119
|
+
root.withdraw()
|
|
120
|
+
root.update_idletasks()
|
|
121
|
+
root.attributes("-topmost", True)
|
|
122
|
+
root.update()
|
|
123
|
+
root.focus_force()
|
|
124
|
+
ipath = filedialog.askdirectory(title=f'Select import path for {name}')
|
|
125
|
+
root.destroy()
|
|
126
|
+
if not ipath :
|
|
127
|
+
print('Searching for import_path was cancelled', style='red')
|
|
128
|
+
raise ValueError('Searching for import_path was cancelled')
|
|
129
|
+
epath = exec_globals.get('export_path', "None")
|
|
130
|
+
if ipath != "None" :
|
|
131
|
+
ipath = Path(ipath)
|
|
132
|
+
exec_globals['import_path'] = ipath
|
|
133
|
+
if epath != "None" :
|
|
134
|
+
epath = ipath.parent if epath is None else Path(epath)
|
|
135
|
+
|
|
136
|
+
# Creating new export path
|
|
137
|
+
prefix = name.replace('.', '_')
|
|
138
|
+
if epath != "None" :
|
|
139
|
+
if _new :
|
|
140
|
+
base_path = folder(epath / (f'{prefix}_' + datetime.now().strftime("%Y-%m-%d-%Hh%Mmin%Ss")), warning=False)
|
|
141
|
+
else :
|
|
142
|
+
#Searching for newest old folder
|
|
143
|
+
base_folder = None
|
|
144
|
+
_date = None
|
|
145
|
+
for f in epath.iterdir() :
|
|
146
|
+
if (not f.is_dir()) or (not f.name.startswith(f'{prefix}_')) :
|
|
147
|
+
continue
|
|
148
|
+
date_str = f.name.split('_')[-1]
|
|
149
|
+
date = datetime.strptime(date_str, "%Y-%m-%d-%Hh%Mmin%Ss")
|
|
150
|
+
if _date is None or date > _date :
|
|
151
|
+
_date, base_folder = date, f
|
|
152
|
+
base_path = base_folder if base_folder is not None else epath / (f'{prefix}_' + datetime.now().strftime("%Y-%m-%d-%Hh%Mmin%Ss"))
|
|
153
|
+
epath = base_path / 'export_folder'
|
|
154
|
+
exec_globals['export_path'] = epath
|
|
155
|
+
if not epath.exists():
|
|
156
|
+
os.makedirs(epath) #creates folders until end
|
|
157
|
+
if ipath != "None" :
|
|
158
|
+
ilink = base_path / 'import_folder'
|
|
159
|
+
if ilink.exists() or ilink.is_symlink():
|
|
160
|
+
ilink.unlink()
|
|
161
|
+
if os.name == "nt":
|
|
162
|
+
try :
|
|
163
|
+
ilink.symlink_to(ipath, ipath.is_dir())
|
|
164
|
+
except OSError :
|
|
165
|
+
print("Windows does not allow to create symlink, aborting. Consider using Windows in Developper mode.")
|
|
166
|
+
else:
|
|
167
|
+
ilink.symlink_to(ipath)
|
|
168
|
+
md_file = epath / (name+'_log.md')
|
|
169
|
+
html_file = epath / (name+'_log.html')
|
|
170
|
+
else :
|
|
171
|
+
md_file = None
|
|
172
|
+
html_file = None
|
|
173
|
+
|
|
174
|
+
# Defining bulk processing subfolders
|
|
175
|
+
subfolders = {"" : ""} if wrapper.bulk is None else wrapper.bulk(**exec_globals)
|
|
176
|
+
|
|
177
|
+
#Begining prints
|
|
178
|
+
print_status = kwargsself(print)
|
|
179
|
+
print.console = None
|
|
180
|
+
print.file = md_file
|
|
181
|
+
print(f'\n\n\n# **BEGIN {name}**\n')
|
|
182
|
+
print(f"{time.ctime()}")
|
|
183
|
+
if ipath != "None" :
|
|
184
|
+
print(f'import_path : {ipath}\n')
|
|
185
|
+
if epath != "None" :
|
|
186
|
+
print(f'export_path : {epath}\n')
|
|
187
|
+
|
|
188
|
+
# Bulk processing
|
|
189
|
+
results = {} # {export_subfolder : fucntion result}
|
|
190
|
+
for import_subfolder, export_subfolder in subfolders.items() :
|
|
191
|
+
if ipath != "None" :
|
|
192
|
+
impath = ipath / import_subfolder
|
|
193
|
+
exec_globals["import_path"] = impath
|
|
194
|
+
if epath != "None" :
|
|
195
|
+
expath = epath / export_subfolder
|
|
196
|
+
exec_globals["export_path"] = expath
|
|
197
|
+
|
|
198
|
+
# Create export subfolder
|
|
199
|
+
if not expath.exists() :
|
|
200
|
+
os.mkdir(expath)
|
|
201
|
+
|
|
202
|
+
# Updating sections
|
|
203
|
+
wrapper.section.parent_path = epath
|
|
204
|
+
wrapper.section.path = expath
|
|
205
|
+
wrapper.section.new = _new
|
|
206
|
+
|
|
207
|
+
#Applying function
|
|
208
|
+
print("\n---\n")
|
|
209
|
+
subfolder_string = f"{export_subfolder}" if export_subfolder != "" else ""
|
|
210
|
+
print(f'## **Launched script {subfolder_string}**\n')
|
|
211
|
+
tic = time.perf_counter()
|
|
212
|
+
try :
|
|
213
|
+
results[export_subfolder] = new_func()
|
|
214
|
+
|
|
215
|
+
# Errors
|
|
216
|
+
except Exception as e :
|
|
217
|
+
toc = time.perf_counter()
|
|
218
|
+
print.error()
|
|
219
|
+
print(f'\n## **{subfolder_string} took {toc-tic:.2f}s**')
|
|
220
|
+
print("\n---\n")
|
|
221
|
+
if not _overnight :
|
|
222
|
+
raise e
|
|
223
|
+
|
|
224
|
+
# No error
|
|
225
|
+
else :
|
|
226
|
+
toc = time.perf_counter()
|
|
227
|
+
print(f'\n## **{subfolder_string} took {toc-tic:.2f}s**')
|
|
228
|
+
print("\n---\n")
|
|
229
|
+
|
|
230
|
+
# END
|
|
231
|
+
print(time.ctime())
|
|
232
|
+
print(f'# **END {name}**\n\n')
|
|
233
|
+
print.export_html(html_file)
|
|
234
|
+
selfkwargs(print, print_status)
|
|
235
|
+
if wrapper.bulk is None :
|
|
236
|
+
results = results[""]
|
|
237
|
+
return results
|
|
238
|
+
|
|
239
|
+
# Making sections
|
|
240
|
+
section = Section()
|
|
241
|
+
wrapper.section = section
|
|
242
|
+
wrapper.bulk = None
|
|
243
|
+
wrapper.export = lambda : main_export(name)
|
|
244
|
+
wrapper.overview = lambda : main_overview(name)
|
|
245
|
+
wrapper.rawcode = lambda *args : main_rawcode(name, *args)
|
|
246
|
+
|
|
247
|
+
return wrapper
|
|
248
|
+
return decorator
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def main_export(name) :
|
|
253
|
+
launch = mo.ui.run_button(label=f"**--->>> {name} <<<---**")
|
|
254
|
+
launch.center()
|
|
255
|
+
|
|
256
|
+
user_inputs(True)
|
|
257
|
+
import_path = mo.ui.text(placeholder="copy-paste import path", full_width=True)
|
|
258
|
+
export_path = mo.ui.text(placeholder="copy-paste export path", full_width=True)
|
|
259
|
+
new = mo.ui.switch(value=True, label="**New**: check to create new processing folder each time [default: True]")
|
|
260
|
+
overnight = mo.ui.switch(value=False, label="**Overnight**: check to ignore bulk processing errors [default: False]")
|
|
261
|
+
parameters_execution = user_inputs()
|
|
262
|
+
|
|
263
|
+
# Markdown output
|
|
264
|
+
md_execution = f"""
|
|
265
|
+
# **{name} launch script**
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## **Script execution inputs**
|
|
270
|
+
|
|
271
|
+
### Launch script button
|
|
272
|
+
|
|
273
|
+
{launch}
|
|
274
|
+
|
|
275
|
+
Execution logs will appear on the right panel -->
|
|
276
|
+
|
|
277
|
+
(Check user inputs parameters before launching)
|
|
278
|
+
|
|
279
|
+
### Execution parameters
|
|
280
|
+
|
|
281
|
+
**Import path** : Folder with data to process [default: will open a browse window]
|
|
282
|
+
{import_path}
|
|
283
|
+
|
|
284
|
+
**Export path** : Folder where to save [default: import path **parent**]
|
|
285
|
+
{export_path}
|
|
286
|
+
|
|
287
|
+
{mo.hstack([new, overnight])}
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
return launch, parameters_execution, mo.md(md_execution);
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def main_overview(name) :
|
|
295
|
+
string = f"""
|
|
296
|
+
# **{name} code overview**
|
|
297
|
+
|
|
298
|
+
In the next tab is defined the raw code for {name}, using functions from the custom package. You can use this notebook as a template to learn how to use these functions. For any help, please contact authors.
|
|
299
|
+
|
|
300
|
+
Below is the definition of this script main function (`{name}`), which can be identified by the `@main` decorator. All the logic of the script is orchestrated from this function.
|
|
301
|
+
|
|
302
|
+
A *main script function* generally aims to transform data stored on disk into processed data that is also saved back to disk. As such, it usually defines:
|
|
303
|
+
- a path **from which to import** the input data
|
|
304
|
+
- a path **where to save** the processed data
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## **Sections and Checkpoints**
|
|
309
|
+
|
|
310
|
+
The processing logic is divided into **sections**, which are identified by the `@{name}.section` decorator.
|
|
311
|
+
|
|
312
|
+
Each section can be seen as a *checkpoint* in the pipeline:
|
|
313
|
+
- It computes intermediate data of interest
|
|
314
|
+
- These computations may be heavy or time-consuming
|
|
315
|
+
- Results are cached into a **dedicated folder**
|
|
316
|
+
|
|
317
|
+
Thanks to this mechanism, re-running the script on the same data allows previously computed results to be automatically loaded from disk, avoiding unnecessary recomputation.
|
|
318
|
+
|
|
319
|
+
The `section` decorator attaches a new attribute named `path` (of type `pathlib.Path`) to the decorated function. This attribute points to the directory associated with the section, where cached data and intermediate outputs are stored.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## **Bulk Processing and Batches**
|
|
324
|
+
|
|
325
|
+
Another feature of the `@main` decorator is support for **bulk processing**.
|
|
326
|
+
|
|
327
|
+
The basic use case is to define a single import directory that contains one batch of data to process. However, it is also possible to define a function that:
|
|
328
|
+
- selects subfolders based on specific criteria
|
|
329
|
+
- returns a list of independent import subfolders
|
|
330
|
+
This function is stored as `{name}.bulk`.
|
|
331
|
+
|
|
332
|
+
Each of these subfolders is treated as a separate **batch** and can be processed in a single run.
|
|
333
|
+
|
|
334
|
+
A typical use case is to launch a large processing job overnight after collecting sufficient data during the day. In this scenario, faulty batches can be skipped to avoid interrupting the processing of the remaining batches.
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## **Export Directory Structure**
|
|
339
|
+
|
|
340
|
+
In the export directory, a dedicated **processing folder** is created for each run.
|
|
341
|
+
This folder is named using the launch timestamp (e.g. hour and date) to clearly distinguish multiple runs.
|
|
342
|
+
|
|
343
|
+
Inside this processing folder, three subfolders are created:
|
|
344
|
+
|
|
345
|
+
- **import_folder**
|
|
346
|
+
A symbolic link to the `import_path`, allowing easy identification of the input data used.
|
|
347
|
+
|
|
348
|
+
- **export_folder**
|
|
349
|
+
Contains all processed data, organized by batch and by section.
|
|
350
|
+
|
|
351
|
+
- **_outputs**
|
|
352
|
+
Contains symbolic links to selected output files of interest, grouped by file type and batch.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## **Export Folder Layout**
|
|
357
|
+
|
|
358
|
+
Inside the `export_folder`:
|
|
359
|
+
- One folder is created per **batch**
|
|
360
|
+
- Inside each batch folder, one folder is created per **section**
|
|
361
|
+
- These section folders store cached data, intermediate results, and section-specific outputs
|
|
362
|
+
|
|
363
|
+
The overall structure of the `export_path` directory is as follows:
|
|
364
|
+
|
|
365
|
+
```text
|
|
366
|
+
export_path/
|
|
367
|
+
├── import_path/ (by default export_path is the parent of import_path)
|
|
368
|
+
│ ├── import_data.xx
|
|
369
|
+
│ └── ...
|
|
370
|
+
└── extract_signals_YYYY-MM-DD-HHhMMminSSs/
|
|
371
|
+
├── import_folder -> /path/to/import_path (symlink)
|
|
372
|
+
├── export_folder/
|
|
373
|
+
│ ├── batch_00/
|
|
374
|
+
│ │ ├── 000_section/
|
|
375
|
+
│ │ │ ├── cache_hash.pkl/
|
|
376
|
+
│ │ │ ├── output_00.xx/
|
|
377
|
+
│ │ │ └── ...
|
|
378
|
+
│ │ ├── 001_section/
|
|
379
|
+
│ │ │ ├── cache_hash.pkl/
|
|
380
|
+
│ │ │ ├── output_00.xx/
|
|
381
|
+
│ │ │ └── ...
|
|
382
|
+
│ │ └── ...
|
|
383
|
+
│ ├── batch_01/
|
|
384
|
+
│ │ ├── 000_section/
|
|
385
|
+
│ │ └── ...
|
|
386
|
+
│ └── ...
|
|
387
|
+
└── _outputs/
|
|
388
|
+
├── output_file_00/
|
|
389
|
+
│ ├── batch_00.xx (symlink)
|
|
390
|
+
│ ├── batch_01.xx (symlink)
|
|
391
|
+
│ └── ...
|
|
392
|
+
├── output_file_01/
|
|
393
|
+
│ ├── batch_00.xx (symlink)
|
|
394
|
+
│ ├── batch_01.xx (symlink)
|
|
395
|
+
│ └── ...
|
|
396
|
+
└── ...
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## **Execution Parameters**
|
|
402
|
+
|
|
403
|
+
The `@main` decorator adds several execution parameters to the function call:
|
|
404
|
+
|
|
405
|
+
1. **`import_path`** and **`export_path`**
|
|
406
|
+
- If set to `None`, a file dialog prompts the user to select an import directory
|
|
407
|
+
- The export path is then automatically defined as the **parent directory** of the import path
|
|
408
|
+
- Paths are passed to the function as `pathlib.Path` objects for improved usability
|
|
409
|
+
|
|
410
|
+
2. **`new`** (`bool`)
|
|
411
|
+
- If `True`, a new processing folder is created using the current launch timestamp
|
|
412
|
+
- If `False`, the most recent processing folder is reused
|
|
413
|
+
|
|
414
|
+
3. **`overnight`** (`bool`)
|
|
415
|
+
- If `True`, errors in individual batches are skipped
|
|
416
|
+
- This allows long-running bulk processing jobs to continue without interruption
|
|
417
|
+
"""
|
|
418
|
+
return mo.md(string)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def main_rawcode(name, showcode) :
|
|
423
|
+
string = f"""
|
|
424
|
+
# **{name} raw code**
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
{showcode}
|
|
429
|
+
"""
|
|
430
|
+
return mo.md(string)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
# %% Test function run
|
|
435
|
+
if __name__ == "__main__":
|
|
436
|
+
from corelp import test
|
|
437
|
+
test(__file__)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Date : 2025-08-28
|
|
4
|
+
# Author : Lancelot PINCET
|
|
5
|
+
# GitHub : https://github.com/LancelotPincet
|
|
6
|
+
# Library : rootLP
|
|
7
|
+
# Module : main
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
This file allows to test main
|
|
11
|
+
|
|
12
|
+
main : This function can decorate the main function of a script.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# %% Libraries
|
|
18
|
+
from corelp import debug, main
|
|
19
|
+
from time import sleep
|
|
20
|
+
import pytest
|
|
21
|
+
debug_folder = debug(__file__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# %% User inputs
|
|
26
|
+
search = False # True to apply manual search
|
|
27
|
+
global import_path
|
|
28
|
+
global export_path
|
|
29
|
+
import_path = None if search else debug_folder # Path to the imported data
|
|
30
|
+
export_path = import_path # Path to the exported data
|
|
31
|
+
new = False # True to create new result folder at each run
|
|
32
|
+
bulk = None # function(import_path) that returns a dictionnary of {import_subfolder:export_subfolder} for multiple decorated function run.
|
|
33
|
+
overnight= False # If True and exception occurs, will skip and pass to the next run in bulk processing.
|
|
34
|
+
myparam = "Hello from main!"
|
|
35
|
+
apply_error = False
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@main()
|
|
40
|
+
def mainfunc() :
|
|
41
|
+
if apply_error :
|
|
42
|
+
1/0
|
|
43
|
+
print(myparam)
|
|
44
|
+
result = section_1()
|
|
45
|
+
print(f"import_path = {import_path}")
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
@mainfunc.section()
|
|
49
|
+
def section_1() :
|
|
50
|
+
print('> Hello from section!')
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# %% Function test
|
|
56
|
+
def test_function() :
|
|
57
|
+
'''
|
|
58
|
+
Test main function
|
|
59
|
+
'''
|
|
60
|
+
mainfunc()
|
|
61
|
+
sleep(2) # Ensure new folder
|
|
62
|
+
mainfunc(myparam="Hello changed!!")
|
|
63
|
+
sleep(2) # Ensure new folder
|
|
64
|
+
mainfunc(new=True)
|
|
65
|
+
sleep(2) # Ensure new folder
|
|
66
|
+
with pytest.raises(ZeroDivisionError, match="division by zero") :
|
|
67
|
+
mainfunc(apply_error=True)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# %% Test function run
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
from corelp import test
|
|
74
|
+
test(__file__)
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Date : 2026-01-18
|
|
4
|
+
# Author : Lancelot PINCET
|
|
5
|
+
# GitHub : https://github.com/LancelotPincet
|
|
6
|
+
# Library : rootLP
|
|
7
|
+
# Module : menu
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
This module creates a menu on the side of the marimo window.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# %% Libraries
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
import marimo as mo
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# %% Function
|
|
22
|
+
def menu(file, name) :
|
|
23
|
+
'''
|
|
24
|
+
This module creates a menu on the side of the marimo window.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
file : str
|
|
29
|
+
path to notebook file.
|
|
30
|
+
name : str
|
|
31
|
+
name of project.
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
>>> from rootlp import menu
|
|
36
|
+
...
|
|
37
|
+
>>> menu(__file__)
|
|
38
|
+
'''
|
|
39
|
+
|
|
40
|
+
# Mode
|
|
41
|
+
if mo.app_meta().mode == "edit" :
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Folder
|
|
45
|
+
folder = Path(file).parents[1] # folder / notebooks / file.py
|
|
46
|
+
|
|
47
|
+
# Scripts
|
|
48
|
+
scripts = [notebook.stem for notebook in (folder / 'scripts').iterdir() if not notebook.name.startswith('__')]
|
|
49
|
+
scripts_menu = {f"/{name}/{script}": script for script in scripts}
|
|
50
|
+
|
|
51
|
+
# Figures
|
|
52
|
+
figures = [notebook.stem for notebook in (folder / 'figures').iterdir() if not notebook.name.startswith('__')]
|
|
53
|
+
figures_menu = {f"/{name}/{figure}": figure for figure in figures}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# Sidebar
|
|
57
|
+
sidebar = mo.sidebar(
|
|
58
|
+
[
|
|
59
|
+
mo.md(f"# {name}"),
|
|
60
|
+
mo.nav_menu(
|
|
61
|
+
{
|
|
62
|
+
f"/{name}/home": f"{mo.icon('lucide:home')} Home",
|
|
63
|
+
"Scripts": scripts_menu,
|
|
64
|
+
"Figures": figures_menu,
|
|
65
|
+
},
|
|
66
|
+
orientation="vertical",
|
|
67
|
+
),
|
|
68
|
+
]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return sidebar
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# %% Test function run
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
from corelp import test
|
|
78
|
+
test(__file__)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Date : 2026-01-18
|
|
4
|
+
# Author : Lancelot PINCET
|
|
5
|
+
# GitHub : https://github.com/LancelotPincet
|
|
6
|
+
# Library : rootLP
|
|
7
|
+
# Module : menu
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
This file allows to test menu
|
|
11
|
+
|
|
12
|
+
menu : This module creates a menu on the side of the marimo window.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# %% Libraries
|
|
18
|
+
from corelp import print, debug
|
|
19
|
+
import pytest
|
|
20
|
+
from rootlp import menu
|
|
21
|
+
debug_folder = debug(__file__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# %% Function test
|
|
26
|
+
def test_function() :
|
|
27
|
+
'''
|
|
28
|
+
Test menu function
|
|
29
|
+
'''
|
|
30
|
+
print('Hello world!')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# %% Instance fixture
|
|
35
|
+
@pytest.fixture()
|
|
36
|
+
def instance() :
|
|
37
|
+
'''
|
|
38
|
+
Create a new instance at each test function
|
|
39
|
+
'''
|
|
40
|
+
return menu()
|
|
41
|
+
|
|
42
|
+
def test_instance(instance) :
|
|
43
|
+
'''
|
|
44
|
+
Test on fixture
|
|
45
|
+
'''
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# %% Returns test
|
|
50
|
+
@pytest.mark.parametrize("args, kwargs, expected, message", [
|
|
51
|
+
#([], {}, None, ""),
|
|
52
|
+
([], {}, None, ""),
|
|
53
|
+
])
|
|
54
|
+
def test_returns(args, kwargs, expected, message) :
|
|
55
|
+
'''
|
|
56
|
+
Test menu return values
|
|
57
|
+
'''
|
|
58
|
+
assert menu(*args, **kwargs) == expected, message
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# %% Error test
|
|
63
|
+
@pytest.mark.parametrize("args, kwargs, error, error_message", [
|
|
64
|
+
#([], {}, None, ""),
|
|
65
|
+
([], {}, None, ""),
|
|
66
|
+
])
|
|
67
|
+
def test_errors(args, kwargs, error, error_message) :
|
|
68
|
+
'''
|
|
69
|
+
Test menu error values
|
|
70
|
+
'''
|
|
71
|
+
with pytest.raises(error, match=error_message) :
|
|
72
|
+
menu(*args, **kwargs)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# %% Test function run
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
from corelp import test
|
|
79
|
+
test(__file__)
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# Date : 2026-01-18
|
|
4
|
+
# Author : Lancelot PINCET
|
|
5
|
+
# GitHub : https://github.com/LancelotPincet
|
|
6
|
+
# Library : rootLP
|
|
7
|
+
# Module : mo
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
Marimo library shortcut.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# %% Libraries
|
|
16
|
+
import marimo as mo
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# %% Test function run
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
from corelp import test
|
|
23
|
+
test(__file__)
|