Skip to content

Commit

Permalink
Merge pull request #411 from aiondemand/enh/entry_status
Browse files Browse the repository at this point in the history
Make `aiod_entry.status` a true ENUM
  • Loading branch information
PGijsbers authored Jan 6, 2025
2 parents 28ef235 + e261d2b commit 8132e27
Show file tree
Hide file tree
Showing 17 changed files with 174 additions and 118 deletions.
90 changes: 90 additions & 0 deletions alembic/alembic/versions/1662d64ebe23_make_draft_status_enum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""make draft status enum
Revision ID: 1662d64ebe23
Revises: d09ed8ad4533
Create Date: 2024-12-17 09:02:30.480835
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy import Column, INT, String, Enum

from database.model.field_length import NORMAL
from database.model.concept.aiod_entry import EntryStatus

# revision identifiers, used by Alembic.
revision: str = "1662d64ebe23"
down_revision: Union[str, None] = "d09ed8ad4533"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
op.drop_table("aiod_entry_status_link")
op.add_column(
"aiod_entry",
Column("status", Enum(EntryStatus)),
)
op.execute(
"""
UPDATE aiod_entry
INNER JOIN status
ON status.identifier = aiod_entry.status_identifier
SET aiod_entry.status = status.name
"""
)
op.drop_constraint(
constraint_name="aiod_entry_ibfk_1",
table_name="aiod_entry",
type_="foreignkey",
)
op.drop_column(
table_name="aiod_entry",
column_name="status_identifier",
)
op.drop_table("status")


def downgrade() -> None:
# No need to recreate table status link, it was not used.
op.create_table(
"status",
Column("identifier", type_=INT, primary_key=True),
Column(
"name",
unique=True,
type_=String(NORMAL),
index=True,
),
)
op.execute(
"""
INSERT INTO status
VALUES (1, 'draft'), (2, 'published'), (3, 'rejected'), (4, 'submitted')
"""
)
op.add_column(
"aiod_entry",
Column("status_identifier", INT),
)
op.execute(
"""
UPDATE aiod_entry
INNER JOIN status
ON aiod_entry.status = status.name
SET aiod_entry.status_identifier = status.identifier
"""
)
op.drop_column(
"aiod_entry",
"status",
)
op.create_foreign_key(
"aiod_entry_ibfk_1",
"aiod_entry",
"status",
["status_identifier"],
["identifier"],
)
1 change: 1 addition & 0 deletions scripts/set_alembic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
docker exec -it sqlserver mysql -uroot -pok --database=aiod -e "UPDATE alembic_version SET version_num = '$1'"
7 changes: 0 additions & 7 deletions src/connectors/example/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from database.model.agent.organisation_type import OrganisationType
from database.model.ai_asset.license import License
from database.model.ai_resource.application_area import ApplicationArea
from database.model.concept.status import Status
from database.model.educational_resource.educational_resource_type import EducationalResourceType
from database.model.event.event_mode import EventMode
from database.model.event.event_status import EventStatus
Expand Down Expand Up @@ -60,9 +59,3 @@ class EnumConnectorNewsCategory(EnumConnector[NewsCategory]):
def __init__(self):
json_path = ENUM_PATH / "news_categories.json"
super().__init__(json_path, NewsCategory)


class EnumConnectorStatus(EnumConnector[Status]):
def __init__(self):
json_path = ENUM_PATH / "status.json"
super().__init__(json_path, Status)
5 changes: 0 additions & 5 deletions src/connectors/example/resources/enum/status.json

This file was deleted.

43 changes: 22 additions & 21 deletions src/database/model/concept/aiod_entry.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import enum
from datetime import datetime
from typing import TYPE_CHECKING

import sqlalchemy
from sqlalchemy import Column
from sqlmodel import SQLModel, Field, Relationship

from database.model.concept.status import Status
from database.model.helper_functions import many_to_many_link_factory
from database.model.relationships import ManyToOne, ManyToMany
from database.model.relationships import ManyToMany
from database.model.serializers import (
AttributeSerializer,
create_getter_dict,
FindByNameDeserializer,
)

if TYPE_CHECKING:
Expand All @@ -21,6 +22,13 @@ class AIoDEntryBase(SQLModel):
known on other platforms, etc."""


class EntryStatus(enum.StrEnum):
DRAFT = enum.auto()
PUBLISHED = enum.auto()
REJECTED = enum.auto() # Not used, for historical reasons
SUBMITTED = enum.auto()


class AIoDEntryORM(AIoDEntryBase, table=True): # type: ignore [call-arg]
"""Metadata of the metadata: when was the metadata last updated, with what identifiers is it
known on other platforms, etc."""
Expand All @@ -31,20 +39,16 @@ class AIoDEntryORM(AIoDEntryBase, table=True): # type: ignore [call-arg]
editor: list["Person"] = Relationship(
link_model=many_to_many_link_factory("aiod_entry", "person", table_prefix="editor"),
)
status_identifier: int | None = Field(foreign_key=Status.__tablename__ + ".identifier")
status: Status | None = Relationship()
status: EntryStatus = Field(
sa_column=Column(sqlalchemy.Enum(EntryStatus)), default=EntryStatus.DRAFT
)

# date_modified is updated in the resource_router
date_modified: datetime = Field(default_factory=datetime.utcnow)
date_created: datetime = Field(default_factory=datetime.utcnow)

class RelationshipConfig:
editor: list[int] = ManyToMany() # No deletion triggers: "orphan" Persons should be kept
status: str | None = ManyToOne(
example="draft",
identifier_name="status_identifier",
deserializer=FindByNameDeserializer(Status),
)


class AIoDEntryCreate(AIoDEntryBase):
Expand All @@ -53,10 +57,10 @@ class AIoDEntryCreate(AIoDEntryBase):
default_factory=list,
schema_extra={"example": []},
)
status: str | None = Field(
description="Status of the entry (published, draft, rejected)",
schema_extra={"example": "published"},
default="draft",
status: EntryStatus = Field(
description="Status of the entry. One of {', '.join(EntryStatus)}.",
default=EntryStatus.DRAFT,
schema_extra={"example": EntryStatus.DRAFT},
)


Expand All @@ -66,10 +70,9 @@ class AIoDEntryRead(AIoDEntryBase):
default_factory=list,
schema_extra={"example": []},
)
status: str | None = Field(
description="Status of the entry (published, draft, rejected)",
schema_extra={"example": "published"},
default="draft",
status: EntryStatus = Field(
description="Status of the entry ({', '.join(EntryStatus)}).",
schema_extra={"example": EntryStatus.PUBLISHED},
)
date_modified: datetime | None = Field(
description="The datetime on which the metadata was last updated in the AIoD platform,"
Expand All @@ -86,6 +89,4 @@ class AIoDEntryRead(AIoDEntryBase):
)

class Config:
getter_dict = create_getter_dict(
{"editor": AttributeSerializer("identifier"), "status": AttributeSerializer("name")}
)
getter_dict = create_getter_dict({"editor": AttributeSerializer("identifier")})
29 changes: 0 additions & 29 deletions src/database/model/concept/status.py

This file was deleted.

3 changes: 2 additions & 1 deletion src/routers/search_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@ def _cast_resource(
}
resource = read_class(**kwargs)
resource.aiod_entry = AIoDEntryRead(
date_modified=resource_dict["date_modified"], status=None
date_modified=resource_dict["date_modified"],
status=resource_dict["status"],
)
resource.description = {
"plain": resource_dict["description_plain"],
Expand Down
8 changes: 5 additions & 3 deletions src/tests/connectors/example/test_enum_connector.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from connectors.example.enum import EnumConnectorStatus
from connectors.example.enum import EnumConnectorEventMode


def test_fetch_happy_path():
connector = EnumConnectorStatus()
connector = EnumConnectorEventMode()
resources = list(connector.fetch())
assert set(resources) == {"published", "draft", "rejected"}

allowed_modes = {"offline", "online", "hybrid"}
assert set(resources) == allowed_modes
15 changes: 6 additions & 9 deletions src/tests/database/deletion/test_hard_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@
from sqlmodel import select

from database.deletion import hard_delete
from database.model.concept.aiod_entry import AIoDEntryORM
from database.model.concept.status import Status
from database.model.concept.aiod_entry import AIoDEntryORM, EntryStatus
from database.session import DbSession
from tests.testutils.test_resource import factory, TestResource


def test_hard_delete(
draft: Status,
):
def test_hard_delete():
now = datetime.datetime.now()
deletion_time = now - datetime.timedelta(seconds=10)
with DbSession() as session:
Expand All @@ -21,28 +18,28 @@ def test_hard_delete(
title="test_resource_to_keep",
platform="example",
platform_resource_identifier=1,
status=draft,
status=EntryStatus.DRAFT,
date_deleted=None,
),
factory(
title="test_resource_to_keep_2",
platform="example",
platform_resource_identifier=2,
status=draft,
status=EntryStatus.DRAFT,
date_deleted=now,
),
factory(
title="my_test_resource",
platform="example",
platform_resource_identifier=3,
status=draft,
status=EntryStatus.DRAFT,
date_deleted=deletion_time,
),
factory(
title="second_test_resource",
platform="example",
platform_resource_identifier=4,
status=draft,
status=EntryStatus.DRAFT,
date_deleted=deletion_time,
),
]
Expand Down
12 changes: 5 additions & 7 deletions src/tests/routers/generic/test_router_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import pytest
from starlette.testclient import TestClient

from database.model.concept.status import Status
from database.session import DbSession
from database.model.concept.aiod_entry import EntryStatus
from tests.testutils.test_resource import factory


Expand All @@ -13,7 +13,6 @@ def test_happy_path(
client_test_resource: TestClient,
identifier: int,
mocked_privileged_token: Mock,
draft: Status,
):
with DbSession() as session:
session.add_all(
Expand All @@ -22,13 +21,13 @@ def test_happy_path(
title="my_test_resource",
platform="example",
platform_resource_identifier=1,
status=draft,
status=EntryStatus.DRAFT,
),
factory(
title="second_test_resource",
platform="example",
platform_resource_identifier=2,
status=draft,
status=EntryStatus.DRAFT,
),
]
)
Expand All @@ -49,7 +48,6 @@ def test_non_existent(
client_test_resource: TestClient,
identifier: int,
mocked_privileged_token: Mock,
draft: Status,
):
with DbSession() as session:
session.add_all(
Expand All @@ -58,13 +56,13 @@ def test_non_existent(
title="my_test_resource",
platform="example",
platform_resource_identifier=1,
status=draft,
status=EntryStatus.DRAFT,
),
factory(
title="second_test_resource",
platform="example",
platform_resource_identifier=2,
status=draft,
status=EntryStatus.DRAFT,
),
]
)
Expand Down
Loading

0 comments on commit 8132e27

Please sign in to comment.