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.
Files changed (36) hide show
  1. rootlp/__init__.py +23 -0
  2. rootlp/modules/Section_LP/Section.py +177 -0
  3. rootlp/modules/Section_LP/__init__.py +0 -0
  4. rootlp/modules/Section_LP/test_Section.py +44 -0
  5. rootlp/modules/__init__.py +0 -0
  6. rootlp/modules/getfunction_LP/__init__.py +0 -0
  7. rootlp/modules/getfunction_LP/getfunction.py +97 -0
  8. rootlp/modules/getfunction_LP/test_getfunction.py +79 -0
  9. rootlp/modules/main_LP/__init__.py +0 -0
  10. rootlp/modules/main_LP/main.py +437 -0
  11. rootlp/modules/main_LP/test_main.py +74 -0
  12. rootlp/modules/menu_LP/__init__.py +0 -0
  13. rootlp/modules/menu_LP/menu.py +78 -0
  14. rootlp/modules/menu_LP/test_menu.py +79 -0
  15. rootlp/modules/mo_LP/__init__.py +0 -0
  16. rootlp/modules/mo_LP/mo.py +23 -0
  17. rootlp/modules/mo_LP/test_mo.py +79 -0
  18. rootlp/modules/print_LP/__init__.py +0 -0
  19. rootlp/modules/print_LP/print.py +337 -0
  20. rootlp/modules/print_LP/test_print.py +109 -0
  21. rootlp/modules/project_server_LP/__init__.py +0 -0
  22. rootlp/modules/project_server_LP/project_server.py +86 -0
  23. rootlp/modules/project_server_LP/test_project_server.py +79 -0
  24. rootlp/modules/readme_string_LP/__init__.py +0 -0
  25. rootlp/modules/readme_string_LP/readme_string.py +53 -0
  26. rootlp/modules/readme_string_LP/test_readme_string.py +79 -0
  27. rootlp/modules/user_inputs_LP/__init__.py +0 -0
  28. rootlp/modules/user_inputs_LP/test_user_inputs.py +46 -0
  29. rootlp/modules/user_inputs_LP/user_inputs.py +103 -0
  30. rootlp/modules.json +56 -0
  31. rootlp/py.typed +0 -0
  32. rootlp/scripts/__init__.py +0 -0
  33. rootlp/scripts.json +1 -0
  34. rootlp-0.1.7.dist-info/METADATA +41 -0
  35. rootlp-0.1.7.dist-info/RECORD +36 -0
  36. 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__)