[Enhancement] Swappable Packages (#216)

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#216
Reviewed-by: liambrydon <lbry121@aucklanduni.ac.nz>
Reviewed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
2025-11-14 21:42:39 +13:00
parent d584791ee8
commit a8554c903c
22 changed files with 239 additions and 179 deletions

View File

@ -2,40 +2,41 @@ import abc
import hashlib
import json
import logging
import math
import os
import secrets
from abc import abstractmethod
from collections import defaultdict
from datetime import datetime
from typing import Union, List, Optional, Dict, Tuple, Set, Any
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from uuid import uuid4
import math
import joblib
import nh3
import numpy as np
from django.conf import settings as s
from django.contrib.auth.models import AbstractUser
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField
from django.db import models, transaction
from django.db.models import JSONField, Count, Q, QuerySet
from django.db.models import Count, JSONField, Q, QuerySet
from django.utils import timezone
from django.utils.functional import cached_property
from envipy_additional_information import EnviPyModel
from model_utils.models import TimeStampedModel
from polymorphic.models import PolymorphicModel
from sklearn.metrics import precision_score, recall_score, jaccard_score
from sklearn.metrics import jaccard_score, precision_score, recall_score
from sklearn.model_selection import ShuffleSplit
from utilities.chem import FormatConverter, ProductSet, PredictionResult, IndigoUtils
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
from utilities.ml import (
RuleBasedDataset,
ApplicabilityDomainPCA,
EnsembleClassifierChain,
RelativeReasoning,
EnviFormerDataset,
Dataset,
EnsembleClassifierChain,
EnviFormerDataset,
RelativeReasoning,
RuleBasedDataset,
)
logger = logging.getLogger(__name__)
@ -44,8 +45,6 @@ logger = logging.getLogger(__name__)
##########################
# User/Groups/Permission #
##########################
class User(AbstractUser):
email = models.EmailField(unique=True)
uuid = models.UUIDField(
@ -53,7 +52,10 @@ class User(AbstractUser):
)
url = models.TextField(blank=False, null=True, verbose_name="URL", unique=True)
default_package = models.ForeignKey(
"epdb.Package", verbose_name="Default Package", null=True, on_delete=models.SET_NULL
s.EPDB_PACKAGE_MODEL,
verbose_name="Default Package",
null=True,
on_delete=models.SET_NULL,
)
default_group = models.ForeignKey(
"Group",
@ -243,7 +245,7 @@ class UserPackagePermission(Permission):
)
user = models.ForeignKey("User", verbose_name="Permission to", on_delete=models.CASCADE)
package = models.ForeignKey(
"epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
s.EPDB_PACKAGE_MODEL, verbose_name="Permission on", on_delete=models.CASCADE
)
class Meta:
@ -259,7 +261,7 @@ class GroupPackagePermission(Permission):
)
group = models.ForeignKey("Group", verbose_name="Permission to", on_delete=models.CASCADE)
package = models.ForeignKey(
"epdb.Package", verbose_name="Permission on", on_delete=models.CASCADE
s.EPDB_PACKAGE_MODEL, verbose_name="Permission on", on_delete=models.CASCADE
)
class Meta:
@ -728,10 +730,13 @@ class Package(EnviPathModel):
rules = sorted(rules, key=lambda x: x.url)
return rules
class Meta:
swappable = "EPDB_PACKAGE_MODEL"
class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin):
package = models.ForeignKey(
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
)
default_structure = models.ForeignKey(
"CompoundStructure",
@ -781,7 +786,7 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin
@staticmethod
@transaction.atomic
def create(
package: Package, smiles: str, name: str = None, description: str = None, *args, **kwargs
package: "Package", smiles: str, name: str = None, description: str = None, *args, **kwargs
) -> "Compound":
if smiles is None or smiles.strip() == "":
raise ValueError("SMILES is required")
@ -1061,7 +1066,7 @@ class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey(
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
)
# # https://github.com/django-polymorphic/django-polymorphic/issues/229
@ -1167,7 +1172,7 @@ class SimpleAmbitRule(SimpleRule):
@staticmethod
@transaction.atomic
def create(
package: Package,
package: "Package",
name: str = None,
description: str = None,
smirks: str = None,
@ -1241,7 +1246,7 @@ class SimpleAmbitRule(SimpleRule):
@property
def related_reactions(self):
qs = Package.objects.filter(reviewed=True)
qs = s.GET_PACKAGE_MODEL().objects.filter(reviewed=True)
return self.reaction_rule.filter(package__in=qs).order_by("name")
@property
@ -1333,7 +1338,7 @@ class SequentialRuleOrdering(models.Model):
class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin):
package = models.ForeignKey(
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
)
educts = models.ManyToManyField(
"epdb.CompoundStructure", verbose_name="Educts", related_name="reaction_educts"
@ -1355,7 +1360,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
@staticmethod
@transaction.atomic
def create(
package: Package,
package: "Package",
name: str = None,
description: str = None,
educts: Union[List[str], List[CompoundStructure]] = None,
@ -1514,7 +1519,7 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey(
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
)
setting = models.ForeignKey(
"epdb.Setting", verbose_name="Setting", on_delete=models.CASCADE, null=True, blank=True
@ -2076,7 +2081,7 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin):
class EPModel(PolymorphicModel, EnviPathModel):
package = models.ForeignKey(
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
)
def _url(self):
@ -2085,17 +2090,17 @@ class EPModel(PolymorphicModel, EnviPathModel):
class PackageBasedModel(EPModel):
rule_packages = models.ManyToManyField(
"Package",
s.EPDB_PACKAGE_MODEL,
verbose_name="Rule Packages",
related_name="%(app_label)s_%(class)s_rule_packages",
)
data_packages = models.ManyToManyField(
"Package",
s.EPDB_PACKAGE_MODEL,
verbose_name="Data Packages",
related_name="%(app_label)s_%(class)s_data_packages",
)
eval_packages = models.ManyToManyField(
"Package",
s.EPDB_PACKAGE_MODEL,
verbose_name="Evaluation Packages",
related_name="%(app_label)s_%(class)s_eval_packages",
)
@ -3400,7 +3405,7 @@ class PluginModel(EPModel):
class Scenario(EnviPathModel):
package = models.ForeignKey(
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
s.EPDB_PACKAGE_MODEL, verbose_name="Package", on_delete=models.CASCADE, db_index=True
)
scenario_date = models.CharField(max_length=256, null=False, blank=False, default="No date")
scenario_type = models.CharField(
@ -3555,7 +3560,7 @@ class Setting(EnviPathModel):
)
rule_packages = models.ManyToManyField(
"Package",
s.EPDB_PACKAGE_MODEL,
verbose_name="Setting Rule Packages",
related_name="setting_rule_packages",
blank=True,