Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ wheels/

# Virtual environments
.venv

.zed
.ruff_cache
.ropeproject
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
ports:
- "8000:8000"
frontend:
build: ./meshwork/frontend/meshwork-ui
build: ./meshwork/frontend/webapp
ports:
- "3000:3000"
depends_on:
Expand Down
15 changes: 10 additions & 5 deletions meshwork/backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
# Install poetry (isolated installation)
ENV POETRY_HOME=/opt/poetry
ENV POETRY_VERSION=2.1.0
RUN curl -sSL https://install.python-poetry.org | python3 - && \
cd /usr/local/bin && \
ln -s /opt/poetry/bin/poetry && \
poetry config virtualenvs.in-project true

# Set working directory
WORKDIR /app

# Copy dependency files
COPY pyproject.toml uv.lock ./
COPY pyproject.toml poetry.lock ./

# Install dependencies in a virtual environment
RUN uv sync --frozen --no-cache --no-dev
RUN poetry install --no-interaction --no-ansi --only main --no-root

# Production stage
FROM python:3.12-slim-bullseye
Expand Down Expand Up @@ -62,4 +67,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
EXPOSE 8000

# Use proper signal handling and run on port 8000 (standard for development)
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
40 changes: 36 additions & 4 deletions meshwork/backend/app.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import logging

from pydantic import BaseModel
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from core.task import Task
from core.task import Task, Status
from core.user import User
from core.graph import TaskGraph

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def setup_examples() -> tuple[TaskGraph, User]:
"""Initialize example tasks and users"""
example_graph = TaskGraph()
example_graph.id = "abc123"
user = User(id=123, name="John Doe", email="john@example.com", graphs=[example_graph.id])
task_1 = Task(name="Task 1", description="Description 1", status=Status.TODO)
logger.info(f"Created task {task_1.id}")
task_2 = Task(name="Task 2", description="Description 2", status=Status.TODO)
logger.info(f"Created task {task_2.id}")
task_3 = Task(name="Task 3", description="Description 3", status=Status.TODO, depends_on=[task_1.id, task_2.id])
logger.info(f"Created task {task_3.id}")
example_graph.add_task(task_1)
example_graph.add_task(task_2)
example_graph.add_task(task_3)
return example_graph, user

app = FastAPI()
TG = TaskGraph()
logger = logging.getLogger(__name__)
TG, user = setup_examples()
logger.info(f"Initialized graph {TG.id}")
task_graphs = {
TG.id: TG
Expand All @@ -19,12 +37,16 @@
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # React's default port
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

class ConnectNodesRequest(BaseModel):
node_dependency: str
node_dependee: str

@app.get("/")
async def root():
return {"message": "Hello World"}
Expand Down Expand Up @@ -66,3 +88,13 @@ async def delete_task(graph_id: str, task_id: str):
async def edit_task(graph_id: str, task_id: str, new_task: Task):
task_graphs[graph_id].edit_task(task_id, new_task)
return {"message": f"Task {task_id} edited"}

@app.post("/v0/{graph_id}/connect_nodes/")
async def connect_nodes(graph_id: str, request: ConnectNodesRequest):
task_graphs[graph_id].connect_nodes(request.node_dependency, request.node_dependee)
return {"message": f"Nodes {request.node_dependency} and {request.node_dependee} connected"}

@app.post("/v0/{graph_id}/disconnect_nodes/")
async def disconnect_nodes(graph_id: str, request: ConnectNodesRequest):
task_graphs[graph_id].disconnect_nodes(request.node_dependency, request.node_dependee)
return {"message": f"Nodes {request.node_dependency} and {request.node_dependee} disconnected"}
25 changes: 23 additions & 2 deletions meshwork/backend/core/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ def load_backup(self):
def add_task(self, task: Task):
"""Add a task to the graph."""
self.graph.add_node(task.id, task=task)
logger.info(f"Added task {task.id} to the graph")
for d in task.depends_on:
self.graph.add_edge(d, task.id)
logger.info(f"Added dependency {d} -> {task.id}")

self.set_blocked_tasks()

Expand All @@ -57,12 +59,31 @@ def get_all_tasks(self):
return []
return [self.graph.nodes[node]["task"] for node in self.graph.nodes]

def edit_task(self, task_id: str, new_task: Task):
def edit_task(self, task_id: str, fields: dict):
# TODO: take only the necessary fields from new_task
"""Edit a task in the graph."""
self.graph.nodes[task_id]["task"] = new_task
for key, value in fields.items():
self.graph.nodes[task_id]["task"].key = value
self.set_blocked_tasks()

def connect_nodes(self, node_dependency: str, node_dependee: str):
"""Connect two nodes in the graph."""
self.graph.add_edge(node_dependency, node_dependee)
self.graph.nodes[node_dependee]["task"].depends_on.append(node_dependency)
logger.info(f"Connected nodes {node_dependency} and {node_dependee}")

self.set_blocked_tasks()


def disconnect_nodes(self, node_dependency: str, node_dependee: str):
"""Disconnect two nodes in the graph."""
self.graph.remove_edge(node_dependency, node_dependee)
self.graph.nodes[node_dependee]["task"].depends_on.remove(node_dependency)
logger.info(f"Disconnected nodes {node_dependency} and {node_dependee}")

self.set_blocked_tasks()


def set_blocked_tasks(self):
"""
Iterate over graph and set tasks as blocked if their dependencies are not DONE.
Expand Down
3 changes: 3 additions & 0 deletions meshwork/backend/core/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ class Task(BaseModel):
"""Task status"""
# completion_condition: Optional[CompletionCondition]
# """Condition that need to be met for the task to be completed"""
def __init__(self, **data):
super().__init__(**data)
self.id = uuid.uuid4().hex
7 changes: 7 additions & 0 deletions meshwork/backend/core/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from pydantic import BaseModel, Field

class User(BaseModel):
id: int = Field(..., title="User ID")
email: str = Field(..., title="Email Address")
name: str = Field(..., title="Full Name")
graphs: list[str] = Field(..., title="Graph IDs")
Loading
Loading