marimo-dev 0.2.1__tar.gz → 0.2.3__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.
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/PKG-INFO +1 -1
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/pyproject.toml +1 -1
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/src/marimo_dev/__init__.py +6 -4
- marimo_dev-0.2.3/src/marimo_dev/build.py +98 -0
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/src/marimo_dev/read.py +19 -0
- marimo_dev-0.2.1/src/marimo_dev/build.py +0 -40
- marimo_dev-0.2.1/src/marimo_dev/cli.py +0 -21
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/README.md +0 -0
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/src/marimo_dev/core.py +0 -0
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/src/marimo_dev/docs.py +0 -0
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/src/marimo_dev/pkg.py +0 -0
- {marimo_dev-0.2.1 → marimo_dev-0.2.3}/src/marimo_dev/publish.py +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"""Build and publish python packages from marimo notebooks"""
|
|
2
|
-
__version__ = '0.2.
|
|
2
|
+
__version__ = '0.2.3'
|
|
3
3
|
__author__ = 'Deufel'
|
|
4
4
|
from .core import Config, read_config, Kind, Param, Node
|
|
5
5
|
from .read import inline_doc, parse_params, parse_hash_pipe, parse_class_params, parse_class_methods, parse_ret, src_with_decs, is_export, parse_import, parse_const, parse_export, parse_node, parse_file, read_meta, nb_name, scan
|
|
6
6
|
from .pkg import clean, write, write_mod, rewrite_imports, write_init
|
|
7
7
|
from .docs import cls_sig, fn_sig, sig, write_llms, exp_type, render_param, nb_path, render_node, render_module_page, build_docs, export_wasm, write_nojekyll, html_preview, render_index_page, Icon
|
|
8
|
-
from .build import build, tidy, nuke
|
|
8
|
+
from .build import build, tidy, nuke, get_pypi_name, extract_import_names, pep723_header, bundle
|
|
9
9
|
from .publish import publish
|
|
10
|
-
from .cli import main
|
|
11
10
|
__all__ = [
|
|
12
11
|
"Config",
|
|
13
12
|
"Icon",
|
|
@@ -16,15 +15,17 @@ __all__ = [
|
|
|
16
15
|
"Param",
|
|
17
16
|
"build",
|
|
18
17
|
"build_docs",
|
|
18
|
+
"bundle",
|
|
19
19
|
"clean",
|
|
20
20
|
"cls_sig",
|
|
21
21
|
"exp_type",
|
|
22
22
|
"export_wasm",
|
|
23
|
+
"extract_import_names",
|
|
23
24
|
"fn_sig",
|
|
25
|
+
"get_pypi_name",
|
|
24
26
|
"html_preview",
|
|
25
27
|
"inline_doc",
|
|
26
28
|
"is_export",
|
|
27
|
-
"main",
|
|
28
29
|
"nb_name",
|
|
29
30
|
"nb_path",
|
|
30
31
|
"nuke",
|
|
@@ -38,6 +39,7 @@ __all__ = [
|
|
|
38
39
|
"parse_node",
|
|
39
40
|
"parse_params",
|
|
40
41
|
"parse_ret",
|
|
42
|
+
"pep723_header",
|
|
41
43
|
"publish",
|
|
42
44
|
"read_config",
|
|
43
45
|
"read_meta",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from .core import Kind, Param, Node, Config, read_config
|
|
2
|
+
from .read import scan, read_meta
|
|
3
|
+
from .pkg import write_mod, write_init, clean
|
|
4
|
+
from .docs import write_llms
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import ast, shutil, re
|
|
7
|
+
|
|
8
|
+
IMPORT_TO_PYPI = {'bs4': 'beautifulsoup4', 'PIL': 'pillow', 'cv2': 'opencv-python', 'sklearn': 'scikit-learn', 'yaml': 'pyyaml'}
|
|
9
|
+
|
|
10
|
+
def build(
|
|
11
|
+
root='.', # root directory containing pyproject.toml
|
|
12
|
+
)->str: # path to built package
|
|
13
|
+
"Build a Python package from notebooks."
|
|
14
|
+
cfg = read_config(root)
|
|
15
|
+
meta, mods = scan(root)
|
|
16
|
+
mod_names = [name for name, _ in mods]
|
|
17
|
+
pkg = Path(root) / cfg.out / meta['name'].replace('-', '_')
|
|
18
|
+
if pkg.exists(): shutil.rmtree(pkg)
|
|
19
|
+
pkg.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
for name, nodes in mods:
|
|
21
|
+
stripped = re.sub(r'^[a-z]_', '', name)
|
|
22
|
+
if stripped != 'index' and any(n.kind == Kind.EXP for n in nodes): write_mod(pkg/f'{stripped}.py', nodes, mod_names)
|
|
23
|
+
write_init(pkg/'__init__.py', meta, mods)
|
|
24
|
+
all_exp = [n for _, nodes in mods for n in nodes if n.kind == Kind.EXP]
|
|
25
|
+
if all_exp: write_llms(meta, all_exp)
|
|
26
|
+
return str(pkg)
|
|
27
|
+
|
|
28
|
+
def tidy():
|
|
29
|
+
"Remove cache and temporary files (__pycache__, __marimo__, .pytest_cache, etc)."
|
|
30
|
+
import shutil
|
|
31
|
+
for p in Path('.').rglob('__pycache__'): shutil.rmtree(p, ignore_errors=True)
|
|
32
|
+
for p in Path('.').rglob('__marimo__'): shutil.rmtree(p, ignore_errors=True)
|
|
33
|
+
for p in Path('.').rglob('.pytest_cache'): shutil.rmtree(p, ignore_errors=True)
|
|
34
|
+
for p in Path('.').rglob('*.pyc'): p.unlink(missing_ok=True)
|
|
35
|
+
print("Cleaned cache files")
|
|
36
|
+
|
|
37
|
+
def nuke():
|
|
38
|
+
"Remove all build artifacts (dist, docs, src) and cache files."
|
|
39
|
+
import shutil
|
|
40
|
+
tidy()
|
|
41
|
+
for d in ['dist', 'docs', 'src', 'temp']: shutil.rmtree(d, ignore_errors=True)
|
|
42
|
+
print("Nuked build artifacts")
|
|
43
|
+
|
|
44
|
+
def get_pypi_name(import_name):
|
|
45
|
+
"Map import name to PyPI package name."
|
|
46
|
+
root = import_name.split('.')[0]
|
|
47
|
+
return IMPORT_TO_PYPI.get(root, root)
|
|
48
|
+
|
|
49
|
+
def extract_import_names(nodes):
|
|
50
|
+
"Extract top-level module names from import nodes."
|
|
51
|
+
names = set()
|
|
52
|
+
for n in nodes:
|
|
53
|
+
if n.kind != Kind.IMP: continue
|
|
54
|
+
tree = ast.parse(n.src)
|
|
55
|
+
for stmt in ast.walk(tree):
|
|
56
|
+
if isinstance(stmt, ast.Import):
|
|
57
|
+
for alias in stmt.names:
|
|
58
|
+
names.add(alias.name.split('.')[0])
|
|
59
|
+
elif isinstance(stmt, ast.ImportFrom) and stmt.module:
|
|
60
|
+
names.add(stmt.module.split('.')[0])
|
|
61
|
+
return names
|
|
62
|
+
|
|
63
|
+
def pep723_header(deps):
|
|
64
|
+
"Generate PEP 723 inline script metadata."
|
|
65
|
+
deps_str = ', '.join(f'"{d}"' for d in sorted(deps))
|
|
66
|
+
return f'# /// script\n# dependencies = [{deps_str}]\n# ///\n'
|
|
67
|
+
|
|
68
|
+
def bundle(root='.', name=None):
|
|
69
|
+
"Bundle all notebooks into a single Python file with PEP 723 dependencies."
|
|
70
|
+
cfg = read_config(root)
|
|
71
|
+
meta, mods = scan(root)
|
|
72
|
+
|
|
73
|
+
# Collect all nodes
|
|
74
|
+
all_nodes = [n for _, nodes in mods for n in nodes]
|
|
75
|
+
|
|
76
|
+
# Extract dependencies
|
|
77
|
+
import_names = extract_import_names(all_nodes)
|
|
78
|
+
# Filter out stdlib and local modules
|
|
79
|
+
mod_names = [m for m, _ in mods]
|
|
80
|
+
external = {get_pypi_name(n) for n in import_names if n not in mod_names}
|
|
81
|
+
|
|
82
|
+
# Build output
|
|
83
|
+
header = pep723_header(external)
|
|
84
|
+
imports = '\n'.join(n.src for n in all_nodes if n.kind == Kind.IMP)
|
|
85
|
+
consts = '\n'.join(n.src for n in all_nodes if n.kind == Kind.CONST)
|
|
86
|
+
exports = '\n\n'.join(clean(n.src) for n in all_nodes if n.kind == Kind.EXP)
|
|
87
|
+
|
|
88
|
+
content = '\n\n'.join(p for p in [header, imports, consts, exports] if p.strip())
|
|
89
|
+
|
|
90
|
+
# Determine output path
|
|
91
|
+
if name:
|
|
92
|
+
out_path = Path(root) / name
|
|
93
|
+
else:
|
|
94
|
+
out_path = Path(root) / cfg.out / meta['name'] / '__init__.py'
|
|
95
|
+
|
|
96
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
out_path.write_text(content)
|
|
98
|
+
return f"Bundled to {out_path}"
|
|
@@ -118,10 +118,29 @@ def parse_node(
|
|
|
118
118
|
): # yields Node objects for imports, constants, and exports
|
|
119
119
|
"Extract importable nodes from an AST node."
|
|
120
120
|
ls = src.splitlines()
|
|
121
|
+
|
|
122
|
+
# Handle setup cells
|
|
121
123
|
if isinstance(n, ast.With):
|
|
122
124
|
for s in n.body:
|
|
123
125
|
if (node := parse_import(s, ls)): yield node
|
|
124
126
|
if (node := parse_const(s, ls)): yield node
|
|
127
|
+
|
|
128
|
+
# Handle export-named cells (e.g. def export(): or def export_main():)
|
|
129
|
+
if isinstance(n, ast.FunctionDef) and n.name.startswith('export'):
|
|
130
|
+
# Check it's decorated with @app.cell, not @app.function
|
|
131
|
+
is_cell = any(
|
|
132
|
+
(isinstance(d, ast.Attribute) and d.attr == 'cell') or
|
|
133
|
+
(isinstance(d, ast.Name) and d.id == 'cell')
|
|
134
|
+
for d in n.decorator_list
|
|
135
|
+
)
|
|
136
|
+
if is_cell:
|
|
137
|
+
body = [s for s in n.body if not isinstance(s, ast.Return)]
|
|
138
|
+
if body:
|
|
139
|
+
src = '\n\n'.join(ast.unparse(s) for s in body)
|
|
140
|
+
yield Node(Kind.EXP, n.name, src)
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
# Handle decorated exports
|
|
125
144
|
if (node := parse_export(n, ls, cfg)): yield node
|
|
126
145
|
|
|
127
146
|
def parse_file(
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
from .core import Kind, Param, Node, Config, read_config
|
|
2
|
-
from .read import scan, read_meta
|
|
3
|
-
from .pkg import write_mod, write_init
|
|
4
|
-
from .docs import write_llms
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
import ast, shutil, re
|
|
7
|
-
|
|
8
|
-
def build(
|
|
9
|
-
root='.', # root directory containing pyproject.toml
|
|
10
|
-
)->str: # path to built package
|
|
11
|
-
"Build a Python package from notebooks."
|
|
12
|
-
cfg = read_config(root)
|
|
13
|
-
meta, mods = scan(root)
|
|
14
|
-
mod_names = [name for name, _ in mods]
|
|
15
|
-
pkg = Path(root) / cfg.out / meta['name'].replace('-', '_')
|
|
16
|
-
if pkg.exists(): shutil.rmtree(pkg)
|
|
17
|
-
pkg.mkdir(parents=True, exist_ok=True)
|
|
18
|
-
for name, nodes in mods:
|
|
19
|
-
stripped = re.sub(r'^[a-z]_', '', name)
|
|
20
|
-
if stripped != 'index' and any(n.kind == Kind.EXP for n in nodes): write_mod(pkg/f'{stripped}.py', nodes, mod_names)
|
|
21
|
-
write_init(pkg/'__init__.py', meta, mods)
|
|
22
|
-
all_exp = [n for _, nodes in mods for n in nodes if n.kind == Kind.EXP]
|
|
23
|
-
if all_exp: write_llms(meta, all_exp)
|
|
24
|
-
return str(pkg)
|
|
25
|
-
|
|
26
|
-
def tidy():
|
|
27
|
-
"Remove cache and temporary files (__pycache__, __marimo__, .pytest_cache, etc)."
|
|
28
|
-
import shutil
|
|
29
|
-
for p in Path('.').rglob('__pycache__'): shutil.rmtree(p, ignore_errors=True)
|
|
30
|
-
for p in Path('.').rglob('__marimo__'): shutil.rmtree(p, ignore_errors=True)
|
|
31
|
-
for p in Path('.').rglob('.pytest_cache'): shutil.rmtree(p, ignore_errors=True)
|
|
32
|
-
for p in Path('.').rglob('*.pyc'): p.unlink(missing_ok=True)
|
|
33
|
-
print("Cleaned cache files")
|
|
34
|
-
|
|
35
|
-
def nuke():
|
|
36
|
-
"Remove all build artifacts (dist, docs, src) and cache files."
|
|
37
|
-
import shutil
|
|
38
|
-
tidy()
|
|
39
|
-
for d in ['dist', 'docs', 'src', 'temp']: shutil.rmtree(d, ignore_errors=True)
|
|
40
|
-
print("Nuked build artifacts")
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from .build import build, tidy, nuke
|
|
2
|
-
from .publish import publish
|
|
3
|
-
from .docs import build_docs
|
|
4
|
-
import sys, subprocess
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
def main():
|
|
8
|
-
if len(sys.argv) < 2: print("Usage: md [build|publish|docs|tidy|nuke]"); sys.exit(1)
|
|
9
|
-
cmd = sys.argv[1]
|
|
10
|
-
if cmd == 'build':
|
|
11
|
-
print(f"Built package at: {build()}")
|
|
12
|
-
print(build_docs())
|
|
13
|
-
elif cmd == 'publish':
|
|
14
|
-
test = '--test' in sys.argv or '-t' in sys.argv
|
|
15
|
-
target = "TestPyPI" if test else "PyPI"
|
|
16
|
-
if input(f"Publish to {target}? [y/N] ").lower() != 'y': print("Aborted"); sys.exit(0)
|
|
17
|
-
publish(test=test)
|
|
18
|
-
elif cmd == 'docs': build_docs()
|
|
19
|
-
elif cmd == 'tidy': tidy()
|
|
20
|
-
elif cmd == 'nuke': nuke()
|
|
21
|
-
else: print(f"Unknown command: {cmd}"); sys.exit(1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|