Convert an AST node to a CellDef.
(
self, node: Node, attribute: str | None = None
)
| 413 | ) |
| 414 | |
| 415 | def to_cell( |
| 416 | self, node: Node, attribute: str | None = None |
| 417 | ) -> ParseResult[CellDef]: |
| 418 | """Convert an AST node to a CellDef.""" |
| 419 | violations: list[Violation] = [] |
| 420 | if isinstance( |
| 421 | node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef) |
| 422 | ): |
| 423 | decorator = get_valid_decorator(node) |
| 424 | kwargs, _violations = _maybe_kwargs(decorator) |
| 425 | violations.extend(_violations) |
| 426 | if attribute is None and decorator is not None: |
| 427 | if isinstance(decorator, ast.Call): |
| 428 | if not hasattr(decorator.func, "attr"): |
| 429 | raise MarimoFileError( |
| 430 | "Invalid decorator, expected form `@app.fn`" |
| 431 | ) |
| 432 | attribute = decorator.func.attr |
| 433 | else: |
| 434 | attribute = decorator.attr |
| 435 | |
| 436 | # switch on app.cell vs app.function |
| 437 | if attribute == "cell": |
| 438 | assert isinstance( |
| 439 | node, (ast.FunctionDef, ast.AsyncFunctionDef) |
| 440 | ), "@app.cell cannot be used on classes." |
| 441 | cell_result = self.to_cell_def(node, kwargs) |
| 442 | violations.extend(cell_result.violations) |
| 443 | return ParseResult(cell_result.unwrap(), violations=violations) |
| 444 | cell_types: dict[str | None, type[CellDef]] = { |
| 445 | "function": FunctionCell, |
| 446 | "class_definition": ClassCell, |
| 447 | } |
| 448 | cell_type = cell_types.get(attribute, None) |
| 449 | if cell_type is not None: |
| 450 | code_result = self.extract_from_code(node) |
| 451 | violations.extend(code_result.violations) |
| 452 | return ParseResult( |
| 453 | cell_type( |
| 454 | code=code_result.unwrap(), |
| 455 | _ast=node, |
| 456 | options=kwargs, |
| 457 | ), |
| 458 | violations=violations, |
| 459 | ) |
| 460 | |
| 461 | raise MarimoFileError(f"Unsupported cell type. {attribute}") |
| 462 | elif is_unparsable_cell(node): |
| 463 | # These are all captured by is_unparsable_cell |
| 464 | # but mypy is struggling. |
| 465 | kwargs, _violations = _eval_kwargs(node.value.keywords) # type: ignore |
| 466 | violations.extend(_violations) |
| 467 | raw_string_node = node.value.args[0] # type: ignore |
| 468 | if not isinstance(raw_string_node, ast.Constant) or not isinstance( |
| 469 | raw_string_node.value, str |
| 470 | ): |
| 471 | violations.append( |
| 472 | Violation( |
no test coverage detected