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
30 changes: 22 additions & 8 deletions carbonserver/carbonserver/api/infra/database/sql_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class Emission(Base):
gpu_energy = Column(Float)
ram_energy = Column(Float)
energy_consumed = Column(Float)
run_id = Column(UUID(as_uuid=True), ForeignKey("runs.id"))
run_id = Column(UUID(as_uuid=True), ForeignKey("runs.id", ondelete="CASCADE"))
run = relationship("Run", back_populates="emissions")

def __repr__(self):
Expand All @@ -37,7 +37,9 @@ class Run(Base):
__tablename__ = "runs"
id = Column(UUID(as_uuid=True), primary_key=True, index=True, default=uuid.uuid4)
timestamp = Column(DateTime)
experiment_id = Column(UUID(as_uuid=True), ForeignKey("experiments.id"))
experiment_id = Column(
UUID(as_uuid=True), ForeignKey("experiments.id", ondelete="CASCADE")
)
os = Column(String, nullable=True)
python_version = Column(String, nullable=True)
codecarbon_version = Column(String, nullable=True)
Expand All @@ -52,7 +54,9 @@ class Run(Base):
ram_total_size = Column(Float, nullable=True)
tracking_mode = Column(String, nullable=True)
experiment = relationship("Experiment", back_populates="runs")
emissions = relationship("Emission", back_populates="run")
emissions = relationship(
"Emission", back_populates="run", cascade="all, delete-orphan"
)

def __repr__(self):
return (
Expand Down Expand Up @@ -87,9 +91,13 @@ class Experiment(Base):
on_cloud = Column(Boolean, default=False)
cloud_provider = Column(String)
cloud_region = Column(String)
project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id"))
project_id = Column(
UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE")
)
project = relationship("Project", back_populates="experiments")
runs = relationship("Run", back_populates="experiment")
runs = relationship(
"Run", back_populates="experiment", cascade="all, delete-orphan"
)

def __repr__(self):
return (
Expand All @@ -111,9 +119,13 @@ class Project(Base):
description = Column(String)
public = Column(Boolean, default=False)
organization_id = Column(UUID(as_uuid=True), ForeignKey("organizations.id"))
experiments = relationship("Experiment", back_populates="project")
experiments = relationship(
"Experiment", back_populates="project", cascade="all, delete-orphan"
)
organization = relationship("Organization", back_populates="projects")
project_tokens = relationship("ProjectToken", back_populates="project")
project_tokens = relationship(
"ProjectToken", back_populates="project", cascade="all, delete-orphan"
)

def __repr__(self):
return (
Expand Down Expand Up @@ -176,7 +188,9 @@ def __repr__(self):
class ProjectToken(Base):
__tablename__ = "project_tokens"
id = Column(UUID(as_uuid=True), primary_key=True, index=True, default=uuid.uuid4)
project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id"))
project_id = Column(
UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE")
)
name = Column(String)
hashed_token = Column(String, nullable=False)
lookup_value = Column(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""add_cascade_delete_for_projects

Revision ID: 2a898cf81c3e
Revises: f3a10c95079f
Create Date: 2025-10-05 11:40:28.037992

"""

from alembic import op

# revision identifiers, used by Alembic.
revision = "2a898cf81c3e"
down_revision = "f3a10c95079f"
branch_labels = None
depends_on = None


def upgrade():
"""
Add CASCADE delete behavior to foreign keys in the project hierarchy.

When a Project is deleted, this will automatically delete:
- All Experiments belonging to that project
- All Runs belonging to those experiments
- All Emissions belonging to those runs
- All ProjectTokens belonging to that project
"""

# 1. Add CASCADE delete to emissions.run_id foreign key
# Drop existing constraint (using the name from initial migration)
op.drop_constraint("fk_emissions_runs", "emissions", type_="foreignkey")
# Recreate with CASCADE
op.create_foreign_key(
"fk_emissions_runs", "emissions", "runs", ["run_id"], ["id"], ondelete="CASCADE"
)

# 2. Add CASCADE delete to runs.experiment_id foreign key
op.drop_constraint("fk_runs_experiments", "runs", type_="foreignkey")
op.create_foreign_key(
"fk_runs_experiments",
"runs",
"experiments",
["experiment_id"],
["id"],
ondelete="CASCADE",
)

# 3. Add CASCADE delete to experiments.project_id foreign key
op.drop_constraint("fk_experiments_projects", "experiments", type_="foreignkey")
op.create_foreign_key(
"fk_experiments_projects",
"experiments",
"projects",
["project_id"],
["id"],
ondelete="CASCADE",
)

# 4. Add CASCADE delete to project_tokens.project_id foreign key
op.drop_constraint(
"fk_project_tokens_projects", "project_tokens", type_="foreignkey"
)
op.create_foreign_key(
"fk_project_tokens_projects",
"project_tokens",
"projects",
["project_id"],
["id"],
ondelete="CASCADE",
)


def downgrade():
"""
Remove CASCADE delete behavior from foreign keys.
This restores the original behavior where deleting a project with
child records will fail with a foreign key constraint violation.
"""

# 4. Remove CASCADE from project_tokens.project_id
op.drop_constraint(
"fk_project_tokens_projects", "project_tokens", type_="foreignkey"
)
op.create_foreign_key(
"fk_project_tokens_projects",
"project_tokens",
"projects",
["project_id"],
["id"],
)

# 3. Remove CASCADE from experiments.project_id
op.drop_constraint("fk_experiments_projects", "experiments", type_="foreignkey")
op.create_foreign_key(
"fk_experiments_projects", "experiments", "projects", ["project_id"], ["id"]
)

# 2. Remove CASCADE from runs.experiment_id
op.drop_constraint("fk_runs_experiments", "runs", type_="foreignkey")
op.create_foreign_key(
"fk_runs_experiments", "runs", "experiments", ["experiment_id"], ["id"]
)

# 1. Remove CASCADE from emissions.run_id
op.drop_constraint("fk_emissions_runs", "emissions", type_="foreignkey")
op.create_foreign_key("fk_emissions_runs", "emissions", "runs", ["run_id"], ["id"])
122 changes: 122 additions & 0 deletions carbonserver/docs/cascade-delete-diagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions carbonserver/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ Changelog = "https://github.com/mlco2/codecarbon/releases"

[tool.hatch.build]
exclude = ["*"]

[tool.pytest.ini_options]
pythonpath = "."
Loading
Loading