Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
5 changes: 4 additions & 1 deletion bases/lif/mdr_restapi/transformation_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
async def create_transformation(
data: CreateTransformationDTO, response: Response, session: AsyncSession = Depends(get_session)
):
transformation = await transformation_service.create_transformation(session, data)
if data.TargetAttribute.EntityIdPath.__contains__(","):
transformation = await transformation_service.create_transformation_with_portable_entity_id_path(session, data)
else:
transformation = await transformation_service.create_transformation(session, data)
# Set the Location header with the new entity association ID
response.headers["Location"] = f"/transformation_groups/transformations/{transformation.Id}"
return transformation
Expand Down
6 changes: 3 additions & 3 deletions components/lif/mdr_dto/transformation_dto.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
from typing import List, Optional

from lif.datatypes.mdr_sql_model import ExpressionLanguageType
from pydantic import BaseModel


class TransformationAttributeDTO(BaseModel):
Expand All @@ -24,7 +24,7 @@ class Config:


class CreateTransformationAttributeDTO(BaseModel):
AttributeId: int
AttributeId: Optional[int] = None # No longer used
EntityId: Optional[int] = None # Existing column
# AttributeName: Optional[str] = None
# EntityName: Optional[str] = None
Expand Down
18 changes: 16 additions & 2 deletions components/lif/mdr_services/entity_association_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import List, Optional

from fastapi import HTTPException
from lif.datatypes.mdr_sql_model import DataModel, DataModelType, Entity, EntityAssociation, ExtInclusionsFromBaseDM
from lif.mdr_dto.entity_association_dto import (
Expand All @@ -11,9 +12,8 @@
from lif.mdr_services.helper_service import check_datamodel_by_id, check_entity_by_id
from lif.mdr_utils.logger_config import get_logger
from sqlalchemy.ext.asyncio import AsyncSession
from sqlmodel import or_, select
from sqlalchemy.orm import aliased

from sqlmodel import or_, select

logger = get_logger(__name__)

Expand Down Expand Up @@ -61,6 +61,20 @@ async def check_existing_association(
return result.scalar_one_or_none() is not None


async def check_entity_association_strict(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using this instead of check_existing_association? There are cases where ExtendedByDataModelId could be null:

  • If data model type is BaseLIF or SourceSchema, then I think the ExtendedByDataModelId will always be null.
  • If the data model type is OrgLIF or PartnerLIF then the ExtendedByDataModelId may or may not be null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't like that it was an 'or' condition in check_existing_association. I felt we should be able to determine what value the ExtendedByDataModelId should be prior to invoking the method.

Copy link

@mgwozdz-unicon mgwozdz-unicon Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been having a hard time trying to determine how we could reverse engineer the ExtendedByDataModelId of the association. I think what I said in my other comment is true that for the OrgLIF and PartnerLIF, these are the cases where ExtendedByDataModelId of the association will not be null:

  1. The entity/attribute itself is an Extension and has ExtendedByDataModelId non-null.
  2. Someone used "+ Existing" to create an association in their OrgLIF or PartnerLIF that does not exist in BaseLIF.

That 2nd case is difficult to discern. The only thing I can think of to prove this is:

  1. If the Association has Extension=true, then ExtendedByDataModelId=anchor_data_model_id.

But we're trying to fetch the Association based off of this info, so we don't know yet whether or not it's an extension. I guess if you wanted, you could fetch the Association with the 'or' condition and then check if Extension=true and if it does then check that ExtendedByDataModelId=anchor_data_model_id.

Copy link
Contributor Author

@cbeach47 cbeach47 Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like from your last paragraph we can determine existance by the query:

!Extension AND ExtendedByDM == AnchorDM
OR 
Extension AND ExtendedByDM == Null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's backwards. And to summarize the full context:
If data_model_type == BaseLIF or SourceSchema, then Extension is always False and ExtendedByDataModelId is always null.
If data_model_type == OrgLIF or PartnerLIF, then...

!Extension AND ExtendedByDM == Null
OR 
Extension AND ExtendedByDM == AnchorDM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙃 I coded it the way you just noted , but then wrote my comment backwards.

session: AsyncSession, parent_entity_id: int, child_entity_id: int, extended_by_data_model_id: int | None
) -> bool:
query = select(EntityAssociation).where(
EntityAssociation.ParentEntityId == parent_entity_id,
EntityAssociation.ChildEntityId == child_entity_id,
EntityAssociation.Deleted == False,
EntityAssociation.ExtendedByDataModelId == extended_by_data_model_id,
)
result = await session.execute(query)
return result.scalar_one_or_none() is not None


# TODO: Should any of this go into check_transformation_attribute()?
async def validate_entity_associations_for_transformation_attribute(
session: AsyncSession, transformation_attribute: TransformationAttributeDTO
) -> bool:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import List, Optional, Tuple

from fastapi import HTTPException
from sqlalchemy import Select, func, or_, select
from lif.datatypes.mdr_sql_model import (
Attribute,
DataModelType,
Expand All @@ -15,6 +15,7 @@
)
from lif.mdr_services.helper_service import check_attribute_by_id, check_datamodel_by_id, check_entity_by_id
from lif.mdr_utils.logger_config import get_logger
from sqlalchemy import Select, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession

logger = get_logger(__name__)
Expand All @@ -41,6 +42,19 @@ async def check_existing_association(
return result.scalar_one_or_none() is not None


async def check_entity_attribute_association_strict(
session: AsyncSession, entity_id: int, attribute_id: int, extended_by_data_model_id: int | None
) -> bool:
query = select(EntityAttributeAssociation).where(
EntityAttributeAssociation.EntityId == entity_id,
EntityAttributeAssociation.AttributeId == attribute_id,
EntityAttributeAssociation.Deleted == False,
EntityAttributeAssociation.ExtendedByDataModelId == extended_by_data_model_id,
)
result = await session.execute(query)
return result.scalar_one_or_none() is not None


async def create_entity_attribute_association(session: AsyncSession, data: CreateEntityAttributeAssociationDTO):
# checking if provided entity id and attribute id exist or not
entity = await check_entity_by_id(session=session, id=data.EntityId)
Expand Down
18 changes: 18 additions & 0 deletions components/lif/mdr_services/helper_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ async def check_entity_attribute_association(session: AsyncSession, entity_id: i
return associations


async def check_entity_attribute_association_with_model(
session: AsyncSession, entity_id: int, attribute_id: int, extended_by_data_model_id: int
):
query = select(EntityAttributeAssociation).where(
EntityAttributeAssociation.EntityId == entity_id,
EntityAttributeAssociation.AttributeId == attribute_id,
EntityAttributeAssociation.ExtendedByDataModelId == extended_by_data_model_id,
)
result = await session.execute(query)
associations = result.fetchall()
if not associations:
raise HTTPException(
status_code=404,
detail=f"EntityAttributeAssociation with EntityId {entity_id} and AttributeId {attribute_id} not found",
)
return associations


async def check_value_set_by_id(session: AsyncSession, id: int):
value_set = await session.get(ValueSet, id)
if not value_set:
Expand Down
17 changes: 15 additions & 2 deletions components/lif/mdr_services/inclusions_service.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from fastapi import HTTPException
from lif.datatypes.mdr_sql_model import DatamodelElementType, EntityAttributeAssociation, ExtInclusionsFromBaseDM
from lif.mdr_dto.inclusion_dto import CreateInclusionDTO, InclusionDTO, UpdateInclusionDTO
from lif.mdr_services.attribute_service import get_attribute_by_id
from lif.mdr_services.entity_service import get_entity_by_id
from lif.mdr_services.helper_service import check_datamodel_by_id
from lif.mdr_utils.logger_config import get_logger
from sqlalchemy.ext.asyncio import AsyncSession
from lif.datatypes.mdr_sql_model import EntityAttributeAssociation, ExtInclusionsFromBaseDM
from sqlmodel import select, func
from sqlmodel import func, select

logger = get_logger(__name__)

Expand Down Expand Up @@ -229,3 +229,16 @@ async def get_attribute_inclusions_by_data_model_id_and_entity_id(
inclusions = result.scalars().all()
inclusion_dtos = [InclusionDTO.from_orm(inclusion) for inclusion in inclusions]
return inclusion_dtos


async def check_existing_inclusion(
session: AsyncSession, type: DatamodelElementType, node_id: int, included_by_data_model_id: int
) -> bool:
query = select(ExtInclusionsFromBaseDM).where(
ExtInclusionsFromBaseDM.ExtDataModelId == included_by_data_model_id,
ExtInclusionsFromBaseDM.IncludedElementId == node_id,
ExtInclusionsFromBaseDM.ElementType == type,
ExtInclusionsFromBaseDM.Deleted == False,
)
result = await session.execute(query)
return result.scalar_one_or_none() is not None
Loading