marimo-dev 0.3.0__tar.gz → 0.3.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: marimo-dev
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Build and publish python packages from marimo notebooks
5
5
  Author: Deufel
6
6
  Author-email: Deufel <MDeufel13@gmail.com>
@@ -13,13 +13,14 @@ Requires-Dist: mohtml>=0.1.11
13
13
  Requires-Dist: pydantic-ai-slim>=1.50.0
14
14
  Requires-Dist: pytest>=9.0.2
15
15
  Requires-Dist: python-fasthtml>=0.12.37
16
- Requires-Dist: youtube-transcript-api>=1.2.3
17
- Requires-Python: >=3.13
16
+ Requires-Python: >=3.12
18
17
  Project-URL: PyPI, https://pypi.org/project/marimo-dev/
19
18
  Project-URL: Repository, https://github.com/deufel/m-dev
20
19
  Description-Content-Type: text/markdown
21
20
 
22
21
  # marimo-dev
22
+ ![PyPI version](https://img.shields.io/pypi/v/marimo-dev)
23
+
23
24
 
24
25
  > [!WARNING]
25
26
  > This project is under active development and is not an official marimo tool - Mar 2026
@@ -1,4 +1,6 @@
1
1
  # marimo-dev
2
+ ![PyPI version](https://img.shields.io/pypi/v/marimo-dev)
3
+
2
4
 
3
5
  > [!WARNING]
4
6
  > This project is under active development and is not an official marimo tool - Mar 2026
@@ -4,10 +4,10 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "marimo-dev"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "Build and publish python packages from marimo notebooks"
9
9
  readme = "README.md"
10
- requires-python = ">=3.13"
10
+ requires-python = ">=3.12"
11
11
  dependencies = [
12
12
  "anthropic>=0.84.0",
13
13
  "fastcore>=1.10.0",
@@ -17,7 +17,6 @@ dependencies = [
17
17
  "pydantic-ai-slim>=1.50.0",
18
18
  "pytest>=9.0.2",
19
19
  "python-fasthtml>=0.12.37",
20
- "youtube-transcript-api>=1.2.3",
21
20
  ]
22
21
 
23
22
  [project.license]
@@ -1,5 +1,5 @@
1
1
  """Build and publish python packages from marimo notebooks"""
2
- __version__ = '0.3.0'
2
+ __version__ = '0.3.2'
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
@@ -21,10 +21,11 @@ def read_config(root='.'):
21
21
 
22
22
  class Kind(Enum):
23
23
  "Types of nodes in parsed code"
24
- IMP='import'
25
- CONST='const'
26
- SETUP='setup'
27
- EXP='export'
24
+ IMP="import"
25
+ CONST="const"
26
+ SETUP="setup"
27
+ EXP="export"
28
+ RAW="raw"
28
29
 
29
30
  @dataclass
30
31
  class Param:
@@ -23,7 +23,7 @@ def write_mod(
23
23
  "Write module file with imports, constants, and exports."
24
24
  g = {k: [n for n in nodes if n.kind == k] for k in Kind}
25
25
  imports = '\n'.join(rewrite_imports(n.src, mod_names) for n in g[Kind.IMP])
26
- parts = [imports, '\n'.join(n.src for n in g[Kind.CONST]), '\n'.join(n.src for n in g[Kind.SETUP]), '\n\n'.join(clean(n.src) for n in g[Kind.EXP])]
26
+ parts = [imports, '\n'.join(n.src for n in g[Kind.CONST]), '\n'.join(n.src for n in g[Kind.SETUP]), '\n\n'.join(clean(n.src) for n in g[Kind.EXP]), '\n\n'.join(n.src for n in g[Kind.RAW])]
27
27
  write(path, *parts)
28
28
 
29
29
  def rewrite_imports(
@@ -65,10 +65,16 @@ def check_pypi_auth(test=True):
65
65
  return {'found': True, 'section': section, 'username': config[section].get('username', ''), 'preview': preview, 'source': str(pypirc)}
66
66
 
67
67
  def publish(
68
- test:bool=True, # Use Test PyPI if True, real PyPI if False
68
+ test=True # When ready to upload to PyPi change this to false
69
69
  ):
70
- "Build and publish package to PyPI. Looks for ~/.pypirc for credentials, otherwise prompts."
71
-
70
+ "Build and publish package to PyPI."
71
+ platform = 'testpypi' if test else 'pypi'
72
+ auth = check_credentials(platform)
73
+ if not auth['found']:
74
+ print(f"❌ {auth['msg']}")
75
+ return
76
+
77
+ print(f"✅ Authenticated as {auth['username']} ({auth['preview']}) from {auth['source']}")
72
78
  print("Rebuilding package from notebooks...")
73
79
  build()
74
80
 
@@ -76,18 +82,24 @@ def publish(
76
82
  print("Building distribution...")
77
83
  subprocess.run(['uv', 'build'], check=True)
78
84
 
79
- pypirc, cmd = Path.home() / '.pypirc', ['uv', 'publish']
80
- section = 'testpypi' if test else 'pypi'
81
-
85
+ cmd = ['uv', 'publish']
82
86
  if test: cmd.extend(['--publish-url', 'https://test.pypi.org/legacy/'])
83
87
  else: cmd.extend(['--publish-url', 'https://upload.pypi.org/legacy/'])
84
88
 
85
- if pypirc.exists():
86
- config = configparser.ConfigParser()
87
- config.read(pypirc)
88
- if section in config:
89
- username, password = config[section].get('username', '__token__'), config[section].get('password', '')
90
- cmd.extend(['--username', username, '--password', password])
89
+ config = configparser.ConfigParser()
90
+ config.read(Path.home() / '.pypirc')
91
+ section = auth['section'] if 'section' in auth else platform
92
+ username = config[section].get('username', '__token__')
93
+ password = config[section].get('password', '')
94
+ cmd.extend(['--username', username, '--password', password])
91
95
 
92
- print(f"Publishing to {'Test ' if test else ''}PyPI...")
93
- subprocess.run(cmd, check=True)
96
+ target = 'Test PyPI' if test else 'PyPI'
97
+ print(f"Publishing to {target}...")
98
+ try:
99
+ subprocess.run(cmd, capture_output=True, text=True, check=True)
100
+ print(f"✅ Published to {target}!")
101
+ except subprocess.CalledProcessError as e:
102
+ if '403' in (e.stderr or ''):
103
+ print(f"❌ Auth failed. Token may be expired. Regenerate at {CREDENTIALS[platform]['url']}")
104
+ else:
105
+ print(f"❌ Publish failed: {e.stderr}")
@@ -124,8 +124,8 @@ def parse_node(
124
124
  for s in n.body:
125
125
  if (node := parse_import(s, ls)): yield node
126
126
  elif (node := parse_const(s, ls)): yield node
127
- else: yield Node(Kind.SETUP,
128
- getattr(s, 'name', '_setup'),
127
+ else: yield Node(Kind.SETUP,
128
+ getattr(s, 'name', '_setup'),
129
129
  ast.get_source_segment(src, s) or ast.unparse(s)
130
130
  )
131
131
  # Handle export-named cells (e.g. def export(): or def export_main():)
@@ -143,6 +143,15 @@ def parse_node(
143
143
  yield Node(Kind.EXP, n.name, src)
144
144
  return
145
145
 
146
+ # Handle raw escape hatch cells
147
+ if isinstance(n, ast.FunctionDef) and n.name.startswith('_'):
148
+ body_src = '\n'.join(ls[n.lineno-1:n.end_lineno])
149
+ if '#| raw' in body_src:
150
+ raw_lines = [l for l in body_src.splitlines() if not l.strip().startswith(('#|', '@app.', 'def _(', 'return'))]
151
+ raw_src = '\n'.join(raw_lines).strip()
152
+ if raw_src: yield Node(Kind.RAW, '_raw', raw_src)
153
+ return
154
+
146
155
  # Handle decorated exports
147
156
  if (node := parse_export(n, ls, cfg)): yield node
148
157