forked from enviPath/enviPy
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b12c2dda69 | ||
|
|
0b325a30b7 | ||
|
|
d8741c5375 | ||
|
|
887de03a19 | ||
|
|
b54c8eaab6 | ||
|
|
f885745127 | ||
|
|
48d2bce52a | ||
|
|
67aa3731cb |
@@ -85,6 +85,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libxext6 \
|
||||
libfontconfig1 \
|
||||
nano \
|
||||
openjdk-21-jre-headless \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN useradd -ms /bin/bash django
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 6.0.3 on 2026-09-08 08:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bayer', '0003_pescompound_pesstructure_package_data_pool'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='package',
|
||||
name='shareable',
|
||||
field=models.BooleanField(default=True, verbose_name='Shareable'),
|
||||
),
|
||||
]
|
||||
@@ -23,6 +23,7 @@ class Package(EnviPathModel):
|
||||
license = models.ForeignKey(
|
||||
"epdb.License", on_delete=models.SET_NULL, blank=True, null=True, verbose_name="License"
|
||||
)
|
||||
shareable = models.BooleanField(verbose_name="Shareable", default=True)
|
||||
|
||||
class Classification(models.IntegerChoices):
|
||||
INTERNAL = 0, "Internal"
|
||||
|
||||
+85
-39
@@ -8,7 +8,7 @@ from django.shortcuts import redirect
|
||||
|
||||
from bayer.models import PESCompound
|
||||
from epdb.logic import PackageManager
|
||||
from epdb.models import Pathway, Node
|
||||
from epdb.models import Pathway, Node, Group
|
||||
from epdb.views import _anonymous_or_real, error
|
||||
from utilities.decorators import package_permission_required
|
||||
|
||||
@@ -18,6 +18,23 @@ Package = s.GET_PACKAGE_MODEL()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def has_secret_group(user):
|
||||
"""
|
||||
Determines if the specified user belongs to any secret group.
|
||||
|
||||
This function checks whether the given user is a member of any group
|
||||
that is marked as secret.
|
||||
|
||||
Args:
|
||||
user: The user for whom the check is performed.
|
||||
|
||||
Returns:
|
||||
bool: True if the user belongs to at least one secret group,
|
||||
False otherwise.
|
||||
"""
|
||||
return Group.objects.filter(secret=True, user_member=user).exists()
|
||||
|
||||
|
||||
@package_permission_required()
|
||||
def create_pes(request, package_uuid):
|
||||
current_user = _anonymous_or_real(request)
|
||||
@@ -38,7 +55,7 @@ def create_pes(request, package_uuid):
|
||||
|
||||
if pes_link:
|
||||
try:
|
||||
pes_data = fetch_pes(request, pes_link)
|
||||
pes_data = fetch_pes(request, pes_link, current_user)
|
||||
except ValueError as e:
|
||||
return error(
|
||||
request,
|
||||
@@ -98,12 +115,12 @@ def create_pes_node(request, package_uuid, pathway_uuid):
|
||||
|
||||
if pes_link:
|
||||
try:
|
||||
pes_data = fetch_pes(request, pes_link)
|
||||
pes_data = fetch_pes(request, pes_link, current_user)
|
||||
except ValueError as e:
|
||||
return error(
|
||||
request,
|
||||
"Could not fetch PES",
|
||||
f"Could not fetch PES data for {pes_link}"
|
||||
"Failed to fetch this PES",
|
||||
"Either the PES-ID is incorrect, or you're missing sufficient permissions to access this (potentially secret) item. In case you're missing permissions you can request the entitlement cs.u.enviPath_secret_data_user_group on go/idnow to gain access."
|
||||
)
|
||||
|
||||
classification = pes_data.get("classificationLevel", "")
|
||||
@@ -157,53 +174,82 @@ def create_pes_node(request, package_uuid, pathway_uuid):
|
||||
return HttpResponseNotAllowed(["POST"])
|
||||
|
||||
|
||||
def fetch_pes(request, pes_url) -> dict:
|
||||
from epauth.views import get_access_token_from_request
|
||||
token = get_access_token_from_request(request)
|
||||
def get_application_token(prod: bool) -> str:
|
||||
scope = f"{s.PROD_PES_SCOPE if prod else s.NON_PROD_PES_SCOPE}/.default"
|
||||
|
||||
if token is None:
|
||||
token = pes_url.split('/')[-1] == 'dummy'
|
||||
url = f"https://login.microsoftonline.com/{s.MS_ENTRA_TENANT_ID}/oauth2/v2.0/token"
|
||||
data = {
|
||||
"grant_type": "client_credentials",
|
||||
"client_id": s.MS_ENTRA_CLIENT_ID,
|
||||
"client_secret": s.MS_ENTRA_CLIENT_SECRET,
|
||||
"scope": scope,
|
||||
}
|
||||
|
||||
if token:
|
||||
for k, v in s.PES_API_MAPPING.items():
|
||||
if pes_url.startswith(k):
|
||||
pes_id = pes_url.split('/')[-1]
|
||||
try:
|
||||
response = requests.post(url, data=data)
|
||||
response.raise_for_status()
|
||||
return response.json()["access_token"]
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.error(f"Could not fetch application token: {e}")
|
||||
raise ValueError(f"Could not fetch application token!")
|
||||
|
||||
if pes_id == 'dummy':
|
||||
import json
|
||||
res_data = json.load(open(s.BASE_DIR / "fixtures/pes.json"))
|
||||
|
||||
def fetch_pes(request, pes_url, user) -> dict:
|
||||
|
||||
for k, v in s.PES_API_MAPPING.items():
|
||||
if pes_url.startswith(k):
|
||||
|
||||
prod = "cropkey-np" not in pes_url
|
||||
|
||||
pes_id = pes_url.split('/')[-1]
|
||||
|
||||
if pes_id == 'dummy':
|
||||
import json
|
||||
res_data = json.load(open(s.BASE_DIR / "fixtures/pes.json"))
|
||||
res_data["pes_url"] = pes_url
|
||||
return res_data
|
||||
else:
|
||||
headers = {
|
||||
"accept": "*/*",
|
||||
"authorization": "Bearer " + get_application_token(prod),
|
||||
}
|
||||
|
||||
# Restrict request if user is not part of any secret group
|
||||
if not has_secret_group(user):
|
||||
headers["app-classification-level-restriction"] = "restrict-pes-secret-structure-access"
|
||||
|
||||
|
||||
params = {"pes_reg_entity_corporate_id": pes_id}
|
||||
|
||||
res = requests.get(v, headers=headers, params=params, proxies=s.PROXIES or None)
|
||||
|
||||
try:
|
||||
res.raise_for_status()
|
||||
pes_data = res.json()
|
||||
|
||||
# Handle missing response
|
||||
if "detail" in pes_data and "The following PES Reg Entities Corporate Ids could not be found" in pes_data["detail"]:
|
||||
raise ValueError(f"PES with id {pes_id} not found")
|
||||
|
||||
# Ensure we have a entity
|
||||
if len(pes_data) == 0:
|
||||
raise ValueError(f"PES with id {pes_id} not found")
|
||||
|
||||
res_data = pes_data[0]
|
||||
res_data["pes_url"] = pes_url
|
||||
return res_data
|
||||
else:
|
||||
headers = {"Authorization": f"Bearer {token['access_token']}"}
|
||||
params = {"pes_reg_entity_corporate_id": pes_id}
|
||||
|
||||
res = requests.get(v, headers=headers, params=params, proxies=s.PROXIES or None)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
raise ValueError(f"Error fetching PES with id {pes_id}: {e}")
|
||||
|
||||
try:
|
||||
res.raise_for_status()
|
||||
pes_data = res.json()
|
||||
|
||||
if len(pes_data) == 0:
|
||||
raise ValueError(f"PES with id {pes_id} not found")
|
||||
|
||||
res_data = pes_data[0]
|
||||
res_data["pes_url"] = pes_url
|
||||
return res_data
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
raise ValueError(f"Error fetching PES with id {pes_id}: {e}")
|
||||
else:
|
||||
raise ValueError(f"Unknown URL {pes_url}")
|
||||
else:
|
||||
raise ValueError("Could not fetch access token from request.")
|
||||
raise ValueError(f"Unknown URL {pes_url}")
|
||||
|
||||
|
||||
def visualize_pes(request):
|
||||
pes_link = request.GET.get('pesLink')
|
||||
|
||||
if pes_link:
|
||||
pes_data = fetch_pes(request, pes_link)
|
||||
pes_data = fetch_pes(request, pes_link, request.user)
|
||||
|
||||
representations = pes_data.get('representations')
|
||||
|
||||
|
||||
@@ -445,6 +445,8 @@ if MS_ENTRA_ENABLED:
|
||||
MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
|
||||
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
|
||||
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
|
||||
NON_PROD_PES_SCOPE = os.environ.get("NON_PROD_PES_SCOPE")
|
||||
PROD_PES_SCOPE = os.environ.get("PROD_PES_SCOPE")
|
||||
|
||||
# Site ID 10 -> beta.envipath.org
|
||||
MATOMO_SITE_ID = os.environ.get("MATOMO_SITE_ID", "10")
|
||||
|
||||
+17
-2
@@ -9,7 +9,7 @@ from django.shortcuts import redirect
|
||||
|
||||
from epdb.logic import UserManager, GroupManager
|
||||
from epdb.models import Group
|
||||
from epdb.views import get_remote_address
|
||||
from epdb.views import get_remote_address, error
|
||||
|
||||
auth_log = logging.getLogger("auth")
|
||||
|
||||
@@ -72,6 +72,15 @@ def entra_callback(request):
|
||||
|
||||
claims = result["id_token_claims"]
|
||||
|
||||
if claims.get("roles") is None or claims.get("roles") == [] or "envipath_registered_user" not in claims.get("roles"):
|
||||
auth_log.error(f"Login attempt by {get_remote_address(request)} failed due to missing role")
|
||||
return error(
|
||||
request,
|
||||
"Login Failed",
|
||||
"Request access to role 1230/MON/APPS/Envipath/Envipath_Prod on go/idnow",
|
||||
403,
|
||||
)
|
||||
|
||||
user_name = claims.get("name")
|
||||
# preferred_username is a fallback for 2nd CWID
|
||||
user_email = claims.get("emailaddress", claims.get("email", claims.get("preferred_username")))
|
||||
@@ -130,10 +139,16 @@ def entra_callback(request):
|
||||
auth_log.info(f"Login Group Sync: Adding {u.username} to Group {g.name} ({ uuid })")
|
||||
else:
|
||||
g = Group.objects.get(uuid=uuid)
|
||||
if g.user_member.contains(u):
|
||||
# Do not remove users from All enviPath Users
|
||||
if g.user_member.contains(u) and g.name != 'All enviPath Users':
|
||||
g.user_member.remove(u)
|
||||
auth_log.info(f"Login Group Sync: Removing {u.username} from Group {g.name} ({ uuid })")
|
||||
|
||||
# Ensure people are part of All enviPath Users -> they have to as, envipath_registered_user is granted
|
||||
all_envipath_users = Group.objects.get(name="All enviPath Users")
|
||||
if not all_envipath_users.user_member.contains(u):
|
||||
all_envipath_users.user_member.add(u)
|
||||
|
||||
if registered:
|
||||
# #72 make package secret if user is part of a secret group
|
||||
for id, name in s.ENTRA_SECRET_GROUPS.items():
|
||||
|
||||
@@ -11,6 +11,7 @@ from .models import (
|
||||
CompoundStructure,
|
||||
Edge,
|
||||
EnviFormer,
|
||||
EnzymeLink,
|
||||
ExternalDatabase,
|
||||
ExternalIdentifier,
|
||||
Group,
|
||||
@@ -212,6 +213,10 @@ class CompoundStructureAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class EnzymeLinkAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class SimpleAmbitRuleAdmin(EPAdmin):
|
||||
pass
|
||||
|
||||
@@ -266,6 +271,7 @@ admin.site.register(License, LicenseAdmin)
|
||||
admin.site.register(ClassifierPluginModel, ClassifierPluginModelAdmin)
|
||||
admin.site.register(Compound, CompoundAdmin)
|
||||
admin.site.register(CompoundStructure, CompoundStructureAdmin)
|
||||
admin.site.register(EnzymeLink, EnzymeLinkAdmin)
|
||||
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)
|
||||
admin.site.register(ParallelRule, ParallelRuleAdmin)
|
||||
admin.site.register(Reaction, ReactionAdmin)
|
||||
|
||||
+2
-2
@@ -889,7 +889,7 @@ def create_package_compound(
|
||||
from bayer.models import PESCompound
|
||||
|
||||
try:
|
||||
pes_data = fetch_pes(request, c.pesLink)
|
||||
pes_data = fetch_pes(request, c.pesLink, request.user)
|
||||
except ValueError as e:
|
||||
return 400, {"message": f"Could not fetch PES data for {c.pesLink}"}
|
||||
|
||||
@@ -2014,7 +2014,7 @@ def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
|
||||
from bayer.models import PESCompound
|
||||
|
||||
try:
|
||||
pes_data = fetch_pes(request, n.pesLink)
|
||||
pes_data = fetch_pes(request, n.pesLink, request.user)
|
||||
except ValueError as e:
|
||||
return 400, {"message": f"Could not fetch PES data for {n.pesLink}"}
|
||||
|
||||
|
||||
+20
-6
@@ -211,7 +211,7 @@ class UserManager(object):
|
||||
# Create package
|
||||
package_name = f"{u.username}{'’' if u.username[-1] in 'sxzß' else 's'} Package"
|
||||
package_description = "This package was generated during registration."
|
||||
p = PackageManager.create_package(u, package_name, package_description)
|
||||
p = PackageManager.create_package(u, package_name, package_description, shareable=False)
|
||||
u.default_package = p
|
||||
u.save()
|
||||
|
||||
@@ -547,7 +547,7 @@ class PackageManager(object):
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def create_package(current_user, name: str, description: str = None):
|
||||
def create_package(current_user, name: str, description: str = None, *args, **kwargs):
|
||||
p = Package()
|
||||
|
||||
# Clean for potential XSS
|
||||
@@ -556,6 +556,9 @@ class PackageManager(object):
|
||||
if description is not None and description.strip() != "":
|
||||
p.description = nh3.clean(description.strip(), tags=s.ALLOWED_HTML_TAGS).strip()
|
||||
|
||||
if "shareable" in kwargs:
|
||||
p.shareable = kwargs["shareable"]
|
||||
|
||||
p.save()
|
||||
|
||||
up = UserPackagePermission()
|
||||
@@ -623,6 +626,10 @@ class PackageManager(object):
|
||||
def grant_write(caller: User, package: Package, grantee: Union[User, Group]):
|
||||
PackageManager.update_permissions(caller, package, grantee, Permission.WRITE[0])
|
||||
|
||||
@staticmethod
|
||||
def grant_owner(caller: User, package: Package, grantee: Union[User, Group]):
|
||||
PackageManager.update_permissions(caller, package, grantee, Permission.ALL[0])
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
def import_legacy_package(
|
||||
@@ -665,11 +672,11 @@ class PackageManager(object):
|
||||
# EDIT START
|
||||
if data.get("classification"):
|
||||
if data["classification"] == "INTERNAL":
|
||||
pack.classification = Package.Classification.RESTRICTED
|
||||
pack.classification_level = Package.Classification.RESTRICTED
|
||||
elif data["classification"] == "RESTRICTED":
|
||||
pack.classification = Package.Classification.RESTRICTED
|
||||
pack.classification_level = Package.Classification.RESTRICTED
|
||||
elif data["classification"] == "SECRET":
|
||||
pack.classification = Package.Classification.SECRET
|
||||
pack.classification_level = Package.Classification.SECRET
|
||||
|
||||
if not "datapool" in data:
|
||||
raise ValueError("Missing datapool in package")
|
||||
@@ -821,7 +828,14 @@ class PackageManager(object):
|
||||
r.name = rule["name"]
|
||||
r.description = rule["description"]
|
||||
r.aliases = rule.get("aliases", [])
|
||||
r.smirks = rule["smirks"]
|
||||
|
||||
if rule.get("smirks") is not None:
|
||||
r.smirks = rule["smirks"]
|
||||
elif rule.get("reactionSmarts") is not None:
|
||||
r.smirks = rule["reactionSmarts"]
|
||||
else:
|
||||
raise ValueError(f"No SMIRKS or reactionSmarts found for rule {rule['id']}")
|
||||
|
||||
r.reactant_filter_smarts = rule.get("reactantFilterSmarts", None)
|
||||
r.product_filter_smarts = rule.get("productFilterSmarts", None)
|
||||
r.save()
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<i class="glyphicon glyphicon-edit"></i> Edit Package</a
|
||||
>
|
||||
</li>
|
||||
{% if meta.current_package.shareable %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
@@ -15,6 +16,7 @@
|
||||
<i class="glyphicon glyphicon-user"></i> Edit Permissions</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if meta.current_package.get_classification_level_display != "Secret" %}
|
||||
<li>
|
||||
<a
|
||||
@@ -52,7 +54,7 @@
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if not meta.can_edit %}
|
||||
{% if not meta.can_edit and meta.current_package.shareable %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<i class="glyphicon glyphicon-edit"></i> Update</a
|
||||
>
|
||||
</li>
|
||||
{% if 1 == 0 %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
@@ -15,6 +16,7 @@
|
||||
<i class="glyphicon glyphicon-lock"></i> Update Password</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a
|
||||
role="button"
|
||||
|
||||
@@ -180,12 +180,26 @@
|
||||
>Aspartame</a
|
||||
>
|
||||
</div>
|
||||
<a
|
||||
class="absolute top-0 left-[calc(100%-5.4rem)]"
|
||||
href="/predict"
|
||||
>Advanced</a
|
||||
>
|
||||
</div>
|
||||
{% if meta.current_package %}
|
||||
<div
|
||||
class="mt-4 rounded-lg border border-base-300 bg-base-50 px-4 py-2.5 text-sm text-base-content/70"
|
||||
>
|
||||
Prediction will be stored in
|
||||
<strong class="text-base-content"
|
||||
>{{ meta.current_package.name|safe }}</strong
|
||||
>
|
||||
{% if meta.user.default_setting %}
|
||||
using setting
|
||||
<strong class="text-base-content"
|
||||
>{{ meta.user.default_setting.name|safe }}</strong
|
||||
>
|
||||
{% endif %}
|
||||
<br />
|
||||
To use a different setting click
|
||||
<a class="label link" href="/predict">here</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div
|
||||
id="ketcher-container"
|
||||
@@ -208,9 +222,25 @@
|
||||
>
|
||||
Predict!
|
||||
</button>
|
||||
<div class="mt-1 flex w-full justify-end">
|
||||
<a class="label justify-end" href="/predict">Advanced</a>
|
||||
</div>
|
||||
{% if meta.current_package %}
|
||||
<div
|
||||
class="mt-4 rounded-lg border border-base-300 bg-base-50 px-4 py-2.5 text-sm text-base-content/70"
|
||||
>
|
||||
Prediction will be stored in
|
||||
<strong class="text-base-content"
|
||||
>{{ meta.current_package.name|safe }}</strong
|
||||
>
|
||||
{% if meta.user.default_setting %}
|
||||
using setting
|
||||
<strong class="text-base-content"
|
||||
>{{ meta.user.default_setting.name|safe }}</strong
|
||||
>
|
||||
{% endif %}
|
||||
<br />
|
||||
To use a different setting click
|
||||
<a class="label link" href="/predict">here</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<input
|
||||
type="hidden"
|
||||
|
||||
+33
-7
@@ -21,6 +21,8 @@ from epdb.models import (
|
||||
Compound,
|
||||
CompoundStructure,
|
||||
Edge,
|
||||
EnzymeLink,
|
||||
Group,
|
||||
License,
|
||||
Node,
|
||||
ParallelRule,
|
||||
@@ -150,6 +152,8 @@ class ReactionExportSchema(RefReactionExportSchema):
|
||||
# Rules #
|
||||
#########
|
||||
class EnzymeExportSchema(RefEnzymeExportSchema):
|
||||
name: str
|
||||
description: str
|
||||
ec_number: str
|
||||
classification_level: int
|
||||
linking_method: str
|
||||
@@ -162,12 +166,12 @@ class EnzymeRuleExportSchema(RefRuleExportSchema):
|
||||
|
||||
@staticmethod
|
||||
def resolve_enzymes(obj):
|
||||
if isinstance(obj, dict):
|
||||
res = []
|
||||
for e in obj.get("enzymes", []):
|
||||
res.append(EnzymeExportSchema.model_validate(e))
|
||||
return res
|
||||
return obj.enzymelink_set.all()
|
||||
if isinstance(obj, EnzymeRuleExportSchema):
|
||||
return obj.enzymes
|
||||
elif isinstance(obj, dict):
|
||||
return obj.get("enzymes", [])
|
||||
else:
|
||||
return obj.enzymelink_set.all()
|
||||
|
||||
|
||||
class RuleExportSchema(EnzymeRuleExportSchema):
|
||||
@@ -271,7 +275,7 @@ class PackageExportSchema(Schema):
|
||||
@staticmethod
|
||||
def resolve_classification_level(obj):
|
||||
if isinstance(obj, dict):
|
||||
return obj["classification_level"]
|
||||
return obj.get("classification_level", "Internal")
|
||||
return obj.Classification(obj.classification_level).name
|
||||
|
||||
|
||||
@@ -679,6 +683,27 @@ class PackageImporter:
|
||||
for scen in elem.scenarios:
|
||||
elem_obj.scenarios.add(self._cache[scen.uuid])
|
||||
|
||||
def _import_enzyme(self, rule: Rule, enzyme: EnzymeExportSchema):
|
||||
e = EnzymeLink()
|
||||
e.uuid = str(uuid.uuid4()) if not self.preserve_uuids else enzyme.uuid
|
||||
e.rule = rule
|
||||
e.name = enzyme.name
|
||||
e.description = enzyme.description
|
||||
e.ec_number = enzyme.ec_number
|
||||
e.classification_level = enzyme.classification_level
|
||||
e.linking_method = enzyme.linking_method
|
||||
e.save()
|
||||
for reaction in enzyme.reaction_evidence:
|
||||
e.reaction_evidence.add(self._cache[reaction.uuid])
|
||||
for edge in enzyme.edge_evidence:
|
||||
e.edge_evidence.add(self._cache[edge.uuid])
|
||||
self._cache[enzyme.uuid] = e
|
||||
|
||||
def _import_and_link_enzymes(self, data: PackageExportSchema):
|
||||
for rule in data.composite_rules:
|
||||
for enzyme in rule.enzymes:
|
||||
self._import_enzyme(self._cache[rule.uuid], enzyme)
|
||||
|
||||
def _import_package_from_json(
|
||||
self,
|
||||
) -> Package:
|
||||
@@ -717,6 +742,7 @@ class PackageImporter:
|
||||
self._import_scenarios(package, parsed.scenarios)
|
||||
self._import_additional_information(package, parsed.additional_information)
|
||||
self._link_scenarios_after_import(parsed)
|
||||
self._import_and_link_enzymes(parsed)
|
||||
|
||||
return package
|
||||
|
||||
|
||||
@@ -894,7 +894,7 @@ provides-extras = ["ms-login", "dev", "pepper-plugin"]
|
||||
[[package]]
|
||||
name = "envipy-additional-information"
|
||||
version = "0.4.2"
|
||||
source = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git?branch=develop#ad825570480bbe2f1a35c04923fede756c450751" }
|
||||
source = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git?branch=develop#4909e35aac5a0a3969ac2a01f8d4ad8bdadae04c" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user