Skip to content

Commit

Permalink
[cards] PythonCode component
Browse files Browse the repository at this point in the history
- Metaflow Card component for rendering a syntax highlighed python function
- Driveby: moved `with_component_id` and `create_component_id` functions to `card.py`
- Example flow :

```
from metaflow import FlowSpec, step, card, current
from metaflow.cards import PythonCode

class PythonCodeDemoFlow(FlowSpec):
    @card
    @step
    def start(self):
        # Example 1: Using a function
        def sample_function():
            x = 1
            y = 2
            return x + y

        current.card.append(PythonCode(sample_function))

        # Example 2: Using a string
        code = """
def another_function():
    return "Hello World"
        """
        current.card.append(PythonCode(code_string=code))

        self.next(self.end)

    @step
    def end(self):
        pass

if __name__ == '__main__':
    PythonCodeDemoFlow()
```
  • Loading branch information
valayDave committed Jan 7, 2025
1 parent 950428e commit 9aef034
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 44 deletions.
1 change: 1 addition & 0 deletions metaflow/cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Markdown,
VegaChart,
ProgressBar,
PythonCode,
)
from metaflow.plugins.cards.card_modules.basic import (
DefaultCard,
Expand Down
21 changes: 20 additions & 1 deletion metaflow/plugins/cards/card_modules/basic.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base64
import json
import os
from .card import MetaflowCard, MetaflowCardComponent
from .card import MetaflowCard, MetaflowCardComponent, with_default_component_id
from .convert_to_native_type import TaskToDict
import uuid

Expand Down Expand Up @@ -236,9 +236,28 @@ def __init__(self, data=None):
super().__init__(title=None, subtitle=None)
self._data = data

@with_default_component_id
def render(self):
datadict = super().render()
datadict["data"] = self._data
if self.component_id is not None:
datadict["id"] = self.component_id
return datadict


class PythonCodeComponent(DefaultComponent):

type = "pythonCode"

def __init__(self, data=None):
super().__init__(title=None, subtitle=None)
self._data = data

def render(self):
datadict = super().render()
datadict["data"] = self._data
if self.component_id is not None:
datadict["id"] = self.component_id
return datadict


Expand Down
15 changes: 15 additions & 0 deletions metaflow/plugins/cards/card_modules/card.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import TYPE_CHECKING
import uuid

if TYPE_CHECKING:
import metaflow
Expand Down Expand Up @@ -140,3 +141,17 @@ def render(self):
`render` returns a string or dictionary. This class can be called on the client side to dynamically add components to the `MetaflowCard`
"""
raise NotImplementedError()


def create_component_id(component):
uuid_bit = "".join(uuid.uuid4().hex.split("-"))[:6]
return type(component).__name__.lower() + "_" + uuid_bit


def with_default_component_id(func):
def ret_func(self, *args, **kwargs):
if self.component_id is None:
self.component_id = create_component_id(self)
return func(self, *args, **kwargs)

return ret_func
74 changes: 59 additions & 15 deletions metaflow/plugins/cards/card_modules/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,13 @@
ImageComponent,
SectionComponent,
MarkdownComponent,
PythonCodeComponent,
)
from .card import MetaflowCardComponent
from .card import MetaflowCardComponent, with_default_component_id
from .convert_to_native_type import TaskToDict, _full_classname
from .renderer_tools import render_safely
import uuid


def create_component_id(component):
uuid_bit = "".join(uuid.uuid4().hex.split("-"))[:6]
return type(component).__name__.lower() + "_" + uuid_bit


def with_default_component_id(func):
def ret_func(self, *args, **kwargs):
if self.component_id is None:
self.component_id = create_component_id(self)
return func(self, *args, **kwargs)

return ret_func
import inspect


def _warning_with_component(component, msg):
Expand Down Expand Up @@ -823,3 +811,59 @@ def render(self):
if self._chart_inside_table and "autosize" not in self._spec:
data["spec"]["autosize"] = "fit-x"
return data


class PythonCode(UserComponent):
"""
A component to display Python code with syntax highlighting.
Example:
```python
@card
@step
def my_step(self):
# Using code_func
def my_function():
x = 1
y = 2
return x + y
current.card.append(
PythonCode(my_function)
)
# Using code_string
code = '''
def another_function():
return "Hello World"
'''
current.card.append(
PythonCode(code_string=code)
)
```
Parameters
----------
code_func : function, optional
The function whose source code should be displayed.
code_string : str, optional
A string containing Python code to display.
Either code_func or code_string must be provided.
"""

def __init__(self, code_func=None, code_string=None):
if code_func is not None:
self._code_string = inspect.getsource(code_func)
else:
self._code_string = code_string

@with_default_component_id
@render_safely
def render(self):
if self._code_string is None:
return ErrorComponent(
"`PythonCode` component requires a `code_func` or `code_string` argument. ",
"None provided for both",
).render()
_code_component = PythonCodeComponent(self._code_string)
_code_component.component_id = self.component_id
return _code_component.render()
52 changes: 27 additions & 25 deletions metaflow/plugins/cards/card_modules/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion metaflow/plugins/cards/component_serializer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from .card_modules import MetaflowCardComponent
from .card_modules.card import create_component_id
from .card_modules.basic import ErrorComponent, SectionComponent
from .card_modules.components import (
UserComponent,
create_component_id,
StubComponent,
)
from .exception import ComponentOverwriteNotSupportedException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import Text from "./text.svelte";
import Title from "./title.svelte";
import VegaChart from "./vega-chart.svelte";
import PythonCode from "./python-code.svelte";
export let componentData: types.CardComponent;
Expand All @@ -33,6 +34,7 @@
text: Text,
title: Title,
vegaChart: VegaChart,
pythonCode: PythonCode,
};
let component = typesMap?.[componentData.type];
Expand Down
19 changes: 19 additions & 0 deletions metaflow/plugins/cards/ui/src/components/python-code.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- This component will render a simple log with syntax highlighting -->
<script lang="ts">
import type * as types from "../types";
export let componentData: types.PythonCodeComponent;
let el: HTMLElement;
function highlightCode() {
el && (window as any)?.Prism?.highlightElement(el, );
}
$: el ? highlightCode() : null;
</script>

<!-- This needs to be in this exact format of <pre><code> ... without a new line between the <pre> and <code> tags -->
<!-- Need to do this to avoid weird indentation issues -->
<!-- based on https://github.com/PrismJS/prism/issues/554#issuecomment-83197995 -->
<pre data-component="pythonCode"><code class="language-python" bind:this={el}>{componentData.data}
</code>
</pre>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import Markdown from "./markdown.svelte";
import Text from "./text.svelte";
import ProgressBar from "./progress-bar.svelte";
import PythonCode from "./python-code.svelte";
export let componentData: types.TableDataCell;
let component: ComponentType;
Expand All @@ -25,6 +26,7 @@
progressBar: ProgressBar,
text: Text,
vegaChart: VegaChart,
pythonCode: PythonCode,
};
const type = (componentData as types.CardComponent)?.type;
Expand Down
12 changes: 10 additions & 2 deletions metaflow/plugins/cards/ui/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export type TableDataCell =
| MarkdownComponent
| ProgressBarComponent
| TextComponent
| VegaChartComponent;
| VegaChartComponent
| PythonCodeComponent;

export type TableColumns = string[];
export type TableData = TableDataCell[][];
Expand Down Expand Up @@ -179,6 +180,12 @@ export interface LogComponent {
data: string;
}

export interface PythonCodeComponent {
type: "pythonCode";
id?: string;
data: string;
}

export interface MarkdownComponent {
type: "markdown";
id?: string;
Expand Down Expand Up @@ -207,4 +214,5 @@ export type CardComponent =
| TableComponent
| TextComponent
| TitleComponent
| VegaChartComponent;
| VegaChartComponent
| PythonCodeComponent;

0 comments on commit 9aef034

Please sign in to comment.