forked from enviPath/enviPy
Compare commits
2 Commits
297c87f4d9
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bc9f86ff1 | |||
| dba6514013 |
2
epdb/exceptions.py
Normal file
2
epdb/exceptions.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
class InvalidSMILESException(Exception):
|
||||||
|
pass
|
||||||
@ -1610,13 +1610,13 @@ class PathwayNode(Schema):
|
|||||||
dt50s: List[Dict[str, str]] = Field([], alias="dt50s")
|
dt50s: List[Dict[str, str]] = Field([], alias="dt50s")
|
||||||
engineeredIntermediate: bool = Field(None, alias="engineered_intermediate")
|
engineeredIntermediate: bool = Field(None, alias="engineered_intermediate")
|
||||||
id: str = Field(None, alias="url")
|
id: str = Field(None, alias="url")
|
||||||
idcomp: str = Field(None, alias="default_node_label.url")
|
idcomp: str = Field(None, alias="node_label_id")
|
||||||
idreact: str = Field(None, alias="default_node_label.url")
|
idreact: str = Field(None, alias="node_label_id")
|
||||||
image: str = Field(None, alias="image")
|
image: str = Field(None, alias="image")
|
||||||
imageSize: int = Field(None, alias="image_size")
|
imageSize: int = Field(None, alias="image_size")
|
||||||
name: str = Field(None, alias="name")
|
name: str = Field(None, alias="name")
|
||||||
proposed: List[Dict[str, str]] = Field([], alias="proposed_intermediate")
|
proposed: List[Dict[str, str]] = Field([], alias="proposed_intermediate")
|
||||||
smiles: str = Field(None, alias="default_node_label.smiles")
|
smiles: str = Field(None, alias="smiles")
|
||||||
pseudo: bool = Field(False, alias="pseudo")
|
pseudo: bool = Field(False, alias="pseudo")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -99,11 +99,15 @@ class Command(BaseCommand):
|
|||||||
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
new_license.image_link = f"https://licensebuttons.net/l/{cc_string}/4.0/88x31.png"
|
||||||
new_license.save()
|
new_license.save()
|
||||||
|
|
||||||
def import_package(self, data, owner):
|
def import_package(self, data, owner, all_envipath_user_group):
|
||||||
return PackageManager.import_legacy_package(
|
p = PackageManager.import_legacy_package(
|
||||||
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
data, owner, keep_ids=True, add_import_timestamp=False, trust_reviewed=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PackageManager.grant_read(owner, p, all_envipath_user_group)
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
def create_default_setting(self, owner, packages):
|
def create_default_setting(self, owner, packages):
|
||||||
s = SettingManager.create_setting(
|
s = SettingManager.create_setting(
|
||||||
owner,
|
owner,
|
||||||
@ -198,7 +202,7 @@ class Command(BaseCommand):
|
|||||||
s.BASE_DIR / "fixtures" / "packages" / "2025-07-18" / p, encoding="utf-8"
|
s.BASE_DIR / "fixtures" / "packages" / "2025-07-18" / p, encoding="utf-8"
|
||||||
).read()
|
).read()
|
||||||
)
|
)
|
||||||
imported_package = self.import_package(package_data, admin)
|
imported_package = self.import_package(package_data, admin, g)
|
||||||
mapping[p.replace(".json", "")] = imported_package
|
mapping[p.replace(".json", "")] = imported_package
|
||||||
|
|
||||||
setting = self.create_default_setting(admin, [mapping["EAWAG-BBD"]])
|
setting = self.create_default_setting(admin, [mapping["EAWAG-BBD"]])
|
||||||
|
|||||||
150
epdb/migrations/0027_alter_compound_aliases_and_more.py
Normal file
150
epdb/migrations/0027_alter_compound_aliases_and_more.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Generated by Django 6.0.3 on 2026-07-01 20:59
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("epdb", "0026_auto_20260602_1718"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compound",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compound",
|
||||||
|
name="default_structure",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="compound_default_structure",
|
||||||
|
to="epdb.compoundstructure",
|
||||||
|
verbose_name="Default Structure",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compound",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compoundstructure",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="compoundstructure",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="edge",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="edge",
|
||||||
|
name="edge_label",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="epdb.reaction",
|
||||||
|
verbose_name="Edge label",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="edge",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="node",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="node",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="pathway",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="pathway",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="medline_references",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(),
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Medline References",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="rules",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="reaction_rule", to="epdb.rule", verbose_name="Rule"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="reaction",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rule",
|
||||||
|
name="aliases",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.TextField(), blank=True, default=list, verbose_name="Aliases"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="rule",
|
||||||
|
name="scenarios",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, to="epdb.scenario", verbose_name="Attached Scenarios"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -31,6 +31,7 @@ from sklearn.model_selection import ShuffleSplit
|
|||||||
|
|
||||||
from bridge.contracts import Property
|
from bridge.contracts import Property
|
||||||
from bridge.dto import RunResult, PropertyPrediction
|
from bridge.dto import RunResult, PropertyPrediction
|
||||||
|
from epdb.exceptions import InvalidSMILESException
|
||||||
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
|
from utilities.chem import FormatConverter, IndigoUtils, PredictionResult, ProductSet
|
||||||
from utilities.ml import (
|
from utilities.ml import (
|
||||||
ApplicabilityDomainPCA,
|
ApplicabilityDomainPCA,
|
||||||
@ -631,7 +632,7 @@ class EnviPathModel(TimeStampedModel):
|
|||||||
|
|
||||||
class AliasMixin(models.Model):
|
class AliasMixin(models.Model):
|
||||||
aliases = ArrayField(
|
aliases = ArrayField(
|
||||||
models.TextField(blank=False, null=False), verbose_name="Aliases", default=list
|
models.TextField(blank=False, null=False), verbose_name="Aliases", default=list, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@ -654,7 +655,9 @@ class AliasMixin(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ScenarioMixin(models.Model):
|
class ScenarioMixin(models.Model):
|
||||||
scenarios = models.ManyToManyField("epdb.Scenario", verbose_name="Attached Scenarios")
|
scenarios = models.ManyToManyField(
|
||||||
|
"epdb.Scenario", verbose_name="Attached Scenarios", blank=True
|
||||||
|
)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def set_scenarios(self, scenarios: List["Scenario"]):
|
def set_scenarios(self, scenarios: List["Scenario"]):
|
||||||
@ -780,7 +783,7 @@ class Compound(
|
|||||||
"CompoundStructure",
|
"CompoundStructure",
|
||||||
verbose_name="Default Structure",
|
verbose_name="Default Structure",
|
||||||
related_name="compound_default_structure",
|
related_name="compound_default_structure",
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.SET_NULL,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -857,13 +860,13 @@ class Compound(
|
|||||||
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":
|
) -> "Compound":
|
||||||
if smiles is None or smiles.strip() == "":
|
if smiles is None or smiles.strip() == "":
|
||||||
raise ValueError("SMILES is required")
|
raise InvalidSMILESException("SMILES is required")
|
||||||
|
|
||||||
smiles = smiles.strip()
|
smiles = smiles.strip()
|
||||||
|
|
||||||
parsed = FormatConverter.from_smiles(smiles)
|
parsed = FormatConverter.from_smiles(smiles)
|
||||||
if parsed is None:
|
if parsed is None:
|
||||||
raise ValueError("Given SMILES is invalid")
|
raise InvalidSMILESException("Given SMILES is invalid")
|
||||||
|
|
||||||
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
|
standardized_smiles = FormatConverter.standardize(smiles, remove_stereo=True)
|
||||||
|
|
||||||
@ -1174,6 +1177,8 @@ class CompoundStructure(
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def as_svg(self, width: int = 800, height: int = 400):
|
def as_svg(self, width: int = 800, height: int = 400):
|
||||||
|
if self.molfile is not None and self.molfile.strip() != "":
|
||||||
|
return IndigoUtils.mol_to_svg(self.molfile, width=width, height=height)
|
||||||
return IndigoUtils.mol_to_svg(self.smiles, width=width, height=height)
|
return IndigoUtils.mol_to_svg(self.smiles, width=width, height=height)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1610,10 +1615,15 @@ class Reaction(
|
|||||||
products = models.ManyToManyField(
|
products = models.ManyToManyField(
|
||||||
"epdb.CompoundStructure", verbose_name="Products", related_name="reaction_products"
|
"epdb.CompoundStructure", verbose_name="Products", related_name="reaction_products"
|
||||||
)
|
)
|
||||||
rules = models.ManyToManyField("epdb.Rule", verbose_name="Rule", related_name="reaction_rule")
|
rules = models.ManyToManyField(
|
||||||
|
"epdb.Rule", verbose_name="Rule", related_name="reaction_rule", blank=True
|
||||||
|
)
|
||||||
multi_step = models.BooleanField(verbose_name="Multistep Reaction")
|
multi_step = models.BooleanField(verbose_name="Multistep Reaction")
|
||||||
medline_references = ArrayField(
|
medline_references = ArrayField(
|
||||||
models.TextField(blank=False, null=False), null=True, verbose_name="Medline References"
|
models.TextField(blank=False, null=False),
|
||||||
|
null=True,
|
||||||
|
verbose_name="Medline References",
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
external_identifiers = GenericRelation("ExternalIdentifier")
|
external_identifiers = GenericRelation("ExternalIdentifier")
|
||||||
@ -1630,7 +1640,7 @@ class Reaction(
|
|||||||
educts: Union[List[str], List[CompoundStructure]] = None,
|
educts: Union[List[str], List[CompoundStructure]] = None,
|
||||||
products: Union[List[str], List[CompoundStructure]] = None,
|
products: Union[List[str], List[CompoundStructure]] = None,
|
||||||
rules: Union[Rule | List[Rule]] = None,
|
rules: Union[Rule | List[Rule]] = None,
|
||||||
multi_step: bool = True,
|
multi_step: bool = False,
|
||||||
):
|
):
|
||||||
_educts = []
|
_educts = []
|
||||||
_products = []
|
_products = []
|
||||||
@ -2197,7 +2207,25 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
depth_map[0] = list()
|
depth_map[0] = list()
|
||||||
processed = set()
|
processed = set()
|
||||||
|
|
||||||
for n in self.root_nodes:
|
data_driven_root_nodes = (
|
||||||
|
self.node_set.all()
|
||||||
|
.annotate(prod_cnt=Count("edge_products"), educt_cnt=Count("edge_educts"))
|
||||||
|
.filter(prod_cnt=0, educt_cnt__gt=0)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Eval QuerySet
|
||||||
|
root_nodes_by_depth = list(self.root_nodes)
|
||||||
|
|
||||||
|
data_driven_root_nodes.update(depth=0)
|
||||||
|
root_nodes = [n for n in data_driven_root_nodes]
|
||||||
|
|
||||||
|
for n in root_nodes_by_depth:
|
||||||
|
if n not in root_nodes:
|
||||||
|
if len(n.edge_products.all()) == 0:
|
||||||
|
root_nodes.append(n)
|
||||||
|
|
||||||
|
for n in root_nodes:
|
||||||
depth_map[0].append(n)
|
depth_map[0].append(n)
|
||||||
|
|
||||||
# At most depth len(nodes) is possible
|
# At most depth len(nodes) is possible
|
||||||
@ -2277,7 +2305,12 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
"node_label_id": self.default_node_label.url,
|
"node_label_id": self.default_node_label.url,
|
||||||
"image": f"{self.url}?image=svg",
|
"image": f"{self.url}?image=svg",
|
||||||
"image_svg": IndigoUtils.mol_to_svg(
|
"image_svg": IndigoUtils.mol_to_svg(
|
||||||
self.default_node_label.smiles, width=40, height=40
|
self.default_node_label.molfile
|
||||||
|
if self.default_node_label.molfile is not None
|
||||||
|
and self.default_node_label.molfile.strip()
|
||||||
|
else self.default_node_label.smiles,
|
||||||
|
width=40,
|
||||||
|
height=40,
|
||||||
),
|
),
|
||||||
"image_type": "svg",
|
"image_type": "svg",
|
||||||
"name": self.get_name(),
|
"name": self.get_name(),
|
||||||
@ -2331,6 +2364,11 @@ class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin)
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def as_svg(self):
|
def as_svg(self):
|
||||||
|
if (
|
||||||
|
self.default_node_label.molfile is not None
|
||||||
|
and self.default_node_label.molfile.strip() != ""
|
||||||
|
):
|
||||||
|
return IndigoUtils.mol_to_svg(self.default_node_label.molfile)
|
||||||
return IndigoUtils.mol_to_svg(self.default_node_label.smiles)
|
return IndigoUtils.mol_to_svg(self.default_node_label.smiles)
|
||||||
|
|
||||||
def get_timeseries_data(self):
|
def get_timeseries_data(self):
|
||||||
|
|||||||
@ -19,6 +19,7 @@ from sentry_sdk import capture_exception
|
|||||||
|
|
||||||
from utilities.chem import FormatConverter, IndigoUtils
|
from utilities.chem import FormatConverter, IndigoUtils
|
||||||
from utilities.decorators import package_permission_required
|
from utilities.decorators import package_permission_required
|
||||||
|
from .exceptions import InvalidSMILESException
|
||||||
|
|
||||||
from .logic import (
|
from .logic import (
|
||||||
EPDBURLParser,
|
EPDBURLParser,
|
||||||
@ -754,6 +755,11 @@ def models(request):
|
|||||||
{"Model": s.SERVER_URL + "/model"},
|
{"Model": s.SERVER_URL + "/model"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
context["entity_type"] = "model"
|
||||||
|
context["api_endpoint"] = f"{s.SERVER_PATH}/api/v1/models/"
|
||||||
|
context["per_page"] = s.API_PAGINATION_DEFAULT_PAGE_SIZE
|
||||||
|
context["list_title"] = "models"
|
||||||
|
|
||||||
# Keep model_types for potential modal/action use
|
# Keep model_types for potential modal/action use
|
||||||
context["model_types"] = {
|
context["model_types"] = {
|
||||||
"ML Relative Reasoning": {
|
"ML Relative Reasoning": {
|
||||||
@ -766,19 +772,26 @@ def models(request):
|
|||||||
"requires_rule_packages": True,
|
"requires_rule_packages": True,
|
||||||
"requires_data_packages": True,
|
"requires_data_packages": True,
|
||||||
},
|
},
|
||||||
"EnviFormer": {
|
|
||||||
"type": "enviformer",
|
|
||||||
"requires_rule_packages": False,
|
|
||||||
"requires_data_packages": True,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.ENVIFORMER_PRESENT:
|
||||||
|
context["model_types"]["EnviFormer"] = (
|
||||||
|
{
|
||||||
|
"type": "enviformer",
|
||||||
|
"requires_rule_packages": False,
|
||||||
|
"requires_data_packages": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
if s.FLAGS.get("PLUGINS", False):
|
if s.FLAGS.get("PLUGINS", False):
|
||||||
for k, v in s.CLASSIFIER_PLUGINS.items():
|
for k, v in s.CLASSIFIER_PLUGINS.items():
|
||||||
context["model_types"][v.display()] = {
|
context["model_types"][v.display()] = {
|
||||||
"type": k,
|
"type": k,
|
||||||
"requires_rule_packages": v.requires_rule_packages(),
|
"requires_rule_packages": v.requires_rule_packages(),
|
||||||
"requires_data_packages": v.requires_data_packages(),
|
"requires_data_packages": v.requires_data_packages(),
|
||||||
|
"additional_parameters": v.Config.__name__.lower()
|
||||||
|
if v.Config.__name__ != ""
|
||||||
|
else None,
|
||||||
}
|
}
|
||||||
for k, v in s.PROPERTY_PLUGINS.items():
|
for k, v in s.PROPERTY_PLUGINS.items():
|
||||||
context["model_types"][v.display()] = {
|
context["model_types"][v.display()] = {
|
||||||
@ -787,12 +800,6 @@ def models(request):
|
|||||||
"requires_data_packages": v.requires_data_packages(),
|
"requires_data_packages": v.requires_data_packages(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Context for paginated template
|
|
||||||
context["entity_type"] = "model"
|
|
||||||
context["api_endpoint"] = f"{s.SERVER_PATH}/api/v1/models/"
|
|
||||||
context["per_page"] = s.API_PAGINATION_DEFAULT_PAGE_SIZE
|
|
||||||
context["list_title"] = "models"
|
|
||||||
|
|
||||||
return render(request, "collections/models_paginated.html", context)
|
return render(request, "collections/models_paginated.html", context)
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == "POST":
|
||||||
@ -906,12 +913,13 @@ def package_models(request, package_uuid):
|
|||||||
"requires_rule_packages": True,
|
"requires_rule_packages": True,
|
||||||
"requires_data_packages": True,
|
"requires_data_packages": True,
|
||||||
},
|
},
|
||||||
"EnviFormer": {
|
}
|
||||||
|
if s.ENVIFORMER_PRESENT:
|
||||||
|
context["model_types"]["EnviFormer"] = {
|
||||||
"type": "enviformer",
|
"type": "enviformer",
|
||||||
"requires_rule_packages": False,
|
"requires_rule_packages": False,
|
||||||
"requires_data_packages": True,
|
"requires_data_packages": True,
|
||||||
},
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if s.FLAGS.get("PLUGINS", False):
|
if s.FLAGS.get("PLUGINS", False):
|
||||||
for k, v in s.CLASSIFIER_PLUGINS.items():
|
for k, v in s.CLASSIFIER_PLUGINS.items():
|
||||||
@ -1087,6 +1095,11 @@ def package_model(request, package_uuid, model_uuid):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Sort data by prob desc
|
||||||
|
res["pred"] = sorted(
|
||||||
|
res["pred"], key=lambda x: x["probability"], reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
return JsonResponse(res, safe=False)
|
return JsonResponse(res, safe=False)
|
||||||
|
|
||||||
elif half_life:
|
elif half_life:
|
||||||
@ -1190,9 +1203,7 @@ def package(request, package_uuid):
|
|||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
if request.GET.get("export", False) == "true":
|
if request.GET.get("export", False) == "true":
|
||||||
filename = f"{current_package.get_name().replace(' ', '_')}_{current_package.uuid}.json"
|
filename = f"{current_package.get_name().replace(' ', '_')}_{current_package.uuid}.json"
|
||||||
pack_json = PackageManager.export_package(
|
pack_json = PackageManager.export_package(current_package)
|
||||||
current_package, include_models=False, include_external_identifiers=False
|
|
||||||
)
|
|
||||||
response = JsonResponse(pack_json, content_type="application/json")
|
response = JsonResponse(pack_json, content_type="application/json")
|
||||||
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||||
|
|
||||||
@ -2328,7 +2339,13 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid):
|
|||||||
node_description = request.POST.get("node-description")
|
node_description = request.POST.get("node-description")
|
||||||
|
|
||||||
node_smiles = request.POST.get("node-smiles").strip()
|
node_smiles = request.POST.get("node-smiles").strip()
|
||||||
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
|
||||||
|
try:
|
||||||
|
current_pathway.add_node(node_smiles, name=node_name, description=node_description)
|
||||||
|
except InvalidSMILESException:
|
||||||
|
return error(
|
||||||
|
request, "Node creation failed!", f"Given SMILES ({node_smiles}) is invalid"
|
||||||
|
)
|
||||||
|
|
||||||
return redirect(current_pathway.url)
|
return redirect(current_pathway.url)
|
||||||
|
|
||||||
|
|||||||
@ -81,6 +81,7 @@ def migration(request):
|
|||||||
"bt0416-4269",
|
"bt0416-4269",
|
||||||
"bt0432-4254",
|
"bt0432-4254",
|
||||||
"bt0337-4117",
|
"bt0337-4117",
|
||||||
|
"bt0322-3393",
|
||||||
]
|
]
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
|
|||||||
@ -34,3 +34,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@import "./daisyui-theme.css";
|
@import "./daisyui-theme.css";
|
||||||
|
|
||||||
|
select.select[multiple] {
|
||||||
|
display: block;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,37 @@
|
|||||||
console.log("loaded pw.js")
|
console.log("loaded pw.js")
|
||||||
|
|
||||||
|
function findPaths(source_idx, links) {
|
||||||
|
const resultLinks = new Set();
|
||||||
|
const visited = new Set();
|
||||||
|
// Helper function for depth-first search
|
||||||
|
function dfs(current) {
|
||||||
|
|
||||||
|
if (visited.has(current)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (links.filter(link => link.target.id === current).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visited.add(current);
|
||||||
|
links.forEach(link => {
|
||||||
|
if (
|
||||||
|
link.target.id === current &&
|
||||||
|
!visited.has(link.source.id) &&
|
||||||
|
link.source.id !== link.target.id // Avoid self-loops
|
||||||
|
) {
|
||||||
|
resultLinks.add(link); // Add link to result
|
||||||
|
dfs(link.source.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
visited.delete(current);
|
||||||
|
}
|
||||||
|
// Start DFS
|
||||||
|
dfs(source_idx);
|
||||||
|
return Array.from(resultLinks);
|
||||||
|
}
|
||||||
|
|
||||||
function predictFromNode(url) {
|
function predictFromNode(url) {
|
||||||
fetch("", {
|
fetch("", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -627,6 +659,34 @@ function draw(pathway, elem) {
|
|||||||
return d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)';
|
return d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)';
|
||||||
})
|
})
|
||||||
.attr("fill", "none")
|
.attr("fill", "none")
|
||||||
|
.on("click", function(event, d) {
|
||||||
|
const wasHighlighted = d3.select(this).classed("highlighted");
|
||||||
|
d3.selectAll("path").classed("highlighted", false);
|
||||||
|
if (!wasHighlighted) {
|
||||||
|
const toHighlight = [];
|
||||||
|
toHighlight.push(d.el);
|
||||||
|
|
||||||
|
if (d.source.pseudo || d.target.pseudo) {
|
||||||
|
if (d.target.pseudo) {
|
||||||
|
d3.selectAll("path").each(e => {
|
||||||
|
if (e !== undefined && e.source.id === d.target.id) {
|
||||||
|
toHighlight.push(e.el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
d3.selectAll("path").each(e => {
|
||||||
|
if (e !== undefined && (e.target.id === d.source.id || e.source.id === d.source.id)) {
|
||||||
|
toHighlight.push(e.el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of toHighlight) {
|
||||||
|
d3.select(e).classed("highlighted", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// add element to links array
|
// add element to links array
|
||||||
link.each(function (d) {
|
link.each(function (d) {
|
||||||
@ -643,10 +703,41 @@ function draw(pathway, elem) {
|
|||||||
.call(d3.drag()
|
.call(d3.drag()
|
||||||
.on("start", dragstarted)
|
.on("start", dragstarted)
|
||||||
.on("drag", dragged)
|
.on("drag", dragged)
|
||||||
.on("end", dragended))
|
.on("end", dragended)
|
||||||
|
)
|
||||||
.on("click", function (event, d) {
|
.on("click", function (event, d) {
|
||||||
|
const wasHighlighted = d3.select(this).select("circle").classed("highlighted");
|
||||||
|
d3.selectAll('circle.highlighted').classed('highlighted', false);
|
||||||
|
d3.selectAll("path").classed("inedge", false);
|
||||||
|
d3.selectAll("path").classed("outedge", false);
|
||||||
|
|
||||||
|
if (!wasHighlighted) {
|
||||||
d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
|
d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
|
||||||
})
|
const inEdges = findPaths(d.id, links);
|
||||||
|
const outEdges = []
|
||||||
|
// Colorize out edges green
|
||||||
|
for (const l of links) {
|
||||||
|
if (l.source.id === d.id) {
|
||||||
|
outEdges.push(l);
|
||||||
|
if (l.target.pseudo) {
|
||||||
|
for (const l2 of links) {
|
||||||
|
if (l.target.id === l2.source.id) {
|
||||||
|
outEdges.push(l2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of inEdges) {
|
||||||
|
d3.select(e.el).classed("inedge", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const e of outEdges) {
|
||||||
|
d3.select(e.el).classed("outedge", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Kreise für die Knoten hinzufügen
|
// Kreise für die Knoten hinzufügen
|
||||||
node.append("circle")
|
node.append("circle")
|
||||||
@ -720,14 +811,14 @@ function draw(pathway, elem) {
|
|||||||
|
|
||||||
function serializeSVG(svgElement) {
|
function serializeSVG(svgElement) {
|
||||||
|
|
||||||
svgElement.querySelectorAll("line.link").forEach(line => {
|
svgElement.querySelectorAll("path.link").forEach(line => {
|
||||||
const style = getComputedStyle(line);
|
const style = getComputedStyle(line);
|
||||||
line.setAttribute("stroke", style.stroke);
|
line.setAttribute("stroke", style.stroke);
|
||||||
line.setAttribute("stroke-width", style.strokeWidth);
|
line.setAttribute("stroke-width", style.strokeWidth);
|
||||||
line.setAttribute("fill", style.fill);
|
line.setAttribute("fill", style.fill);
|
||||||
});
|
});
|
||||||
|
|
||||||
svgElement.querySelectorAll("line.link_no_arrow").forEach(line => {
|
svgElement.querySelectorAll("path.link_no_arrow").forEach(line => {
|
||||||
const style = getComputedStyle(line);
|
const style = getComputedStyle(line);
|
||||||
line.setAttribute("stroke", style.stroke);
|
line.setAttribute("stroke", style.stroke);
|
||||||
line.setAttribute("stroke-width", style.strokeWidth);
|
line.setAttribute("stroke-width", style.strokeWidth);
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
{% for tpl in action_button_templates %}
|
{% for tpl in action_button_templates %}
|
||||||
{% include tpl %}
|
{% include tpl %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider h-px"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@ -74,7 +74,7 @@
|
|||||||
Rules</a
|
Rules</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider h-px"></li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
@ -99,11 +99,16 @@
|
|||||||
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
<i class="glyphicon glyphicon-plus"></i> Set Aliases</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider h-px"></li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
onclick="document.getElementById('delete_pathway_node_modal').showModal(); return false;"
|
onclick="
|
||||||
|
const modal = document.getElementById('delete_pathway_node_modal');
|
||||||
|
modal.showModal();
|
||||||
|
window.dispatchEvent(new Event('modal-opened'));
|
||||||
|
return false;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Compound</a
|
||||||
>
|
>
|
||||||
@ -111,7 +116,12 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
onclick="document.getElementById('delete_pathway_edge_modal').showModal(); return false;"
|
onclick="
|
||||||
|
const modal = document.getElementById('delete_pathway_edge_modal');
|
||||||
|
modal.showModal();
|
||||||
|
window.dispatchEvent(new Event('modal-opened'));
|
||||||
|
return false;
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
<i class="glyphicon glyphicon-trash"></i> Delete Reaction</a
|
||||||
>
|
>
|
||||||
|
|||||||
@ -4,6 +4,22 @@
|
|||||||
id="delete_pathway_edge_modal"
|
id="delete_pathway_edge_modal"
|
||||||
class="modal"
|
class="modal"
|
||||||
x-data="modalForm({ state: { selectedEdge: '', imageUrl: '' } })"
|
x-data="modalForm({ state: { selectedEdge: '', imageUrl: '' } })"
|
||||||
|
@modal-opened.window="
|
||||||
|
const links = d3.selectAll('path.highlighted');
|
||||||
|
if (!links.empty()) {
|
||||||
|
const el = links.node();
|
||||||
|
const selectElement = document.getElementById('delete_pathway_edge_edges');
|
||||||
|
console.log(el);
|
||||||
|
console.log(el.__data__);
|
||||||
|
for (let option of selectElement.options) {
|
||||||
|
if (option.value === el.__data__.url) {
|
||||||
|
option.selected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectElement.dispatchEvent(new Event('change'));
|
||||||
|
}
|
||||||
|
"
|
||||||
@close="reset()"
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
|
|||||||
@ -4,6 +4,19 @@
|
|||||||
id="delete_pathway_node_modal"
|
id="delete_pathway_node_modal"
|
||||||
class="modal"
|
class="modal"
|
||||||
x-data="modalForm({ state: { selectedNode: '', imageUrl: '' } })"
|
x-data="modalForm({ state: { selectedNode: '', imageUrl: '' } })"
|
||||||
|
@modal-opened.window="
|
||||||
|
const el = d3.select('circle.highlighted').node();
|
||||||
|
if (el !== null) {
|
||||||
|
const selectElement = document.getElementById('delete_pathway_node_nodes');
|
||||||
|
for (let option of selectElement.options) {
|
||||||
|
if (option.value === el.__data__.url) {
|
||||||
|
option.selected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectElement.dispatchEvent(new Event('change'));
|
||||||
|
}
|
||||||
|
"
|
||||||
@close="reset()"
|
@close="reset()"
|
||||||
>
|
>
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
|
|||||||
@ -92,7 +92,7 @@
|
|||||||
<div class="flex flex-wrap items-center justify-center gap-4">
|
<div class="flex flex-wrap items-center justify-center gap-4">
|
||||||
{% for educt in edge.start_nodes.all %}
|
{% for educt in edge.start_nodes.all %}
|
||||||
<a href="{{ educt.url }}" class="btn btn-outline btn-sm"
|
<a href="{{ educt.url }}" class="btn btn-outline btn-sm"
|
||||||
>{{ educt.name }}</a
|
>{{ educt.get_name }}</a
|
||||||
>
|
>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<svg
|
<svg
|
||||||
@ -112,7 +112,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
{% for product in edge.end_nodes.all %}
|
{% for product in edge.end_nodes.all %}
|
||||||
<a href="{{ product.url }}" class="btn btn-outline btn-sm"
|
<a href="{{ product.url }}" class="btn btn-outline btn-sm"
|
||||||
>{{ product.name }}</a
|
>{{ product.get_name }}</a
|
||||||
>
|
>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -56,7 +56,9 @@
|
|||||||
<ul class="menu bg-base-200 rounded-box">
|
<ul class="menu bg-base-200 rounded-box">
|
||||||
{% for um in group.user_member.all %}
|
{% for um in group.user_member.all %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ um.url }}" class="hover:bg-base-300"
|
<a
|
||||||
|
href="{% if user.is_superuser %}{{ um.url }}{% else %}{{ "#" }}{% endif %}"
|
||||||
|
class="hover:bg-base-300"
|
||||||
>{{ um.username }}
|
>{{ um.username }}
|
||||||
{% if not um.is_active %}<i>(inactive)</i>{% endif %}</a
|
{% if not um.is_active %}<i>(inactive)</i>{% endif %}</a
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{% extends "framework_modern.html" %}
|
{% extends "framework_modern.html" %}
|
||||||
|
{% load envipytags %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
@ -54,6 +55,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% epdb_slot_templates "epdb.objects.node.viz" as viz_templates %}
|
||||||
|
|
||||||
|
{% for tpl in viz_templates %}
|
||||||
|
{% include tpl %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
<!-- Image Representation -->
|
<!-- Image Representation -->
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
<input type="checkbox" checked />
|
<input type="checkbox" checked />
|
||||||
|
|||||||
@ -72,6 +72,15 @@
|
|||||||
stroke: red;
|
stroke: red;
|
||||||
stroke-width: 3px;
|
stroke-width: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inedge {
|
||||||
|
stroke: red;
|
||||||
|
stroke-width: 3px;
|
||||||
|
}
|
||||||
|
.outedge {
|
||||||
|
stroke: green;
|
||||||
|
stroke-width: 3px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="{% static 'js/pw.js' %}"></script>
|
<script src="{% static 'js/pw.js' %}"></script>
|
||||||
|
|
||||||
@ -106,7 +115,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Graphical Representation -->
|
<!-- Graphical Representation -->
|
||||||
<div class="collapse-arrow bg-base-200 collapse">
|
<div class="collapse-arrow bg-base-200 collapse overflow-y-auto">
|
||||||
<input type="checkbox" checked />
|
<input type="checkbox" checked />
|
||||||
<div class="collapse-title text-xl font-medium">
|
<div class="collapse-title text-xl font-medium">
|
||||||
Graphical Representation
|
Graphical Representation
|
||||||
@ -140,7 +149,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-96 p-2"
|
||||||
>
|
>
|
||||||
{% include "actions/objects/pathway.html" %}
|
{% include "actions/objects/pathway.html" %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -212,7 +212,13 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const button = this;
|
const button = this;
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
button.textContent = "Predicting...";
|
|
||||||
|
// Set text depending on mode
|
||||||
|
if (document.getElementById("predict-submit-button").innerText === "Build") {
|
||||||
|
button.textContent = "Building...";
|
||||||
|
} else {
|
||||||
|
button.textContent = "Predicting...";
|
||||||
|
}
|
||||||
|
|
||||||
// Get SMILES from either input or Ketcher
|
// Get SMILES from either input or Ketcher
|
||||||
const smilesInput = document.getElementById("predict-smiles");
|
const smilesInput = document.getElementById("predict-smiles");
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
|
from epdb.exceptions import InvalidSMILESException
|
||||||
from epdb.logic import PackageManager
|
from epdb.logic import PackageManager
|
||||||
from epdb.models import Compound, User, CompoundStructure
|
from epdb.models import Compound, User, CompoundStructure
|
||||||
|
|
||||||
@ -33,13 +35,13 @@ class CompoundTest(TestCase):
|
|||||||
self.assertEqual(c.description, "No Desc")
|
self.assertEqual(c.description, "No Desc")
|
||||||
|
|
||||||
def test_missing_smiles(self):
|
def test_missing_smiles(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Compound.create(self.package, smiles=None, name="Afoxolaner", description="No Desc")
|
_ = Compound.create(self.package, smiles=None, name="Afoxolaner", description="No Desc")
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Compound.create(self.package, smiles="", name="Afoxolaner", description="No Desc")
|
_ = Compound.create(self.package, smiles="", name="Afoxolaner", description="No Desc")
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Compound.create(self.package, smiles=" ", name="Afoxolaner", description="No Desc")
|
_ = Compound.create(self.package, smiles=" ", name="Afoxolaner", description="No Desc")
|
||||||
|
|
||||||
def test_smiles_are_trimmed(self):
|
def test_smiles_are_trimmed(self):
|
||||||
@ -96,7 +98,7 @@ class CompoundTest(TestCase):
|
|||||||
self.assertEqual(len(self.package.compounds), 1)
|
self.assertEqual(len(self.package.compounds), 1)
|
||||||
|
|
||||||
def test_wrong_smiles(self):
|
def test_wrong_smiles(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Compound.create(
|
_ = Compound.create(
|
||||||
self.package,
|
self.package,
|
||||||
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
smiles="C1C(=NOC1(C2=CC(=CC(=C2)Cl)C(F)(F)F)C(F)(F)F)C3=CC=C(C=CC=CC=C43)C(=O)NCC(=O)NCC(F)(F)F",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
||||||
|
from epdb.exceptions import InvalidSMILESException
|
||||||
from epdb.logic import PackageManager
|
from epdb.logic import PackageManager
|
||||||
from epdb.models import Compound, User, Reaction, Rule
|
from epdb.models import Compound, User, Reaction, Rule
|
||||||
|
|
||||||
@ -163,7 +164,7 @@ class ReactionTest(TestCase):
|
|||||||
self.assertEqual(len(self.package.reactions), 1)
|
self.assertEqual(len(self.package.reactions), 1)
|
||||||
|
|
||||||
def test_wrong_smiles(self):
|
def test_wrong_smiles(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(InvalidSMILESException):
|
||||||
_ = Reaction.create(
|
_ = Reaction.create(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
name="Eawag BBD reaction r0001",
|
name="Eawag BBD reaction r0001",
|
||||||
|
|||||||
@ -46,6 +46,14 @@ class ModelViewTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
|
{
|
||||||
|
"products": [["CCN(CC)C(=O)C1=CC(C=O)=CC=C1"]],
|
||||||
|
"probability": 0.75,
|
||||||
|
"btrule": {
|
||||||
|
"url": "http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/2f2e0c39-e109-4836-959f-2bda2524f022",
|
||||||
|
"name": "bt0001-3568",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"products": [["O=C(O)C1=CC(CO)=CC=C1", "CCNCC"]],
|
"products": [["O=C(O)C1=CC(CO)=CC=C1", "CCNCC"]],
|
||||||
"probability": 0.25,
|
"probability": 0.25,
|
||||||
@ -62,14 +70,6 @@ class ModelViewTest(TestCase):
|
|||||||
"name": "bt0243-4301",
|
"name": "bt0243-4301",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"products": [["CCN(CC)C(=O)C1=CC(C=O)=CC=C1"]],
|
|
||||||
"probability": 0.75,
|
|
||||||
"btrule": {
|
|
||||||
"url": "http://localhost:8000/package/1869d3f0-60bb-41fd-b6f8-afa75ffb09d3/simple-ambit-rule/2f2e0c39-e109-4836-959f-2bda2524f022",
|
|
||||||
"name": "bt0001-3568",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
actual = response.json()["pred"]
|
actual = response.json()["pred"]
|
||||||
|
|||||||
@ -6,12 +6,15 @@ import logging
|
|||||||
import uuid
|
import uuid
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, List, TYPE_CHECKING
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from ninja import Schema
|
||||||
|
from pydantic import HttpUrl
|
||||||
|
|
||||||
from epdb.models import (
|
from epdb.models import (
|
||||||
|
AdditionalInformation,
|
||||||
Compound,
|
Compound,
|
||||||
CompoundStructure,
|
CompoundStructure,
|
||||||
Edge,
|
Edge,
|
||||||
@ -28,7 +31,6 @@ from epdb.models import (
|
|||||||
Rule,
|
Rule,
|
||||||
RuleBasedRelativeReasoning,
|
RuleBasedRelativeReasoning,
|
||||||
Scenario,
|
Scenario,
|
||||||
SequentialRule,
|
|
||||||
Setting,
|
Setting,
|
||||||
SimpleAmbitRule,
|
SimpleAmbitRule,
|
||||||
SimpleRDKitRule,
|
SimpleRDKitRule,
|
||||||
@ -43,408 +45,235 @@ if TYPE_CHECKING:
|
|||||||
from epdb.logic import SPathway
|
from epdb.logic import SPathway
|
||||||
|
|
||||||
|
|
||||||
class PackageExporter:
|
class LicenseExportSchema(Schema):
|
||||||
def __init__(
|
cc_string: str
|
||||||
self,
|
link: HttpUrl
|
||||||
package: Package,
|
image_link: HttpUrl
|
||||||
include_models: bool = False,
|
|
||||||
include_external_identifiers: bool = True,
|
|
||||||
):
|
|
||||||
self._raw_package = package
|
|
||||||
self.include_modes = include_models
|
|
||||||
self.include_external_identifiers = include_external_identifiers
|
|
||||||
|
|
||||||
def do_export(self):
|
|
||||||
return PackageExporter._export_package_as_json(
|
##############
|
||||||
self._raw_package, self.include_modes, self.include_external_identifiers
|
# RefSchemas #
|
||||||
)
|
##############
|
||||||
|
class RefExportSchema(Schema):
|
||||||
|
uuid: str
|
||||||
|
url: str
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _export_package_as_json(
|
def resolve_uuid(obj):
|
||||||
package: Package, include_models: bool = False, include_external_identifiers: bool = True
|
value = obj.get("uuid") if isinstance(obj, dict) else obj.uuid
|
||||||
) -> Dict[str, Any]:
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
class RefCompoundExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefCompoundStructureExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefReactionExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefRuleExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefNodeExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefEdgeExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefPathwayExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
class RefScenarioExportSchema(RefExportSchema): ...
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# Compound #
|
||||||
|
############
|
||||||
|
class CompoundExportSchema(RefCompoundExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
default_structure: RefCompoundStructureExportSchema
|
||||||
|
structures: List["CompoundStructureExportSchema"]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class CompoundStructureExportSchema(RefCompoundStructureExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
smiles: str
|
||||||
|
molfile: Optional[str]
|
||||||
|
normalized_structure: bool
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# Reaction #
|
||||||
|
############
|
||||||
|
class ReactionExportSchema(RefReactionExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
educts: List[RefCompoundStructureExportSchema]
|
||||||
|
products: List[RefCompoundStructureExportSchema]
|
||||||
|
rules: List[RefRuleExportSchema]
|
||||||
|
multi_step: bool
|
||||||
|
medline_references: List[str] | None
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
#########
|
||||||
|
# Rules #
|
||||||
|
#########
|
||||||
|
class RuleExportSchema(RefRuleExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
smirks: str
|
||||||
|
reactant_filter_smarts: Optional[str]
|
||||||
|
product_filter_smarts: Optional[str]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class ParallelRuleExportSchema(RefRuleExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
simple_rules: List[RefRuleExportSchema]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# Pathway / Nodes / Edges #
|
||||||
|
###########################
|
||||||
|
class NodeExportSchema(RefNodeExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
default_node_label: RefCompoundStructureExportSchema
|
||||||
|
node_labels: List[RefCompoundStructureExportSchema]
|
||||||
|
depth: int
|
||||||
|
stereo_removed: bool
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class EdgeExportSchema(RefEdgeExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
edge_label: RefReactionExportSchema
|
||||||
|
start_nodes: List[RefNodeExportSchema]
|
||||||
|
end_nodes: List[RefNodeExportSchema]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class PathwayExportSchema(RefPathwayExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
aliases: List[str]
|
||||||
|
predicted: bool
|
||||||
|
nodes: List[NodeExportSchema]
|
||||||
|
edges: List[EdgeExportSchema]
|
||||||
|
scenarios: List[RefScenarioExportSchema]
|
||||||
|
|
||||||
|
|
||||||
|
class ScenarioExportSchema(RefScenarioExportSchema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
scenario_date: str
|
||||||
|
scenario_type: str
|
||||||
|
|
||||||
|
|
||||||
|
class AdditionalInformationExportSchema(RefExportSchema):
|
||||||
|
type: str
|
||||||
|
data: dict
|
||||||
|
scenario: RefScenarioExportSchema | None = None
|
||||||
|
attach_object: RefExportSchema | None = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_attach_object(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
if obj.get("attach_object") is None:
|
||||||
|
return None
|
||||||
|
return RefExportSchema.model_validate(obj["attach_object"])
|
||||||
|
return obj.content_object
|
||||||
|
|
||||||
|
|
||||||
|
###########
|
||||||
|
# Package #
|
||||||
|
###########
|
||||||
|
class PackageExportSchema(Schema):
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
uuid: str
|
||||||
|
reviewed: bool
|
||||||
|
license: LicenseExportSchema | None
|
||||||
|
compounds: List[CompoundExportSchema]
|
||||||
|
reactions: List[ReactionExportSchema]
|
||||||
|
simple_rules: List[RuleExportSchema]
|
||||||
|
composite_rules: List[ParallelRuleExportSchema]
|
||||||
|
pathways: List[PathwayExportSchema]
|
||||||
|
scenarios: List[ScenarioExportSchema]
|
||||||
|
additional_information: List[AdditionalInformationExportSchema]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_uuid(obj):
|
||||||
|
value = obj.get("uuid") if isinstance(obj, dict) else obj.uuid
|
||||||
|
return str(value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_simple_rules(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
result = []
|
||||||
|
for r in obj.get("simple_rules", []):
|
||||||
|
result.append(RuleExportSchema.model_validate(r))
|
||||||
|
return result
|
||||||
|
return SimpleAmbitRule.objects.filter(package=obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_composite_rules(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
result = []
|
||||||
|
for r in obj.get("composite_rules", []):
|
||||||
|
result.append(ParallelRuleExportSchema.model_validate(r))
|
||||||
|
return result
|
||||||
|
return ParallelRule.objects.filter(package=obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_additional_information(obj):
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
result = []
|
||||||
|
for ai in obj.get("additional_information", []):
|
||||||
|
result.append(AdditionalInformationExportSchema.model_validate(ai))
|
||||||
|
return result
|
||||||
|
return AdditionalInformation.objects.filter(package=obj)
|
||||||
|
|
||||||
|
|
||||||
|
class PackageExporter:
|
||||||
|
def __init__(self, package: Package):
|
||||||
|
self._raw_package = package
|
||||||
|
|
||||||
|
def do_export(self):
|
||||||
|
return PackageExporter._export_package_as_json(self._raw_package)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _export_package_as_json(package: Package) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Dumps a Package and all its related objects as JSON.
|
Dumps a Package and all its related objects as JSON.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
package: The Package instance to dump
|
package: The Package instance to dump
|
||||||
include_models: Whether to include EPModel objects
|
|
||||||
include_external_identifiers: Whether to include external identifiers
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict containing the complete package data as JSON-serializable structure
|
Dict containing the complete package data as JSON-serializable structure
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def serialize_base_object(
|
data = PackageExportSchema.from_orm(package)
|
||||||
obj, include_aliases: bool = True, include_scenarios: bool = True
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""Serialize common EnviPathModel fields"""
|
|
||||||
base_dict = {
|
|
||||||
"uuid": str(obj.uuid),
|
|
||||||
"name": obj.name,
|
|
||||||
"description": obj.description,
|
|
||||||
"url": obj.url,
|
|
||||||
"kv": obj.kv,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add aliases if the object has them
|
return data.model_dump(mode="json")
|
||||||
if include_aliases and hasattr(obj, "aliases"):
|
|
||||||
base_dict["aliases"] = obj.aliases
|
|
||||||
|
|
||||||
# Add scenarios if the object has them
|
|
||||||
if include_scenarios and hasattr(obj, "scenarios"):
|
|
||||||
base_dict["scenarios"] = [
|
|
||||||
{"uuid": str(s.uuid), "url": s.url} for s in obj.scenarios.all()
|
|
||||||
]
|
|
||||||
|
|
||||||
return base_dict
|
|
||||||
|
|
||||||
def serialize_external_identifiers(obj) -> List[Dict[str, Any]]:
|
|
||||||
"""Serialize external identifiers for an object"""
|
|
||||||
if not include_external_identifiers or not hasattr(obj, "external_identifiers"):
|
|
||||||
return []
|
|
||||||
|
|
||||||
identifiers = []
|
|
||||||
for ext_id in obj.external_identifiers.all():
|
|
||||||
identifier_dict = {
|
|
||||||
"uuid": str(ext_id.uuid),
|
|
||||||
"database": {
|
|
||||||
"uuid": str(ext_id.database.uuid),
|
|
||||||
"name": ext_id.database.name,
|
|
||||||
"base_url": ext_id.database.base_url,
|
|
||||||
},
|
|
||||||
"identifier_value": ext_id.identifier_value,
|
|
||||||
"url": ext_id.url,
|
|
||||||
"is_primary": ext_id.is_primary,
|
|
||||||
}
|
|
||||||
identifiers.append(identifier_dict)
|
|
||||||
return identifiers
|
|
||||||
|
|
||||||
# Start with the package itself
|
|
||||||
result = serialize_base_object(package, include_aliases=True, include_scenarios=True)
|
|
||||||
result["reviewed"] = package.reviewed
|
|
||||||
|
|
||||||
# # Add license information
|
|
||||||
# if package.license:
|
|
||||||
# result['license'] = {
|
|
||||||
# 'uuid': str(package.license.uuid),
|
|
||||||
# 'name': package.license.name,
|
|
||||||
# 'link': package.license.link,
|
|
||||||
# 'image_link': package.license.image_link
|
|
||||||
# }
|
|
||||||
# else:
|
|
||||||
# result['license'] = None
|
|
||||||
|
|
||||||
# Initialize collections
|
|
||||||
result.update(
|
|
||||||
{
|
|
||||||
"compounds": [],
|
|
||||||
"structures": [],
|
|
||||||
"rules": {"simple_rules": [], "parallel_rules": [], "sequential_rules": []},
|
|
||||||
"reactions": [],
|
|
||||||
"pathways": [],
|
|
||||||
"nodes": [],
|
|
||||||
"edges": [],
|
|
||||||
"scenarios": [],
|
|
||||||
"models": [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
print(f"Exporting package: {package.name}")
|
|
||||||
|
|
||||||
# Export compounds
|
|
||||||
print("Exporting compounds...")
|
|
||||||
for compound in package.compounds.prefetch_related("default_structure").order_by("url"):
|
|
||||||
compound_dict = serialize_base_object(
|
|
||||||
compound, include_aliases=True, include_scenarios=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if compound.default_structure:
|
|
||||||
compound_dict["default_structure"] = {
|
|
||||||
"uuid": str(compound.default_structure.uuid),
|
|
||||||
"url": compound.default_structure.url,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
compound_dict["default_structure"] = None
|
|
||||||
|
|
||||||
compound_dict["external_identifiers"] = serialize_external_identifiers(compound)
|
|
||||||
result["compounds"].append(compound_dict)
|
|
||||||
|
|
||||||
# Export compound structures
|
|
||||||
print("Exporting compound structures...")
|
|
||||||
compound_structures = (
|
|
||||||
CompoundStructure.objects.filter(compound__package=package)
|
|
||||||
.select_related("compound")
|
|
||||||
.order_by("url")
|
|
||||||
)
|
|
||||||
|
|
||||||
for structure in compound_structures:
|
|
||||||
structure_dict = serialize_base_object(
|
|
||||||
structure, include_aliases=True, include_scenarios=True
|
|
||||||
)
|
|
||||||
structure_dict.update(
|
|
||||||
{
|
|
||||||
"compound": {
|
|
||||||
"uuid": str(structure.compound.uuid),
|
|
||||||
"url": structure.compound.url,
|
|
||||||
},
|
|
||||||
"smiles": structure.smiles,
|
|
||||||
"canonical_smiles": structure.canonical_smiles,
|
|
||||||
"inchikey": structure.inchikey,
|
|
||||||
"normalized_structure": structure.normalized_structure,
|
|
||||||
"external_identifiers": serialize_external_identifiers(structure),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["structures"].append(structure_dict)
|
|
||||||
|
|
||||||
# Export rules
|
|
||||||
print("Exporting rules...")
|
|
||||||
|
|
||||||
# Simple rules (including SimpleAmbitRule and SimpleRDKitRule)
|
|
||||||
for rule in SimpleRule.objects.filter(package=package).order_by("url"):
|
|
||||||
rule_dict = serialize_base_object(rule, include_aliases=True, include_scenarios=True)
|
|
||||||
|
|
||||||
# Add specific fields for SimpleAmbitRule
|
|
||||||
if isinstance(rule, SimpleAmbitRule):
|
|
||||||
rule_dict.update(
|
|
||||||
{
|
|
||||||
"rule_type": "SimpleAmbitRule",
|
|
||||||
"smirks": rule.smirks,
|
|
||||||
"reactant_filter_smarts": rule.reactant_filter_smarts or "",
|
|
||||||
"product_filter_smarts": rule.product_filter_smarts or "",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif isinstance(rule, SimpleRDKitRule):
|
|
||||||
rule_dict.update(
|
|
||||||
{"rule_type": "SimpleRDKitRule", "reaction_smarts": rule.reaction_smarts}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
rule_dict["rule_type"] = "SimpleRule"
|
|
||||||
|
|
||||||
result["rules"]["simple_rules"].append(rule_dict)
|
|
||||||
|
|
||||||
# Parallel rules
|
|
||||||
for rule in (
|
|
||||||
ParallelRule.objects.filter(package=package)
|
|
||||||
.prefetch_related("simple_rules")
|
|
||||||
.order_by("url")
|
|
||||||
):
|
|
||||||
rule_dict = serialize_base_object(rule, include_aliases=True, include_scenarios=True)
|
|
||||||
rule_dict["rule_type"] = "ParallelRule"
|
|
||||||
rule_dict["simple_rules"] = [
|
|
||||||
{"uuid": str(sr.uuid), "url": sr.url} for sr in rule.simple_rules.all()
|
|
||||||
]
|
|
||||||
result["rules"]["parallel_rules"].append(rule_dict)
|
|
||||||
|
|
||||||
# Sequential rules
|
|
||||||
for rule in (
|
|
||||||
SequentialRule.objects.filter(package=package)
|
|
||||||
.prefetch_related("simple_rules")
|
|
||||||
.order_by("url")
|
|
||||||
):
|
|
||||||
rule_dict = serialize_base_object(rule, include_aliases=True, include_scenarios=True)
|
|
||||||
rule_dict["rule_type"] = "SequentialRule"
|
|
||||||
rule_dict["simple_rules"] = [
|
|
||||||
{
|
|
||||||
"uuid": str(sr.uuid),
|
|
||||||
"url": sr.url,
|
|
||||||
"order_index": sr.sequentialruleordering_set.get(
|
|
||||||
sequential_rule=rule
|
|
||||||
).order_index,
|
|
||||||
}
|
|
||||||
for sr in rule.simple_rules.all()
|
|
||||||
]
|
|
||||||
result["rules"]["sequential_rules"].append(rule_dict)
|
|
||||||
|
|
||||||
# Export reactions
|
|
||||||
print("Exporting reactions...")
|
|
||||||
for reaction in package.reactions.prefetch_related("educts", "products", "rules").order_by(
|
|
||||||
"url"
|
|
||||||
):
|
|
||||||
reaction_dict = serialize_base_object(
|
|
||||||
reaction, include_aliases=True, include_scenarios=True
|
|
||||||
)
|
|
||||||
reaction_dict.update(
|
|
||||||
{
|
|
||||||
"educts": [{"uuid": str(e.uuid), "url": e.url} for e in reaction.educts.all()],
|
|
||||||
"products": [
|
|
||||||
{"uuid": str(p.uuid), "url": p.url} for p in reaction.products.all()
|
|
||||||
],
|
|
||||||
"rules": [{"uuid": str(r.uuid), "url": r.url} for r in reaction.rules.all()],
|
|
||||||
"multi_step": reaction.multi_step,
|
|
||||||
"medline_references": reaction.medline_references,
|
|
||||||
"external_identifiers": serialize_external_identifiers(reaction),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["reactions"].append(reaction_dict)
|
|
||||||
|
|
||||||
# Export pathways
|
|
||||||
print("Exporting pathways...")
|
|
||||||
for pathway in package.pathways.order_by("url"):
|
|
||||||
pathway_dict = serialize_base_object(
|
|
||||||
pathway, include_aliases=True, include_scenarios=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add setting reference if exists
|
|
||||||
if hasattr(pathway, "setting") and pathway.setting:
|
|
||||||
pathway_dict["setting"] = {
|
|
||||||
"uuid": str(pathway.setting.uuid),
|
|
||||||
"url": pathway.setting.url,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
pathway_dict["setting"] = None
|
|
||||||
|
|
||||||
result["pathways"].append(pathway_dict)
|
|
||||||
|
|
||||||
# Export nodes
|
|
||||||
print("Exporting nodes...")
|
|
||||||
pathway_nodes = (
|
|
||||||
Node.objects.filter(pathway__package=package)
|
|
||||||
.select_related("pathway", "default_node_label")
|
|
||||||
.prefetch_related("node_labels", "out_edges")
|
|
||||||
.order_by("url")
|
|
||||||
)
|
|
||||||
|
|
||||||
for node in pathway_nodes:
|
|
||||||
node_dict = serialize_base_object(node, include_aliases=True, include_scenarios=True)
|
|
||||||
node_dict.update(
|
|
||||||
{
|
|
||||||
"pathway": {"uuid": str(node.pathway.uuid), "url": node.pathway.url},
|
|
||||||
"default_node_label": {
|
|
||||||
"uuid": str(node.default_node_label.uuid),
|
|
||||||
"url": node.default_node_label.url,
|
|
||||||
},
|
|
||||||
"node_labels": [
|
|
||||||
{"uuid": str(label.uuid), "url": label.url}
|
|
||||||
for label in node.node_labels.all()
|
|
||||||
],
|
|
||||||
"out_edges": [
|
|
||||||
{"uuid": str(edge.uuid), "url": edge.url} for edge in node.out_edges.all()
|
|
||||||
],
|
|
||||||
"depth": node.depth,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["nodes"].append(node_dict)
|
|
||||||
|
|
||||||
# Export edges
|
|
||||||
print("Exporting edges...")
|
|
||||||
pathway_edges = (
|
|
||||||
Edge.objects.filter(pathway__package=package)
|
|
||||||
.select_related("pathway", "edge_label")
|
|
||||||
.prefetch_related("start_nodes", "end_nodes")
|
|
||||||
.order_by("url")
|
|
||||||
)
|
|
||||||
|
|
||||||
for edge in pathway_edges:
|
|
||||||
edge_dict = serialize_base_object(edge, include_aliases=True, include_scenarios=True)
|
|
||||||
edge_dict.update(
|
|
||||||
{
|
|
||||||
"pathway": {"uuid": str(edge.pathway.uuid), "url": edge.pathway.url},
|
|
||||||
"edge_label": {"uuid": str(edge.edge_label.uuid), "url": edge.edge_label.url},
|
|
||||||
"start_nodes": [
|
|
||||||
{"uuid": str(node.uuid), "url": node.url} for node in edge.start_nodes.all()
|
|
||||||
],
|
|
||||||
"end_nodes": [
|
|
||||||
{"uuid": str(node.uuid), "url": node.url} for node in edge.end_nodes.all()
|
|
||||||
],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["edges"].append(edge_dict)
|
|
||||||
|
|
||||||
# Export scenarios
|
|
||||||
print("Exporting scenarios...")
|
|
||||||
for scenario in package.scenarios.order_by("url"):
|
|
||||||
scenario_dict = serialize_base_object(
|
|
||||||
scenario, include_aliases=False, include_scenarios=False
|
|
||||||
)
|
|
||||||
scenario_dict.update(
|
|
||||||
{
|
|
||||||
"scenario_date": scenario.scenario_date,
|
|
||||||
"scenario_type": scenario.scenario_type,
|
|
||||||
"parent": {"uuid": str(scenario.parent.uuid), "url": scenario.parent.url}
|
|
||||||
if scenario.parent
|
|
||||||
else None,
|
|
||||||
"additional_information": scenario.additional_information,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
result["scenarios"].append(scenario_dict)
|
|
||||||
|
|
||||||
# Export models
|
|
||||||
if include_models:
|
|
||||||
print("Exporting models...")
|
|
||||||
package_models = (
|
|
||||||
package.models.select_related("app_domain")
|
|
||||||
.prefetch_related("rule_packages", "data_packages", "eval_packages")
|
|
||||||
.order_by("url")
|
|
||||||
)
|
|
||||||
|
|
||||||
for model in package_models:
|
|
||||||
model_dict = serialize_base_object(
|
|
||||||
model, include_aliases=True, include_scenarios=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# Common fields for PackageBasedModel
|
|
||||||
if hasattr(model, "rule_packages"):
|
|
||||||
model_dict.update(
|
|
||||||
{
|
|
||||||
"rule_packages": [
|
|
||||||
{"uuid": str(p.uuid), "url": p.url}
|
|
||||||
for p in model.rule_packages.all()
|
|
||||||
],
|
|
||||||
"data_packages": [
|
|
||||||
{"uuid": str(p.uuid), "url": p.url}
|
|
||||||
for p in model.data_packages.all()
|
|
||||||
],
|
|
||||||
"eval_packages": [
|
|
||||||
{"uuid": str(p.uuid), "url": p.url}
|
|
||||||
for p in model.eval_packages.all()
|
|
||||||
],
|
|
||||||
"threshold": model.threshold,
|
|
||||||
"eval_results": model.eval_results,
|
|
||||||
"model_status": model.model_status,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if model.app_domain:
|
|
||||||
model_dict["app_domain"] = {
|
|
||||||
"uuid": str(model.app_domain.uuid),
|
|
||||||
"url": model.app_domain.url,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
model_dict["app_domain"] = None
|
|
||||||
|
|
||||||
# Specific fields for different model types
|
|
||||||
if isinstance(model, RuleBasedRelativeReasoning):
|
|
||||||
model_dict.update(
|
|
||||||
{
|
|
||||||
"model_type": "RuleBasedRelativeReasoning",
|
|
||||||
"min_count": model.min_count,
|
|
||||||
"max_count": model.max_count,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif isinstance(model, MLRelativeReasoning):
|
|
||||||
model_dict["model_type"] = "MLRelativeReasoning"
|
|
||||||
elif isinstance(model, EnviFormer):
|
|
||||||
model_dict["model_type"] = "EnviFormer"
|
|
||||||
else:
|
|
||||||
model_dict["model_type"] = "EPModel"
|
|
||||||
|
|
||||||
result["models"].append(model_dict)
|
|
||||||
|
|
||||||
print(f"Export completed for package: {package.name}")
|
|
||||||
print(f"- Compounds: {len(result['compounds'])}")
|
|
||||||
print(f"- Structures: {len(result['structures'])}")
|
|
||||||
print(f"- Simple rules: {len(result['rules']['simple_rules'])}")
|
|
||||||
print(f"- Parallel rules: {len(result['rules']['parallel_rules'])}")
|
|
||||||
print(f"- Sequential rules: {len(result['rules']['sequential_rules'])}")
|
|
||||||
print(f"- Reactions: {len(result['reactions'])}")
|
|
||||||
print(f"- Pathways: {len(result['pathways'])}")
|
|
||||||
print(f"- Nodes: {len(result['nodes'])}")
|
|
||||||
print(f"- Edges: {len(result['edges'])}")
|
|
||||||
print(f"- Scenarios: {len(result['scenarios'])}")
|
|
||||||
print(f"- Models: {len(result['models'])}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class PackageImporter:
|
class PackageImporter:
|
||||||
|
|||||||
Reference in New Issue
Block a user