Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ build-backend = "hatchling.build"
[project]
name = "revisitpy"
version = "2.3.0"
requires-python = ">=3.10"
dependencies = [
"anywidget",
"pydantic>=2.10.5",
Expand Down
189 changes: 185 additions & 4 deletions scripts/test_script.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"\n",
"# Now you can import your package\n",
"import src.revisitpy.revisitpy as rvt\n",
"import revisit_server as rs"
"# import revisit_server as rs"
]
},
{
Expand Down Expand Up @@ -170,11 +170,192 @@
"w = rvt.widget(study, server=True, pathToLib='/Users/bbollen23/revisit-py/.venv/lib/python3.12/site-packages/revisit_server')\n",
"w"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Test Dataframe support "
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd "
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>id</th>\n",
" <th>b1</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>0</th>\n",
" <td>1</td>\n",
" <td>a</td>\n",
" </tr>\n",
" <tr>\n",
" <th>1</th>\n",
" <td>2</td>\n",
" <td>b</td>\n",
" </tr>\n",
" <tr>\n",
" <th>2</th>\n",
" <td>3</td>\n",
" <td>C</td>\n",
" </tr>\n",
" <tr>\n",
" <th>3</th>\n",
" <td>4</td>\n",
" <td>d</td>\n",
" </tr>\n",
" <tr>\n",
" <th>4</th>\n",
" <td>5</td>\n",
" <td>e</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" id b1\n",
"0 1 a\n",
"1 2 b\n",
"2 3 C\n",
"3 4 d\n",
"4 5 e"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"df = pd.DataFrame({\n",
" 'id': [i for i in range(1, 6)],\n",
" 'b1': ['a', 'b', 'C', 'd', 'e']\n",
"})\n",
"df"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[DataRow(id=1, b1='a'),\n",
" DataRow(id=2, b1='b'),\n",
" DataRow(id=3, b1='C'),\n",
" DataRow(id=4, b1='d'),\n",
" DataRow(id=5, b1='e')]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data_rows = rvt.data(df)\n",
"data_rows"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"components\": [\n",
" \"test_component\"\n",
" ],\n",
" \"order\": \"fixed\"\n",
"}\n"
]
}
],
"source": [
"# Test using it with from_data()\n",
"# it's ok if we don't have the actual md file, just need a non-empty path \n",
"test_component = rvt.component(\n",
" type='markdown', \n",
" component_name__='test_component', \n",
" path='./assets/introduction.md'\n",
")\n",
"\n",
"test_sequence = rvt.sequence(order='fixed', components=[test_component])\n",
"print(test_sequence)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"components\": [\n",
" \"test_component_id:1_b1:a\",\n",
" \"test_component_id:2_b1:b\",\n",
" \"test_component_id:3_b1:C\",\n",
" \"test_component_id:4_b1:d\",\n",
" \"test_component_id:5_b1:e\"\n",
" ],\n",
" \"order\": \"fixed\"\n",
"}\n"
]
}
],
"source": [
"test_sequence.from_data(data_rows)\n",
"print(test_sequence)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
Expand All @@ -188,9 +369,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.5"
"version": "3.13.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
46 changes: 42 additions & 4 deletions src/revisitpy/revisitpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import json
from . import models as rvt_models
from pydantic import BaseModel, ValidationError # type: ignore
from typing import List, Literal, get_origin, Optional, get_args, Any, Unpack, overload, get_type_hints
from typing import List, Literal, get_origin, Optional, get_args, Any, Unpack, overload, get_type_hints, Union
from enum import Enum
import csv
from dataclasses import make_dataclass, asdict
Expand All @@ -11,7 +11,10 @@
import shutil
from . import widget as _widget
import inspect

try:
import pandas as pd
except ImportError:
pd = None

__all__ = [
"component",
Expand Down Expand Up @@ -547,8 +550,29 @@ def studyConfig(**kwargs: Unpack[_StudyConfigType]) -> _WrappedStudyConfig:


# Function to parse the CSV and dynamically create data classes
def data(file_path: str) -> List[Any]:
# Read the first row to get the headers
def data(source: Union[str, "pd.DataFrame"]) -> List[Any]:
"""
Parse data from. various sources into a list of DataRow objects

Supports:
- CSV file path (strings)
- pandas DataFrames

Args:
source
"""
Comment on lines +554 to +563
if isinstance(source, str):
return _data_from_csv(source)
elif pd is not None and isinstance(source, pd.DataFrame):
return _data_from_pandas(source)
else:
raise RevisitError(
message=f"Unsupported data source type: {type(source)}. Use CSV file path (str), pandas DataFrame, or Polars DataFrame"
)
Comment on lines +569 to +571


def _data_from_csv(file_path: str) -> List[Any]:
# Read the first row to get the headers
with open(file_path, mode='r') as csvfile:
csv_reader = csv.DictReader(csvfile)
headers = csv_reader.fieldnames
Expand All @@ -568,6 +592,20 @@ def data(file_path: str) -> List[Any]:

return data_rows

def _data_from_pandas(df: pd.DataFrame) -> List[Any]:
"""Convert pandas DataFrame to list of DataRow objects."""
if df.empty:
raise RevisitError(message="DataFrame is empty.")

headers = df.columns.tolist()
DataRow = make_dataclass("DataRow", [(header, Any) for header in headers])
data_rows = []

# Convert to dict records in one shot - much faster than iterrows
records = df.to_dict('records')
data_rows = [DataRow(**record) for record in records]

return data_rows

def widget(study: _WrappedStudyConfig, revisitPath: str = '', server=False, pathToLib=''):

Expand Down
28 changes: 28 additions & 0 deletions tests/test_dataframe_support.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import pandas as pd
import sys
sys.path.insert(0, '/Users/shenglong/Downloads/revisitpy/src')

from revisitpy import data

Comment on lines +1 to +6
# Test 1: CSV file
print("Test 1: CSV file")
csv_data = data("tests/config.json") # Replace with actual CSV
print(f"✓ CSV loaded: {len(csv_data)} rows")

# Test 2: Pandas DataFrame
print("\nTest 2: Pandas DataFrame")
df = pd.DataFrame({
'id': [1, 2, 3],
'b1': [0.32, 1.2, 0.6],
'b2': [0.01, 1.2, 1.1]
})
df_data = data(df)
print(f"✓ DataFrame loaded: {len(df_data)} rows")
print(f" First row: {df_data[0]}")

# Test 3: Error handling
print("\nTest 3: Error handling")
try:
data(123) # Should fail
except Exception as e:
print(f"✓ Correctly raised error: {str(e)[:50]}...")
Comment on lines +7 to +28
Loading