Zero-True: A collaborative notebook built for data scientists
import os
import sys
import shutil

sys.path.insert(0, os.path.abspath('../'))

# Create the _static directory if it doesn't exist

project = 'Zero-True'

extensions = [
    'sphinxcontrib.autodoc_pydantic',
]

html_theme = 'furo'

html_title = 'Zero-True documentation'  # Change this line to set your title

html_favicon = '../zt_frontend/public/favicon.ico'

html_css_files = ['custom_theme.css']

docs/conf.py
Zero-True/zero-true
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.components.slider import Slider
from zt_backend.models.components.text_input import TextInput
from zt_backend.models.components.text_area_input import TextArea
from zt_backend.models.components.range_slider import RangeSlider
from zt_backend.models.components.selectbox import SelectBox
from zt_backend.models.components.multiselect import MultiSelectBox
from zt_backend.models.components.button import Button
from zt_backend.models.components.number_input import NumberInput
from zt_backend.models.components.image import Image
from zt_backend.models.components.text import Text
from zt_backend.models.components.dataframe import DataFrame
from zt_backend.models.components.layout import Column,Row,Layout
from zt_backend.models.components.matplotlib import Matplotlib
from zt_backend.models.components.plotly import  PlotlyComponent
from zt_backend.models.components.autocomplete import  Autocomplete
from zt_backend.models.components.card import Card
zero_true/__init__.py
Zero-True/zero-true
from pydantic_settings import BaseSettings
from functools import lru_cache

class Settings(BaseSettings):
    run_mode: str = "app"

@lru_cache()
def get_settings():
    return Settings()

settings = get_settings()
zt_backend/config.py
Zero-True/zero-true
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from typing import OrderedDict
from zt_backend.models.notebook import Notebook, CodeCell
from zt_backend.config import settings
import zt_backend.router as router
import toml
import os
import uuid

app = FastAPI()

current_path = os.path.dirname(os.path.abspath(__file__))


run_mode = settings.run_mode
project_name = ''

route_prefix = ''
if project_name:
    route_prefix = '/'+project_name+'/'+run_mode

app.include_router(router.router, prefix=route_prefix)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,
    allow_methods=["*"],
    allow_headers=["*"],
    expose_headers=["*"]
)

@app.on_event("startup")
def open_project():
    if not os.path.exists('notebook.toml'):
        codeCell = CodeCell(
            id=str(uuid.uuid4()),
            code='',
            components=[],
            variable_name='',
            output='',
            cellType='code'
        )
        zt_notebook = Notebook(userId='', cells=OrderedDict([(codeCell.id, codeCell)]))
        with open('notebook.toml', "w") as project_file:
            toml.dump(zt_notebook.model_dump(), project_file)

if run_mode=='app':
    app.mount(route_prefix, StaticFiles(directory=os.path.join(current_path, "dist_app"), html=True), name="assets")
else:
    app.mount(route_prefix, StaticFiles(directory=os.path.join(current_path, "dist_dev"), html=True), name="assets")
zt_backend/main.py
Zero-True/zero-true
from pydantic import Field, validator, field_validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.validations import validate_color
from typing import List, Union
from zt_backend.models.state import component_values

class Autocomplete(ZTComponent):
    """A class for Autocomplete components inheriting from ZTComponent."""
    
    component: str = Field("v-autocomplete", description="Vue component name.")
    items: List[Union[str, int]] = Field(..., description="Options for the autocomplete box. Can be a list of strings or integers.")
    value: Union[str, int,None] = Field("", description="Selected option for the autocomplete box. Can be a string or integer.")
    label: str = Field(None, description="Label for the autocomplete box.")
    multiple: bool = Field(False, description="Determines if multiple selections are allowed.")
    clearable: bool = Field(None, description="Determines if the autocomplete box has a clearable option.")
    disabled: bool = Field(None, description="Determines if the autocomplete box is disabled.")
    readonly: bool = Field(None, description="Determines if the autocomplete box is read-only.")
    color: str = Field(None, pre=True, description="Color of the autocomplete component. Can be custom or standard Material color.")
    triggerEvent: str = Field('update:modelValue',description="Trigger event for when to run based on the selected value")

    @field_validator('color')
    def validate_color(cls, color):
        return validate_color(color)

    @validator('value', always=True) #TODO: debug and replace with field validator
    def get_value_from_global_state(cls, value, values):
        id = values['id'] # Get the id if it exists in the field values
        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            e
        return value  # If id doesn't exist in global_state, return the original value
zt_backend/models/components/autocomplete.py
Zero-True/zero-true
from pydantic import Field, validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.state import component_values

class Button(ZTComponent):
    """A class for Button components inheriting from ZTComponent."""
    component: str = Field("v-btn", description="Vue component name.")
    value: bool = Field (False, description="Whether the button has been clicked")
    text: str = Field("Click Me", description="Label displayed on the button.")
    color: str = Field("primary", description="Color of the button.")
    disabled: bool = Field(False, description="If true, the button is disabled.")
    outlined: bool = Field(False, description="If true, the button will have an outlined style.")
    triggerEvent: str = Field("click", description="Trigger event to send code to the backend")

    @validator('value', always=True)
    def get_label_from_global_state(cls, value, values):
        id = values.get('id')  # Get the id if it exists in the field values
        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            pass  # Handle exception as needed
        return (value)  # If id doesn't exist in global_state, return the original value

zt_backend/models/components/button.py
Zero-True/zero-true
from pydantic import Field
from typing import List
from zt_backend.models.components.zt_component import ZTComponent

class Card(ZTComponent):
    """A class for Card components inheriting from ZTComponent."""
    
    component: str = Field("v-card", description="Vue component name.")
    cardChildren: List[str] = Field([], description="List of child components within the card.")
    color: str = Field(None, description="Background color of the card.")
    elevation: int = Field(None, ge=0, le=24, description="Elevation level of the card. Must be between 0 and 24.")
    density: str = Field(None, enum=['default','comfortable','compact'], description="Density of the component")
    width: int = Field(None, description="Width of the card.")
    location: str = Field(None, enum=['center'])
zt_backend/models/components/card.py
Zero-True/zero-true
import pandas as pd
from pydantic import Field, validator, BaseModel

from zt_backend.models.components.zt_component import ZTComponent
from typing import List, Dict,Any


class Header(BaseModel):
    title: str
    align: str = Field("start", description="Column alignment.")
    key: str = 'name'

class DataFrame(ZTComponent):
    component: str = "v-data-table"
    headers: List[Header] 
    items: List[Dict[str, Any]]

    @classmethod
    def from_dataframe(cls, df: pd.DataFrame, id: str):
        headers = [{"title": col, "key": col} for col in df.columns]
        items = df.to_dict(orient='records')
        return cls(id=id, headers=headers, items=items)
zt_backend/models/components/dataframe.py
Zero-True/zero-true
from pydantic import Field
from zt_backend.models.components.zt_component import ZTComponent

class Image(ZTComponent):
    """A class for Image components inheriting from ZTComponent."""
    component: str = Field("v-img", description="Vue component name.")
    src: str = Field(..., description="Source URL of the image.")
    alt: str = Field(None, description="Alternative text for the image.")
    width: int = Field(None, description="Width of the image.")
    height: int = Field(None, description="Height of the image.")
zt_backend/models/components/image.py
Zero-True/zero-true
from pydantic import BaseModel,Field
from typing import ForwardRef,Union
from zt_backend.models.state import current_cell_layout,context_globals
from typing import List

Row = ForwardRef('Row')
Column = ForwardRef('Column')

class Column(BaseModel):
    components: List[Union[str,"Row"]] = Field([], description="List of component IDs that belong to this column")
    width: Union[int,bool] = Field(False)
    

class Row(BaseModel):
    components: List[Union[str,Column]] = Field([], description="List of columns that belong to this row")


class Layout(BaseModel):
    rows: List[Row] = Field([], description="List of rows in this layout")
    columns: List[Column] = Field([], description="List of columns in this layout")

    def __init__(self, **data):
        super().__init__(**data)
        if context_globals['exec_mode']:
            current_cell_layout.append(self)
zt_backend/models/components/layout.py
Zero-True/zero-true
from pydantic import Field
from zt_backend.models.components.zt_component import ZTComponent
import matplotlib.pyplot as plt
import base64
from io import BytesIO

class Matplotlib(ZTComponent):
    component: str = Field("v-img", description="Vue component name.")
    src: str = Field(..., description="Source URL of the image.")
    alt: str = Field(None, description="Alternative text for the image.")
    width: int = Field(None, description="Width of the image.")
    height: int = Field(None, description="Height of the image.")
    
    @classmethod
    def from_matplotlib(cls,id: str, figure: plt.Figure, alt=None, width=None, height=None):
        plt.style.use('dark_background')
        buffer = BytesIO()
        figure.savefig(buffer, format="png")
        buffer.seek(0)
        base64_data = base64.b64encode(buffer.read()).decode()
        src = f"data:image/png;base64,{base64_data}"
        return cls(id=id,src=src, alt=alt, width=width, height=height)
zt_backend/models/components/matplotlib.py
Zero-True/zero-true
from pydantic import Field, validator,field_validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.validations import validate_color
from typing import List, Union
from zt_backend.models.state import component_values

class MultiSelectBox(ZTComponent):
    """A class for SelectBox components inheriting from ZTComponent."""
    
    component: str = Field("v-combobox", description="Vue component name.")
    
    items: List[Union[str, int]] = Field(..., description="Options for the select box. Can be a list of strings or integers.")
    value: Union[List[Union[str, int,None]],None,str,int] = Field(None, description="Selected option for the select box. Can be a string or integer.")
    label: str = Field(None, description="Label for the select box.")
    multiple: bool = Field(False, description="Determines if multiple selections are allowed.")
    dense: bool = Field(None, description="Determines if the select box is dense.")
    outlined: bool = Field(None, description="Determines if the select box has an outlined style.")
    clearable: bool = Field(None, description="Determines if the select box has a clearable option.")
    disabled: bool = Field(None, description="Determines if the select box is disabled.")
    readonly: bool = Field(None, description="Determines if the select box is read-only.")
    color: str = Field(None, pre=True, description="Color of the range slider. Can be custom or standard Material color.")
    triggerEvent: str = Field('update:modelValue',description="Trigger event for when to run the slider")
    multiple: str = ''
    @field_validator('color')
    def validate_color(cls, color):
        return validate_color(color)

    @validator('value', always=True) #TODO: debug and replace with field validator
    def get_value_from_global_state(cls, value, values):
        id = values['id'] # Get the id if it exists in the field values
        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            e
        return value  # If id doesn't exist in global_state, return the original value
zt_backend/models/components/multiselect.py
Zero-True/zero-true
from pydantic import Field, field_validator, validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.state import component_values
from typing import Union,Optional

class NumberInput(ZTComponent):
    """A class for TextInput components inheriting from ZTComponent."""
    component: str = Field("v-number-field", description="Vue component name.")
    value: Union[int,float,None] = Field (None,description="The input text value")
    placeholder: str = Field(None, description="Placeholder text.")
    label: str = Field(None, description="Label for the text input.")
    readonly: bool = Field(None, description="If true, the input is read-only.")
    disabled: bool = Field(None, description="If true, the input is disabled.")
    type: str = Field('number',description="Ensures that only numbers are accepted on the frontend")
    triggerEvent: str = Field('input',description="Trigger event to send code to the backend")
    
    @validator('value', always=True) #TODO: debug and replace with field validator
    def get_value_from_global_state(cls, value, values):
        if value=="":
            return None
        id = values['id'] # Get the id if it exists in the field values
        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            e
        return value  # If id doesn't exist in global_state, return the original value
zt_backend/models/components/number_input.py
Zero-True/zero-true
from pydantic import Field
from zt_backend.models.components.zt_component import ZTComponent
from typing import Dict
from plotly.graph_objs import Figure
import pandas as pd
import numpy as np

def convert_special_types(obj):
    """Recursively convert special types like DataFrame and NumPy array to list."""
    if isinstance(obj, dict):
        for key, value in obj.items():
            if isinstance(value, (pd.DataFrame, np.ndarray)):
                obj[key] = value.tolist()
            else:
                convert_special_types(value)
    elif isinstance(obj, list):
        for i, item in enumerate(obj):
            if isinstance(item, (pd.DataFrame, np.ndarray)):
                obj[i] = item.tolist()
            else:
                convert_special_types(item)

class PlotlyComponent(ZTComponent):
    component: str = Field("plotly-plot", description="Vue component name for Plotly.")
    figure: Dict = Field(..., description="Plotly figure object.")
    layout: dict = Field(..., description="Layout for Plotly plot.")
    id: str = Field(..., description="ID for the component")
    
    @classmethod
    def from_figure(cls, figure: Figure, id: str):
        """Creates a PlotlyComponent instance from a Plotly figure object."""
        figure.update_layout(template = 'plotly_dark')
        # Ensure the input is a Plotly figure
        if not isinstance(figure, Figure):
            raise ValueError("Input must be a Plotly Figure")
        # Convert the figure to JSON-serializable dictionary
        figure_json = figure.to_plotly_json()
        figure_data = figure_json.get('data', [])
        layout_data = figure_json['layout']
        #print(layout_data)
        # Convert any special types like DataFrame or NumPy array to list
        for trace in figure_data:
            convert_special_types(trace)
        return cls(figure=figure_json, layout=layout_data, id=id)
    
    def to_json(self):
        """Converts the Plotly figure to a JSON-serializable dictionary."""
        return {
            "component": self.component,
            "figure": self.figure,
            "layout": self.layout,
            "id": self.id
        }
zt_backend/models/components/plotly.py
Zero-True/zero-true
from typing import List, Optional 
from pydantic import Field, field_validator, validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.validations import validate_color
from zt_backend.models.state import component_values

class RangeSlider(ZTComponent):
    """A range slider component that allows you to capture numeric input from a user. 
    
    To use the slider simply import the package and then declare the slider. The only required 
    field is an id. Your slider will render with default max and min values and a number 
    of other defaults.
    
    """
    component: str = Field("v-range-slider", description="Vue component name.")
    value: List[int] = Field([0, 100], description="Current value range of the slider.")
    min: int = Field(0, ge=0, description="Minimum value of the slider.")
    max: int = Field(100, ge=0, description="Maximum value of the slider.")
    step: int = Field(1, gt=0, description="Step increment of the slider.")
    thumb_label: bool = Field(False, description="Displays the thumb label.")
    thumb_size: int = Field(0, description="Size of the thumb.")
    tick_labels: bool = Field(False, description="Displays the tick labels.")
    ticks: list = Field([], description="Displays the ticks.")
    color: str = Field('primary', pre=True, description="Color of the range slider. Can be custom or standard Material color.")
    size: str = Field('large', description="Size of the slider.")
    label: Optional[str] = Field(None,description= 'A label for your slider')

    rounded: bool = Field(True, description="Determines if the slider has rounded edges.")
    triggerEvent: str = Field('end',description="Trigger event for when to run the slider")
    
    @field_validator('color')
    def validate_color(cls, color):
        return validate_color(color)
    
    @validator('value', always=True) #TODO: debug and replace with field validator
    def get_value_from_global_state(cls, value, values):
        id = values['id'] # Get the id if it exists in the field values

        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            e
        return value  # If id doesn't exist in global_state, return the original value
    

zt_backend/models/components/range_slider.py
Zero-True/zero-true
from pydantic import Field, validator,field_validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.validations import validate_color
from typing import List, Union
from zt_backend.models.state import component_values

class SelectBox(ZTComponent):
    """A class for SelectBox components inheriting from ZTComponent."""
    
    component: str = Field("v-select", description="Vue component name.")
    
    items: List[Union[str, int]] = Field(..., description="Options for the select box. Can be a list of strings or integers.")
    value: Union[str, int,None] = Field(None, description="Selected option for the select box. Can be a string or integer.")
    label: str = Field(None, description="Label for the select box.")
    multiple: bool = Field(False, description="Determines if multiple selections are allowed.")
    dense: bool = Field(None, description="Determines if the select box is dense.")
    outlined: bool = Field(None, description="Determines if the select box has an outlined style.")
    clearable: bool = Field(None, description="Determines if the select box has a clearable option.")
    disabled: bool = Field(None, description="Determines if the select box is disabled.")
    readonly: bool = Field(None, description="Determines if the select box is read-only.")
    color: str = Field(None, pre=True, description="Color of the range slider. Can be custom or standard Material color.")
    triggerEvent: str = Field('update:modelValue',description="Trigger event for when to run the slider")

    @field_validator('color')
    def validate_color(cls, color):
        return validate_color(color)

    @validator('value', always=True) #TODO: debug and replace with field validator
    def get_value_from_global_state(cls, value, values):
        id = values['id'] # Get the id if it exists in the field values
        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            e
        return value  # If id doesn't exist in global_state, return the original value
zt_backend/models/components/selectbox.py
Zero-True/zero-true
from pydantic import Field, field_validator, validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.validations import validate_color, validate_min_less_than_max
from zt_backend.models.state import component_values
from typing import Optional

class Slider(ZTComponent):
    """A slider component that allows you to capture numeric input from a user. 
    
    To use the slider simply import the package and then declare the slider. The only required 
    field is an id. Your slider will render with default max and min values and a number 
    of other defaults.
    
    """
    component: str = Field("v-slider", description="Vue component name.")
    value: int = Field(0, description="Current value of the slider.")
    min: int = Field(0, ge=0, description="Minimum value of the slider.")
    max: int = Field(100, ge=0, description="Maximum value of the slider.")
    step: int = Field(1, gt=0, description="Step increment of the slider.")
    thumb_label: bool = Field(False, description="Displays the thumb label.")
    thumb_size: int = Field(0, description="Size of the thumb.")
    tick_labels: bool = Field(False, description="Displays the tick labels.")
    ticks: list = Field([], description="Displays the ticks.")
    color: str = Field('primary', pre=True, description="Color of the range slider. Can be custom or standard Material color.")
    size: str = Field('large', description="Size of the slider.")
    label: Optional[str] = Field(None,description= 'A label for your slider')
    rounded: bool = Field(True, description="Determines if the slider has rounded edges.")
    triggerEvent: str = Field('end',description="Trigger event for when to run the slider")
    
    @field_validator('color')
    def validate_color(cls, color):
        return validate_color(color)
    
    @validator('value', always=True) #TODO: debug and replace with field validator
    def get_value_from_global_state(cls, value, values):
        id = values['id'] # Get the id if it exists in the field values
        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            e
        return value  # If id doesn't exist in global_state, return the original value
zt_backend/models/components/slider.py
Zero-True/zero-true
from pydantic import Field
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.state import component_values



class Text(ZTComponent):
    """A class for static text components inheriting from ZTComponent."""
    component: str = Field("v-text", description="Vue component name for static text.")
    text: str = Field("", description="The actual text content.")
    text_type: str = Field('text-body-1', enum=[
        'text-h1', 'text-h2', 'text-h3', 'text-h4', 'text-h5', 'text-h6',
        'text-subtitle-1', 'text-subtitle-2', 'text-body-1', 'text-body-2',
        'text-button', 'text-caption', 'text-overline'
    ], description="Text type for text-related components.")
    color: str = Field(None, description="Color of the text.")
    triggerEvent: str = Field(None, description="Trigger event to send code to the backend. None for static text.")

zt_backend/models/components/text.py
Zero-True/zero-true
from pydantic import Field, field_validator, validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.state import component_values

class TextArea(ZTComponent):
    """A class for TextInput components inheriting from ZTComponent."""
    component: str = Field("v-textarea", description="Vue component name.")
    value: str = Field ('',description="The input text value")
    placeholder: str = Field(None, description="Placeholder text.")
    label: str = Field(None, description="Label for the text input.")
    readonly: bool = Field(None, description="If true, the input is read-only.")
    disabled: bool = Field(None, description="If true, the input is disabled.")
    triggerEvent: str = Field('input',description="Trigger event to send code to the backend")
    
    @validator('value', always=True) #TODO: debug and replace with field validator
    def get_value_from_global_state(cls, value, values):
        id = values['id'] # Get the id if it exists in the field values
        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            e
        return value  # If id doesn't exist in global_state, return the original value
zt_backend/models/components/text_area_input.py
Zero-True/zero-true
from pydantic import Field, field_validator, validator
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.state import component_values
from typing import Optional


class TextInput(ZTComponent):
    """A class for TextInput components inheriting from ZTComponent."""
    component: str = Field("v-text-field", description="Vue component name.")
    value: str = Field ('',description="The input text value")
    placeholder: str = Field(None, description="Placeholder text.")
    label: Optional[str] = Field(None, description="Label for the text input.")
    readonly: bool = Field(None, description="If true, the input is read-only.")
    disabled: bool = Field(None, description="If true, the input is disabled.")
    triggerEvent: str = Field('input',description="Trigger event to send code to the backend")
    
    @validator('value', always=True) #TODO: debug and replace with field validator
    def get_value_from_global_state(cls, value, values):
        id = values['id'] # Get the id if it exists in the field values
        try:
            if id and id in component_values:  # Check if id exists in global_state
                return component_values[id]  # Return the value associated with id in global_state
        except Exception as e:
            e
        return value  # If id doesn't exist in global_state, return the original value
zt_backend/models/components/text_input.py
Zero-True/zero-true
from pydantic import BaseModel, Field, field_validator
from zt_backend.models.state import created_components, context_globals, current_cell_components

class ZTComponent(BaseModel):
    id: str = Field(description="Unique id for a component")
    variable_name: str = Field('', description="Optional variable name associated with a component")


    def __init__(self, **data):
        super().__init__(**data)
        if context_globals['exec_mode']:
            current_cell_components.append(self)

    @field_validator('id', mode='before')
    def validate_unique_component_id(cls, id):
        if context_globals['exec_mode'] and id in created_components:
            raise Exception("Component ID is not unique")
        elif context_globals['exec_mode']:
            created_components.append(id)
            return id
        else:
            return id
zt_backend/models/components/zt_component.py
Zero-True/zero-true
from .request import Request, ComponentRequest, DeleteRequest, CreateRequest, SaveRequest
from .response import Response
from .notebook import Notebook
from .components.slider import Slider
from .components.text_input import TextInput
from .components.text_area_input import TextArea
from .components.range_slider import RangeSlider
from .components.selectbox import SelectBox
from .components.multiselect import MultiSelectBox
from .components.button import Button
from .components.number_input import NumberInput
from .components.image import Image
from .components.text import Text
from .components.layout import Layout
from .components.autocomplete import Autocomplete
from .components.card import Card
import json

def generate_json(model, name):
    with open('zt_schema/'+name+'.json', 'w+') as file:
        file.write(json.dumps(model.model_json_schema(),indent=2))

def generate_schema():
    generate_json(Request, 'request')
    generate_json(ComponentRequest, 'component_request')
    generate_json(DeleteRequest, 'delete_request')
    generate_json(CreateRequest, 'create_request')
    generate_json(SaveRequest, 'save_request')
    generate_json(Response, 'response')
    generate_json(Slider, 'slider')
    generate_json(Notebook, 'notebook')
    generate_json(TextInput, 'text_input')
    generate_json(TextArea,'text_area')
    generate_json(RangeSlider,'range_slider')
    generate_json(SelectBox,'select_box')
    generate_json(MultiSelectBox,'multiselect_box')
    generate_json(Text,'text')
    generate_json(Button,'button')
    generate_json(NumberInput,'number_input')
    generate_json(Image,'image')
    generate_json(Layout,'layout')
    generate_json(Autocomplete,'autocomplete')
    generate_json(Card,'card')
zt_backend/models/generate_schema.py
Zero-True/zero-true
from pydantic import BaseModel, Field, SerializeAsAny, model_validator
from typing import OrderedDict, List, Dict, Any,Optional
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.components.slider import Slider
from zt_backend.models.components.text_input import TextInput
from zt_backend.models.components.text_area_input import TextArea
from zt_backend.models.components.range_slider import RangeSlider
from zt_backend.models.components.selectbox import SelectBox
from zt_backend.models.components.multiselect import MultiSelectBox
from zt_backend.models.components.button import Button
from zt_backend.models.components.number_input import NumberInput
from zt_backend.models.components.image import Image
from zt_backend.models.components.text import Text
from zt_backend.models.components.dataframe import DataFrame
from zt_backend.models.components.layout import Layout
from zt_backend.models.components.plotly import PlotlyComponent
from zt_backend.models.components.autocomplete import Autocomplete
from zt_backend.models.components.card import Card

def deserialize_component(data: Dict[str, Any]) -> ZTComponent:
    component_map = {
        "v-slider": Slider,
        "v-text-field":TextInput,
        "v-textarea": TextArea,
        "v-number-field": NumberInput,
        "v-range-slider": RangeSlider,
        "v-select": SelectBox,
        "v-combobox": MultiSelectBox,
        "v-btn": Button,
        "v-img": Image,
        "v-text": Text,
        "v-data-table": DataFrame,
        "v-autocomplete": Autocomplete,
        "v-card": Card,
        "plotly-plot": PlotlyComponent
        # add other component types here
    }
    component_class = data.get("component")
    if component_class not in component_map:
        raise ValueError(f"Invalid component class: {component_class}")
    return component_map[component_class].model_validate(data)

class CodeCell(BaseModel):
    id: str
    code: str
    output: str
    variable_name: str
    layout: Layout = Field(Layout())
    components: List[SerializeAsAny[ZTComponent]]
    cellType: str = Field(enum=['code', 'markdown', 'text', 'sql'])

    @model_validator(mode='before')
    def deserialize_components(cls, values):
        components = values.get('components', [])
        values['components'] = [deserialize_component(comp) for comp in components]
        return values
    
class Notebook(BaseModel):
    cells: OrderedDict[str, CodeCell]
    userId: str



zt_backend/models/notebook.py
Zero-True/zero-true
from pydantic import BaseModel, Field
from typing import List, Dict, Union

class CodeRequest(BaseModel):
    id: str
    code: str
    variable_name: str
    cellType: str = Field(enum=['code', 'markdown', 'text', 'sql'])

class Request(BaseModel):
    originId: str
    cells: List[CodeRequest]
    components: Dict[str, Union[str, bool, int, List, None]]

class Cell(BaseModel):
    code: str
    defined_names: List[str]
    loaded_names: List[str]
    child_cells: List[int] = []
    parent_cells: List[int] = []
    previous_child_cells: List[int] = []

class CodeDict(BaseModel):
    cells: Dict[str, Cell]

class ComponentRequest(BaseModel):
    originId: str
    components: Dict[str, Union[str, bool, int, List, None]]
    userId: str

class DeleteRequest(BaseModel):
    cellId: str

class CreateRequest(BaseModel):
    cellType: str = Field(enum=['code', 'markdown', 'text', 'sql'])

class SaveRequest(BaseModel):
    id: str
    text: str
zt_backend/models/request.py
Zero-True/zero-true
from pydantic import BaseModel, SerializeAsAny
from typing import List
from zt_backend.models.components.zt_component import ZTComponent
from zt_backend.models.components.layout import Layout
from typing import Union


class CellResponse(BaseModel):
    id: str
    components: List[SerializeAsAny[ZTComponent]]
    layout: Union[Layout,None]
    output: str

class Response(BaseModel):
    cells: List[CellResponse]
zt_backend/models/response.py
Zero-True/zero-true
component_values = {}
created_components=[]
context_globals={"exec_mode": False}
current_cell_components=[]
current_cell_layout = []
zt_backend/models/state.py
Zero-True/zero-true
def validate_color(value):
    """
    Validates the given color value.
    This validator ensures that the provided color is either one of the custom colors or follows the standard Material color palette format.
    """
    if value is None:
        return value
    custom_colors = ['primary','secondary', 'accent', 'error','info','success','warning']
    material_colors = ['red', 'pink', 'purple', 'deep-purple', 'indigo', 'blue', 'light-blue', 'cyan', 'teal', 'green', 'light-green', 'lime', 'yellow', 'amber', 'orange', 'deep-orange', 'brown', 'grey', 'blue-grey']
    if value in custom_colors:
        return value
    if '-' in value:
        color_name, shade = value.split('-')
        if color_name in material_colors and 100 <= int(shade) <= 900:
            return value
    raise ValueError(f"Invalid color value: '{value}'. Must be one of {custom_colors} or in the standard Material color palette format.")

def validate_min_less_than_max(max_value, values):
    """
    Validates that the maximum value is greater than the minimum value.
    """
    min_value = values.get('min')
    if min_value is not None and max_value <= min_value:
        raise ValueError(f"Max value '{max_value}' must be greater than min value '{min_value}'.")
    return max_value










zt_backend/models/validations.py
Zero-True/zero-true
from fastapi import APIRouter,BackgroundTasks
from zt_backend.models import request, notebook, response
from zt_backend.runner.execute_code import execute_request
from zt_backend.config import settings
import tomli
import uuid
import os
import toml
import json

router = APIRouter()

user_states={}
cell_outputs_dict={}

run_mode = settings.run_mode

@router.get("/health")
def health():
    return('UP')

@router.post("/api/runcode")
async def runcode(request: request.Request,background_tasks: BackgroundTasks):
    if(run_mode=='dev'):
        background_tasks.add_task(globalStateUpdate,run_request=request.model_copy(deep=True))

        response = execute_request(request, cell_outputs_dict)
        background_tasks.add_task(globalStateUpdate,run_response=response)
        return response

@router.post("/api/component_run")
def runcode(component_request: request.ComponentRequest):
    notebook = get_notebook()
    cells = []
    for cell_key, cell in notebook.cells.items():
        cell_request=request.CodeRequest(
            id=cell.id, 
            code=cell.code,
            variable_name=cell.variable_name,
            cellType=cell.cellType
        )
        cells.append(cell_request)
    code_request = request.Request(
        originId=component_request.originId,
        cells=cells,
        components=component_request.components
    )
    if(run_mode=='dev'):
        return execute_request(code_request, cell_outputs_dict)
    else:
        return execute_request(code_request, user_states[component_request.userId])


@router.post("/api/create_cell")
def create_cell(cellRequest: request.CreateRequest):
     if(run_mode=='dev'):
        createdCell = notebook.CodeCell(
            id=str(uuid.uuid4()),
            code='',
            components=[],
            output='',
            variable_name='',
            cellType=cellRequest.cellType
        )
        globalStateUpdate(newCell=createdCell.model_copy(deep=True))
        return createdCell

@router.post("/api/delete_cell")
def delete_cell(deleteRequest: request.DeleteRequest):
     if(run_mode=='dev'):
        globalStateUpdate(deletedCell=deleteRequest.cellId)

@router.post("/api/save_text")
def save_test(saveRequest: request.SaveRequest):
     if(run_mode=='dev'):
        globalStateUpdate(saveCell=saveRequest)

@router.get("/api/notebook")
def get_notebook():
    notebook_start = get_notebook()
    if (run_mode=='app'):
        userId = str(uuid.uuid4())
        notebook_start.userId = userId
        user_states[userId]={}
        cells = []
        components={}

        for cell_key, cell in notebook_start.cells.items():
            cell_request=request.CodeRequest(
                id=cell.id, 
                code=cell.code,
                variable_name=cell.variable_name,
                cellType=cell.cellType
            )
            for comp in cell.components:
                if hasattr(comp, 'value'):
                    components[comp.id] = comp.value
            cells.append(cell_request)
        code_request = request.Request(
            originId='',
            cells=cells,
            components=components
        )
        response = execute_request(code_request, user_states[userId])
        for responseCell in response.cells:
            notebook_start.cells[responseCell.id].components = responseCell.components
            notebook_start.cells[responseCell.id].output = responseCell.output
            notebook_start.cells[responseCell.id].layout = responseCell.layout
    return notebook_start

def get_notebook():
    with open('notebook.toml', "rb") as project_file:
        notebook_data = tomli.load(project_file)
    return notebook.Notebook(**notebook_data)

def globalStateUpdate(newCell: notebook.CodeCell=None, deletedCell: str=None, saveCell: request.SaveRequest=None, run_request: request.Request=None, run_response: response.Response=None):
    zt_notebook = get_notebook()
    if newCell is not None:
        zt_notebook.cells[newCell.id] = newCell
    if deletedCell is not None:
        del zt_notebook.cells[deletedCell]
    if saveCell is not None:
        zt_notebook.cells[saveCell.id].code=saveCell.text
    if run_request is not None:
        for requestCell in run_request.cells:
            zt_notebook.cells[requestCell.id].code = requestCell.code
            zt_notebook.cells[requestCell.id].variable_name = requestCell.variable_name
    if run_response is not None:
        for responseCell in run_response.cells:
            zt_notebook.cells[responseCell.id].components = responseCell.components
            zt_notebook.cells[responseCell.id].output = responseCell.output
            zt_notebook.cells[responseCell.id].layout = responseCell.layout
    
    tmp_uuid_file = 'notebook_'+ str(uuid.uuid4())+'.toml'
    
    try:
        with open(tmp_uuid_file, "w") as project_file:
            toml.dump(zt_notebook.model_dump(), project_file)
        os.replace(tmp_uuid_file,'notebook.toml')

    except Exception as e:
        print(e)
            
    try:
        os.remove(tmp_uuid_file)
    except Exception as e:
        e
zt_backend/router.py
Zero-True/zero-true
from typing import OrderedDict, List, Dict, Tuple, Any
import astroid
from zt_backend.models.request import Request,Cell,CodeDict
import duckdb
import uuid
import re


def get_imports(module) -> List[str]:
    import_froms = [node.names[0][1] or node.names[0][0] for node in module.nodes_of_class(astroid.ImportFrom)]
    imports = [node.names[0][1] or node.names[0][0] for node in module.nodes_of_class(astroid.Import)]
    return import_froms + imports

def get_functions(module) -> Tuple[List[str], List[str]]:
    function_names = []
    argument_names = []
    for function_def in module.nodes_of_class(astroid.FunctionDef):
        function_names.append(function_def.name)
        argument_names.extend([arg.name for arg in function_def.args.args])
    return function_names, argument_names

def get_defined_names(module) -> List[str]:
    defined_names = [target.name for defnode in module.nodes_of_class(astroid.Assign) for target in defnode.targets if hasattr(target, 'name')]
    func_def_names = [arg.name for func in module.nodes_of_class(astroid.FunctionDef) for arg in func.args.args]
    return list(set(defined_names) - set(func_def_names))

def get_loaded_modules(module) -> List[str]:
    try:
        return [node.expr.name for node in module.nodes_of_class(astroid.Attribute) if hasattr(node.expr, 'name')]
    except Exception as e:
        print(f"Error occurred with modules: {e}")
        return []

def get_loaded_names(module, defined_names) -> List[str]:
    function_names, function_arguments = get_functions(module)
    return [usenode.name for usenode in module.nodes_of_class(astroid.Name) if usenode.name not in function_arguments]


def generate_sql_code(cell, uuid_value):
    """Generate SQL code for the given cell."""

    # Common import statements
    base_code = "import duckdb\nimport zero_true as zt"

    # SQL code execution
    sql_execution = f'duckdb.sql(f"""{cell.code}""").df()'

    if cell.variable_name:
        # If variable_name is provided, use it for assignment
        variable_assignment = f"{cell.variable_name} = {sql_execution}"
        data_frame_conversion = f"zt.DataFrame.from_dataframe(id='{uuid_value}', df={cell.variable_name})"
        
        full_code = f"{base_code}\n{variable_assignment}\n{data_frame_conversion}"
        
    else:
        # If variable_name is not provided, directly use the SQL execution
        data_frame_conversion = f"zt.DataFrame.from_dataframe(id='{uuid_value}', df={sql_execution})"
        
        full_code = f"{base_code}\n{data_frame_conversion}"

    return full_code

def parse_cells(request: Request) -> CodeDict:
    cell_dict = {}
    for cell in [c for c in request.cells if c.cellType in ['code', 'sql']]:
        table_names=[]
        if cell.cellType=='sql' and cell.code:
            table_names = duckdb.get_table_names(re.sub(r'\{.*?\}', '1', cell.code))
            uuid_value = str(uuid.uuid4())
            cell.code = generate_sql_code(cell, uuid_value)
        
        module = astroid.parse(cell.code)
        function_names, function_arguments = get_functions(module)
        defined_names = get_defined_names(module) + get_imports(module) + function_names
        loaded_names = get_loaded_names(module, defined_names) + get_loaded_modules(module) + list(table_names)
        cell_dict[cell.id] = Cell(**{
            'code': cell.code,
            'defined_names': defined_names,
            'loaded_names': list(set(loaded_names))})
    return CodeDict(cells=cell_dict)


def build_dependency_graph(cell_dict: Dict[int, Dict[str, Any]]) -> Dict[int, Dict[str, Any]]:
    """
    Builds a dependency graph from a cell dictionary. Each node in the graph represents a cell,
    and edges represent dependencies between cells based on loaded and defined names.
    """
    # Initialize previous components dictionary
    prev_components = {}

    # Add child and parent relationships
    graph = add_child_cells(cell_dict, prev_components)

    return graph

def find_child_cells(cell: Cell, code_dictionary: CodeDict, idx: int) -> List[str]:
    child_cells = []
    names = cell.defined_names
    for next_key in list(code_dictionary.cells.keys())[idx + 1:]:
        next_cell = code_dictionary.cells[next_key]
        next_loaded_names = next_cell.loaded_names
        if set(names).intersection(set(next_loaded_names)):
            child_cells.append(next_key)
    return child_cells

def add_parent_cells(code_dictionary: CodeDict) -> Dict[str, Any]:
    for key in list(code_dictionary.cells.keys()):
        cell = code_dictionary.cells[key]
        child_cells = cell.child_cells
        for child_cell in child_cells:
            code_dictionary.cells[child_cell].parent_cells.append(key)
        cell.child_cells = child_cells
    return code_dictionary

def add_child_cells(code_dictionary: CodeDict, prev_components: Dict[str, Any]) -> Dict[str, Any]:
    for idx, key in enumerate(list(code_dictionary.cells.keys())):
        cell = code_dictionary.cells[key]
        cell.child_cells = find_child_cells(cell, code_dictionary, idx)
        cell.previous_child_cells = prev_components.get(key, {}).get('child_cells', [])
    return add_parent_cells(code_dictionary)



def print_astroid_tree(code):
    module = astroid.parse(code)
    print(module.repr_tree())


def find_downstream_cells(code_dictionary: CodeDict, start_cell_id, visited=None):
    if visited is None:
        visited = set()
        
    if start_cell_id in visited:
        return []
    
    visited.add(start_cell_id)
    
    downstream_cells = []
    
    for cell_id, cell_data in code_dictionary.cells.items():
        if start_cell_id in cell_data.parent_cells:
            downstream_cells.append(cell_id)
            downstream_cells.extend(find_downstream_cells(code_dictionary, cell_id, visited))
    
    return list(OrderedDict.fromkeys(downstream_cells))


zt_backend/runner/code_cell_parser.py
Zero-True/zero-true
from io import StringIO
from typing import Dict,Any,OrderedDict
import pickle
from contextlib import redirect_stdout
from zt_backend.models import request, response
from zt_backend.models.state import component_values, created_components, context_globals, current_cell_components,current_cell_layout
from zt_backend.runner.code_cell_parser import parse_cells,build_dependency_graph, find_downstream_cells, CodeDict
from zt_backend.models.components.layout import Layout
from datetime import datetime

now = datetime.now()


def try_pickle(obj):
    """
    Attempts to pickle and then unpickle an object. If successful, returns the unpickled object, 
    otherwise returns the original object.
    """
    try:
        return pickle.loads(pickle.dumps(obj))
    except Exception as e:
        return obj


def get_parent_vars(cell_id: str, code_components: CodeDict, cell_outputs_dict: Dict[str, Any]) -> Dict[str, Any]:
    """
    Gathers and returns parent variables for a given cell.

    Args:
        cell (str): ID of the current cell.
        code_components (dict): Mapping of all cell IDs to their related components such as parent cells, code, etc.
        cell_outputs_dict (dict): Contains outputs of all previously executed cells.

    Returns:
        dict: Dictionary containing the parent variables for the cell.
    """  
    #print(code_components)
    if cell_id not in code_components.cells:
        # Print localstorage_data and stop execution if cell is not in code_components
        for key in cell_outputs_dict.keys():
            print('Key',key, 'Value', cell_outputs_dict[key])
        raise KeyError(f"'{cell_id}' is not present in code_components. Execution stopped.")

    parent_vars = []
    parent_cells = code_components.cells[cell_id].parent_cells
    cell_dicts = []
    for parent_cell in parent_cells:
        cell_dicts.append(cell_outputs_dict.get(parent_cell, {}))
        parent_vars_list = code_components.cells[parent_cell].loaded_names + \
                           code_components.cells[parent_cell].defined_names
        parent_vars += parent_vars_list

    exec_parents = {key: try_pickle(val) for d in cell_dicts for key, val in d.items() if key in code_components.cells[cell_id].loaded_names}

    return exec_parents


#issue right now is that the request is sending the entire notebook. The request should send the ID of the cell you are running.
#also right now there is no special handling for the 
def execute_request(request: request.Request,cell_outputs_dict: Dict):
    cell_outputs = []
    component_values.update(request.components)
    component_globals={'global_state': component_values}
    dependency_graph = build_dependency_graph(parse_cells(request))
    if request.originId:
        
        downstream_cells = [request.originId]
        downstream_cells.extend(find_downstream_cells(dependency_graph, request.originId))
        
        
        try:
            old_downstream_cells = [request.originId]
            old_downstream_cells.extend(find_downstream_cells(cell_outputs_dict['previous_dependecy_graph'],request.originId))
             # Find cells that are no longer dependent
            no_longer_dependent_cells = set(old_downstream_cells) - set(downstream_cells)
    
            if no_longer_dependent_cells:
                
                downstream_cells.extend(list(OrderedDict.fromkeys(no_longer_dependent_cells)))
        except Exception as e:
            print(e)
    
    else:
        downstream_cells = [cell.id for cell in request.cells if cell.cellType in ['code', 'sql']]
 
    #go through each item in dependency graph (we should just go through the downstream cells)
    for code_cell_id in list(OrderedDict.fromkeys(downstream_cells)):
        code_cell = dependency_graph.cells[code_cell_id]

        f = StringIO()
        with redirect_stdout(f):
            try:
                if code_cell.parent_cells == []:
                    temp_globals = component_globals
                else:
                    temp_globals = get_parent_vars(cell_id=code_cell_id,code_components=dependency_graph,cell_outputs_dict=cell_outputs_dict)
                context_globals['exec_mode'] = True
                exec(code_cell.code, temp_globals)

            except Exception as e:
                print(e)
        context_globals['exec_mode'] = False

        cell_outputs_dict[code_cell_id] = {k: try_pickle(v) for k, v in temp_globals.items() if k != '__builtins__'}

        try:
            layout = current_cell_layout[0]
        except Exception:
            layout = Layout(**{})
        
        for component in current_cell_components:
            if component.component == 'v-btn':
                component.value = False

        cell_outputs.append(response.CellResponse(id=code_cell_id,layout=layout, components=current_cell_components, output=f.getvalue()))
        current_cell_components.clear()
        current_cell_layout.clear()
    
    
    cell_outputs_dict['previous_dependecy_graph'] = dependency_graph

    component_values.clear()
    created_components.clear()
    context_globals['exec_mode'] = False
    return response.Response(cells=cell_outputs)
zt_backend/runner/execute_code.py
Zero-True/zero-true
from zt_backend.runner.code_cell_parser import parse_cells, build_dependency_graph
from zt_backend.models.request import Request,CodeRequest

# Test 1: No dependencies between cells
def test_no_dependencies():
    cells = Request(originId="0", cells=[CodeRequest(id="0",code="a = 1", variable_name="", cellType='code'),
                           CodeRequest(id="1",code="b = 2", variable_name="", cellType='code'),
                           CodeRequest(id="2",code="c = 3", variable_name="", cellType='code')],\
                    components={"":""})
    cell_dict = build_dependency_graph(parse_cells(cells))
    assert cell_dict.cells['0'].defined_names == ['a'] and cell_dict.cells['0'].loaded_names == [], "Test 1 Failed"
    assert cell_dict.cells['1'].defined_names == ['b'] and cell_dict.cells['1'].loaded_names == [], "Test 1 Failed"
    assert cell_dict.cells['2'].defined_names == ['c'] and cell_dict.cells['2'].loaded_names == [], "Test 1 Failed"

# Test 2: Simple dependencies
def test_simple_dependencies():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="a = 1", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="b = a + 2", variable_name="", cellType='code'),
                           CodeRequest(id="2", code="c = b + 3", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert cell_dict.cells['0'].defined_names == ['a'] and cell_dict.cells['0'].loaded_names == [], "Test 2 Failed"
    assert cell_dict.cells['1'].defined_names == ['b'] and cell_dict.cells['1'].loaded_names == ['a'], "Test 2 Failed"
    assert cell_dict.cells['2'].defined_names == ['c'] and cell_dict.cells['2'].loaded_names == ['b'], "Test 2 Failed"

# Test 3: Complex dependencies
def test_complex_dependencies():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="a = 1", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="b = a + 2", variable_name="", cellType='code'),
                           CodeRequest(id="2", code="c = b + a", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert cell_dict.cells['0'].defined_names == ['a'] and cell_dict.cells['0'].loaded_names == [], "Test 3 Failed"
    assert cell_dict.cells['1'].defined_names == ['b'] and cell_dict.cells['1'].loaded_names == ['a'], "Test 3 Failed"
    assert cell_dict.cells['2'].defined_names == ['c'] and set(cell_dict.cells['2'].loaded_names) == set(['b', 'a']), "Test 3 Failed"

# Test 4: Overriding dependencies
def test_overriding_dependencies():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="a = 1", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="b = a + 2", variable_name="", cellType='code'),
                           CodeRequest(id="2", code="a = 3", variable_name="", cellType='code'),
                           CodeRequest(id="3", code="c = a + b", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert cell_dict.cells['0'].defined_names == ['a'] and cell_dict.cells['0'].loaded_names == [], "Test 4 Failed"
    assert cell_dict.cells['1'].defined_names == ['b'] and cell_dict.cells['1'].loaded_names == ['a'], "Test 4 Failed"
    assert cell_dict.cells['2'].defined_names == ['a'] and cell_dict.cells['2'].loaded_names == [], "Test 4 Failed"
    assert cell_dict.cells['3'].defined_names == ['c'] and set(cell_dict.cells['3'].loaded_names) == set(['a', 'b']), "Test 4 Failed"

# Test 5: Function definitions and calls
def test_function_definitions_and_calls():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="def f(x): return x + 2", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="a = f(2)", variable_name="", cellType='code'),
                           CodeRequest(id="2", code="b = a + 3", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert cell_dict.cells['0'].defined_names == ['f'] and cell_dict.cells['0'].loaded_names == [], "Test 5 Failed"
    assert cell_dict.cells['1'].defined_names == ['a'] and cell_dict.cells['1'].loaded_names == ['f'], "Test 5 Failed"
    assert cell_dict.cells['2'].defined_names == ['b'] and cell_dict.cells['2'].loaded_names == ['a'], "Test 5 Failed"

# Test 6: Dependencies with function calls and redefinitions
def test_dependencies_with_function_calls_and_redefinitions():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="def f(x): return x + 2", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="a = f(2)", variable_name="", cellType='code'),
                           CodeRequest(id="2", code="b = a + 3", variable_name="", cellType='code'),
                           CodeRequest(id="3", code="def g(x): return x * 2", variable_name="", cellType='code'),
                           CodeRequest(id="4", code="c = g(b)", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert cell_dict.cells['0'].defined_names == ['f'] and cell_dict.cells['0'].loaded_names == [], "Test 6 Failed"
    assert cell_dict.cells['1'].defined_names == ['a'] and cell_dict.cells['1'].loaded_names == ['f'], "Test 6 Failed"
    assert cell_dict.cells['2'].defined_names == ['b'] and cell_dict.cells['2'].loaded_names == ['a'], "Test 6 Failed"
    assert cell_dict.cells['3'].defined_names == ['g'] and cell_dict.cells['3'].loaded_names == [], "Test 6 Failed"
    assert cell_dict.cells['4'].defined_names == ['c'] and set(cell_dict.cells['4'].loaded_names) == set(['g', 'b']), "Test 6 Failed"

# Test 7: Importing a module in one cell and using it in another
def test_importing_module():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="import math", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="a = math.sqrt(4)", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert cell_dict.cells['0'].defined_names == ['math'] and cell_dict.cells['0'].loaded_names == [], "Test 7 Failed"
    assert cell_dict.cells['1'].defined_names == ['a'] and cell_dict.cells['1'].loaded_names == ['math'], "Test 7 Failed"

# Test 8: Defining multiple variables in one cell
def test_multiple_variables_in_one_cell():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="a = 1; b = 2; c = 3", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="d = a + b + c", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert set(cell_dict.cells['0'].defined_names) == set(['a', 'b', 'c']) and cell_dict.cells['0'].loaded_names == [], "Test 8 Failed"
    assert cell_dict.cells['1'].defined_names == ['d'] and set(cell_dict.cells['1'].loaded_names) == set(['a', 'b', 'c']), "Test 8 Failed"

# Test 9: Multiline cells
def test_multiline_cells():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="a = 1\nb = 2\nc = 3", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="d = a + b\ne = c * d", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert set(cell_dict.cells['0'].defined_names) == set(['a', 'b', 'c']) and cell_dict.cells['0'].loaded_names == [], "Test 9 Failed"
    print(cell_dict)
    assert set(cell_dict.cells['1'].defined_names) == set(['d', 'e']) and set(cell_dict.cells['1'].loaded_names) == set(['a', 'b', 'c','d']), "Test 9a Failed"

# Test 10: Importing a module with an alias in one cell and using it in another
def test_importing_module_with_alias():
    cells = Request(originId="0", cells=[CodeRequest(id="0", code="import math as m", variable_name="", cellType='code'),
                           CodeRequest(id="1", code="a = m.sqrt(4)", variable_name="", cellType='code')],
                    components={})
    cell_dict = build_dependency_graph(parse_cells(cells))
    
    assert cell_dict.cells['0'].defined_names == ['m'] and cell_dict.cells['0'].loaded_names == [], "Test 10 Failed"
    assert cell_dict.cells['1'].defined_names == ['a'] and cell_dict.cells['1'].loaded_names == ['m'], "Test 10 Failed"

zt_backend/tests/test_code_cell_parser.py
Zero-True/zero-true
from zt_backend.runner.execute_code import execute_request
from zt_backend.models.request import Request, CodeRequest

cell_outputs_dict={}

# Initialize or clear cell_outputs_dict
def setup_function():
    cell_outputs_dict.clear()

# Test for simple variable assignment
def test_execute_request_simple_variable():
    setup_function()
    req = Request(originId="0", cells=[CodeRequest(id="0", code="a = 1", variable_name="", cellType='code')], components={})
    execute_request(req, cell_outputs_dict)
    assert 'a' in cell_outputs_dict['0']
    assert cell_outputs_dict['0']['a'] == 1

# Test for function definition
def test_execute_request_function_definition():
    setup_function()
    req = Request(originId="1", cells=[CodeRequest(id="1", code="def add(x, y): return x + y", variable_name="", cellType='code')], components={})
    execute_request(req, cell_outputs_dict)
    assert 'add' in cell_outputs_dict['1']

# Test for importing modules
def test_execute_request_import_module():
    setup_function()
    req = Request(originId="2", cells=[CodeRequest(id="2", code="import math", variable_name="", cellType='code')], components={})
    execute_request(req, cell_outputs_dict)
    assert 'math' in cell_outputs_dict['2']

# Test for multiple cells with dependencies
def test_execute_request_multiple_cells_with_dependencies():
    setup_function()
    req = Request(originId="3", cells=[CodeRequest(id="3", code="a = 1", variable_name="", cellType='code'),
                         CodeRequest(id="4", code="b = a + 1", variable_name="", cellType='code')], components={})
    execute_request(req, cell_outputs_dict)
    assert 'a' in cell_outputs_dict['3']
    assert 'b' in cell_outputs_dict['4']
    assert cell_outputs_dict['3']['a'] == 1
    assert cell_outputs_dict['4']['b'] == 2

zt_backend/tests/test_execute_code.py
Zero-True/zero-true
#!/usr/bin/env python3

import argparse
import os
import uvicorn

def start_servers():
    parser = argparse.ArgumentParser(description="Start the frontend and backend servers.")
    parser.add_argument("mode", choices=["notebook", "app"], help="The mode to run the servers in.")  # Updated choices
    args = parser.parse_args()

    if args.mode == 'app':
        os.environ['RUN_MODE'] = 'app'
        uvicorn.run('zt_backend.main:app', host="0.0.0.0", port=2613)
    elif args.mode=='notebook':
        os.environ['RUN_MODE'] = 'dev'
        uvicorn.run('zt_backend.main:app', host="0.0.0.0", port=1326)
    else:
        raise Exception('You must specify either noteboook or app to run Zero-True')

if __name__ == "__main__":
    start_servers()

zt_cli/cli.py
Zero-True/zero-true
#!/usr/bin/env python3

import argparse
import subprocess
import os
import shutil
from zt_backend.models.generate_schema import generate_schema

def generate_ts():
    os.mkdir('zt_schema')
    generate_schema()
    os.chdir('zt_frontend')
    shutil.rmtree('src/types')
    os.system('yarn json2ts -i ../zt_schema -o src/types')
    os.chdir('..')
    shutil.rmtree('zt_schema')

def build_frontend():
    try:
        shutil.rmtree('zt_backend/dist_dev')
        shutil.rmtree('zt_backend/dist_app')
    except Exception as e:
        print(e)
    os.chdir('zt_frontend')
    os.system('yarn install')
    os.system('yarn run build')
    os.chdir('..')
    shutil.copytree('zt_frontend/dist', 'zt_backend/dist_dev')
    os.chdir('zt_frontend')
    os.system('yarn run buildapp')
    os.chdir('..')
    shutil.copytree('zt_frontend/dist', 'zt_backend/dist_app')

def start_servers(args):
    if args.mode == 'app':
        os.environ['RUN_MODE'] = 'app'
        frontend_cmd = ["yarn", "run", "app", str(args.frontend_port)]
    else:
        os.environ['RUN_MODE'] = 'dev'
        frontend_cmd = ["yarn", "run", "dev", str(args.frontend_port)]
    backend_cmd = ["start", "uvicorn", "zt_backend.main:app", "--reload"]

    backend_process = subprocess.Popen(backend_cmd, shell=True)
    os.chdir("zt_frontend")
    frontend_process = subprocess.Popen(frontend_cmd, shell=True)

    backend_process.wait()
    frontend_process.wait()

def zt_cli():
    parser = argparse.ArgumentParser(description="Start the frontend and backend servers.")
    parser.add_argument("run", nargs='?', default=False, help="Run the project")
    parser.add_argument("mode", choices=["notebook", "app"], nargs='?', default="app", help="The mode to run the servers in.")
    parser.add_argument("--frontend-port", type=int, default=5173, help="The port for the frontend server.") # Default to 8080
    parser.add_argument("--pyd2ts", action=argparse.BooleanOptionalAction, help="Generate TS models without running project")
    parser.add_argument("--yarnBuild", action=argparse.BooleanOptionalAction, help="Generate TS models without running project")
    args = parser.parse_args()

    if args.pyd2ts:
        generate_ts()
    if args.yarnBuild:
        build_frontend()
    if args.run:
        start_servers(args)
    
if __name__ == "__main__":
    zt_cli()

zt_dev_cli/cli.py
Zero-True/zero-true