forked from enviPath/enviPy
Compare commits
1 Commits
develop-ba
...
54056c654d
| Author | SHA1 | Date | |
|---|---|---|---|
| 54056c654d |
@ -30,13 +30,12 @@ RUN mkdir -p -m 0700 /root/.ssh \
|
|||||||
&& ssh-keyscan git.envipath.com >> /root/.ssh/known_hosts
|
&& ssh-keyscan git.envipath.com >> /root/.ssh/known_hosts
|
||||||
|
|
||||||
# We'll need access to private repos, let docker make use of host ssh agent and use it like:
|
# We'll need access to private repos, let docker make use of host ssh agent and use it like:
|
||||||
# docker build --ssh default -t envipath/envipy-bayer:1.0 .
|
# docker build --ssh default -t envipath/envipy:1.0 .
|
||||||
RUN --mount=type=ssh \
|
RUN --mount=type=ssh \
|
||||||
uv sync --locked --extra ms-login --extra pepper-plugin
|
uv sync --locked --extra ms-login --extra pepper-plugin
|
||||||
|
|
||||||
# Now copy source and do a final sync to install the project itself
|
# Now copy source and do a final sync to install the project itself
|
||||||
# Ensure .dockerignore is reasonable
|
# Ensure .dockerignore is reasonable
|
||||||
COPY bb4g bb4g
|
|
||||||
COPY biotransformer biotransformer
|
COPY biotransformer biotransformer
|
||||||
COPY bayer bayer
|
COPY bayer bayer
|
||||||
COPY bridge bridge
|
COPY bridge bridge
|
||||||
|
|||||||
@ -13,14 +13,6 @@ register_template(
|
|||||||
"modals.collections.compound",
|
"modals.collections.compound",
|
||||||
"modals/collections/new_pes_modal.html",
|
"modals/collections/new_pes_modal.html",
|
||||||
)
|
)
|
||||||
register_template(
|
|
||||||
"epdb.actions.objects.pathway.add",
|
|
||||||
"actions/objects/pathway_add_pes.html",
|
|
||||||
)
|
|
||||||
register_template(
|
|
||||||
"epdb.modals.objects.pathway.add",
|
|
||||||
"modals/objects/add_pathway_pes_node_modal.html"
|
|
||||||
)
|
|
||||||
|
|
||||||
# PES Viz
|
# PES Viz
|
||||||
register_template(
|
register_template(
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
<li>
|
|
||||||
<a
|
|
||||||
class="button"
|
|
||||||
onclick="document.getElementById('add_pathway_pes_node_modal').showModal(); return false;"
|
|
||||||
>
|
|
||||||
<i class="glyphicon glyphicon-plus"></i> Add PES</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
@ -103,15 +103,20 @@ def create_pes_node(request, package_uuid, pathway_uuid):
|
|||||||
|
|
||||||
|
|
||||||
def fetch_pes(request, pes_url) -> dict:
|
def fetch_pes(request, pes_url) -> dict:
|
||||||
|
proxies = {
|
||||||
|
"http": "http://10.185.190.100:8080",
|
||||||
|
"https": "http://10.185.190.100:8080",
|
||||||
|
}
|
||||||
|
|
||||||
from epauth.views import get_access_token_from_request
|
from epauth.views import get_access_token_from_request
|
||||||
token = get_access_token_from_request(request)
|
token = get_access_token_from_request(request)
|
||||||
|
|
||||||
if token:
|
if token or True:
|
||||||
for k, v in s.PES_API_MAPPING.items():
|
for k, v in s.PES_API_MAPPING.items():
|
||||||
if pes_url.startswith(k):
|
if pes_url.startswith(k):
|
||||||
pes_id = pes_url.split('/')[-1]
|
pes_id = pes_url.split('/')[-1]
|
||||||
|
|
||||||
if pes_id == 'dummy':
|
if pes_id == 'dummy' or True:
|
||||||
import json
|
import json
|
||||||
res_data = json.load(open(s.BASE_DIR / "fixtures/pes.json"))
|
res_data = json.load(open(s.BASE_DIR / "fixtures/pes.json"))
|
||||||
res_data["pes_url"] = pes_url
|
res_data["pes_url"] = pes_url
|
||||||
@ -120,7 +125,7 @@ def fetch_pes(request, pes_url) -> dict:
|
|||||||
headers = {"Authorization": f"Bearer {token['access_token']}"}
|
headers = {"Authorization": f"Bearer {token['access_token']}"}
|
||||||
params = {"pes_reg_entity_corporate_id": pes_id}
|
params = {"pes_reg_entity_corporate_id": pes_id}
|
||||||
|
|
||||||
res = requests.get(v, headers=headers, params=params, proxies=s.PROXIES or None)
|
res = requests.get(v, headers=headers, params=params, proxies=proxies)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res.raise_for_status()
|
res.raise_for_status()
|
||||||
|
|||||||
183
bb4g/__init__.py
183
bb4g/__init__.py
@ -1,183 +0,0 @@
|
|||||||
import json
|
|
||||||
import math
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import List
|
|
||||||
import enum
|
|
||||||
import requests
|
|
||||||
from django.conf import settings as s
|
|
||||||
from envipy_additional_information import EnviPyModel, UIConfig, WidgetType
|
|
||||||
from envipy_additional_information import register
|
|
||||||
|
|
||||||
from bridge.contracts import Classifier # noqa: I001
|
|
||||||
from bridge.dto import (
|
|
||||||
BuildResult,
|
|
||||||
EnviPyDTO,
|
|
||||||
EvaluationResult,
|
|
||||||
RunResult,
|
|
||||||
TransformationProductPrediction,
|
|
||||||
) # noqa: I001
|
|
||||||
|
|
||||||
class SamplingAlgorithm(enum.Enum):
|
|
||||||
EXACT = "exact"
|
|
||||||
|
|
||||||
|
|
||||||
@register("bb4gconfig")
|
|
||||||
class BB4GConfig(EnviPyModel):
|
|
||||||
sampling_algorithm: SamplingAlgorithm = SamplingAlgorithm.EXACT
|
|
||||||
cutoff: int = -5
|
|
||||||
|
|
||||||
class UI:
|
|
||||||
title = "BB4G Configuration"
|
|
||||||
sampling_algorithm = UIConfig(
|
|
||||||
widget=WidgetType.SELECT,
|
|
||||||
label="BB4G Sampling Algorithm",
|
|
||||||
order=1,
|
|
||||||
placeholder="If unset defaults to 'exact'"
|
|
||||||
)
|
|
||||||
cutoff = UIConfig(
|
|
||||||
widget=WidgetType.NUMBER,
|
|
||||||
label="BB4G Cutoff",
|
|
||||||
order=2,
|
|
||||||
placeholder="If unset defaults to -5"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Once stable these will be exposed by enviPy-plugins lib
|
|
||||||
class BB4G(Classifier):
|
|
||||||
Config = BB4GConfig
|
|
||||||
|
|
||||||
def __init__(self, config: BB4GConfig | None = None):
|
|
||||||
super().__init__(config)
|
|
||||||
self.url = f"{s.BB4G_URL}"
|
|
||||||
|
|
||||||
self.token = self.acquire_token()
|
|
||||||
self.header = {
|
|
||||||
"Authorization": f"Bearer {self.token}",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
def acquire_token(self):
|
|
||||||
BB4G_TENANT_ID = s.BB4G_TENANT_ID
|
|
||||||
BB4G_CLIENT_ID = s.BB4G_CLIENT_ID
|
|
||||||
BB4G_CLIENT_SECRET = s.BB4G_CLIENT_SECRET
|
|
||||||
BB4G_SCOPE = s.BB4G_SCOPE
|
|
||||||
|
|
||||||
BB4G_TOKEN_URL = f"https://login.microsoftonline.com/{BB4G_TENANT_ID}/oauth2/v2.0/token"
|
|
||||||
|
|
||||||
payload = {
|
|
||||||
"client_id": BB4G_CLIENT_ID,
|
|
||||||
"client_secret": BB4G_CLIENT_SECRET,
|
|
||||||
"scope": BB4G_SCOPE,
|
|
||||||
"grant_type": "client_credentials"
|
|
||||||
}
|
|
||||||
|
|
||||||
# No Proxy required, URL is whitelisted
|
|
||||||
res = requests.post(BB4G_TOKEN_URL, data=payload)
|
|
||||||
|
|
||||||
res.raise_for_status()
|
|
||||||
|
|
||||||
return res.json()["access_token"]
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
header = {
|
|
||||||
"Authorization": f"Bearer {self.token}",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
started = False
|
|
||||||
retries = 0
|
|
||||||
while not started and retries < 5:
|
|
||||||
res = requests.post(f"{self.url}/start", headers=header, data={}, proxies=s.PROXIES or None)
|
|
||||||
|
|
||||||
if res.status_code == 200:
|
|
||||||
started = True
|
|
||||||
elif res.status_code in [500, 502]:
|
|
||||||
retries += 1
|
|
||||||
import time
|
|
||||||
time.sleep(5)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unexpected status code: {res.status_code}")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def requires_rule_packages(cls) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def requires_data_packages(cls) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def identifier(cls) -> str:
|
|
||||||
return "bb4g"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def name(cls) -> str:
|
|
||||||
return "BB4G Template Free Model"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def display(cls) -> str:
|
|
||||||
return "BB4G Template Free Model"
|
|
||||||
|
|
||||||
def build(self, eP: EnviPyDTO, *args, **kwargs) -> BuildResult | None:
|
|
||||||
return
|
|
||||||
|
|
||||||
def run(self, eP: EnviPyDTO, *args, **kwargs) -> RunResult:
|
|
||||||
|
|
||||||
# Ensure Service is running
|
|
||||||
self.start()
|
|
||||||
|
|
||||||
smiles = [c.smiles for c in eP.get_compounds()]
|
|
||||||
preds = self._post(smiles)
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for substrate in preds.keys():
|
|
||||||
results.append(
|
|
||||||
TransformationProductPrediction(
|
|
||||||
substrate=substrate,
|
|
||||||
products=preds[substrate],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return RunResult(
|
|
||||||
producer=eP.get_context().url,
|
|
||||||
description=f"Generated at {datetime.now()}",
|
|
||||||
result=results,
|
|
||||||
)
|
|
||||||
|
|
||||||
def evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def build_and_evaluate(self, eP: EnviPyDTO, *args, **kwargs) -> EvaluationResult:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _post(self, smiles: List[str]) -> dict[str, dict[str, float]]:
|
|
||||||
header = {
|
|
||||||
"Authorization": f"Bearer {self.token}",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
for smi in smiles:
|
|
||||||
data = {
|
|
||||||
"smiles": smi,
|
|
||||||
"sampling_alg": self.config.sampling_algorithm.value,
|
|
||||||
"cutoff": self.config.cutoff,
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = requests.post(f"{self.url}/compute", headers=header, data=json.dumps(data), proxies=s.PROXIES or None)
|
|
||||||
|
|
||||||
resp.raise_for_status()
|
|
||||||
|
|
||||||
for substrate, predictions in resp.json().items():
|
|
||||||
preds = {}
|
|
||||||
|
|
||||||
for pred in predictions:
|
|
||||||
prod = pred["prediction"]
|
|
||||||
prob = math.exp(pred["log_likelihood"])
|
|
||||||
preds[prod] = prob
|
|
||||||
|
|
||||||
result[substrate] = preds
|
|
||||||
|
|
||||||
return result
|
|
||||||
@ -254,15 +254,7 @@ class Classifier(Plugin):
|
|||||||
def parse_config(cls, data: dict | None = None) -> EnviPyModel | None:
|
def parse_config(cls, data: dict | None = None) -> EnviPyModel | None:
|
||||||
if cls.Config is None:
|
if cls.Config is None:
|
||||||
return None
|
return None
|
||||||
|
return cls.Config(**(data or {}))
|
||||||
# remove empty strings a.k.a unset params to not overwrite defaults
|
|
||||||
cpy = {}
|
|
||||||
if data is not None:
|
|
||||||
for k, v in data.items():
|
|
||||||
if v != "":
|
|
||||||
cpy[k] = v
|
|
||||||
|
|
||||||
return cls.Config(**cpy)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, data: dict | None = None):
|
def create(cls, data: dict | None = None):
|
||||||
|
|||||||
@ -143,12 +143,6 @@ if os.environ.get("USE_TEMPLATE_DB", False) == "True":
|
|||||||
"TEMPLATE": os.environ["TEMPLATE_DB"],
|
"TEMPLATE": os.environ["TEMPLATE_DB"],
|
||||||
}
|
}
|
||||||
|
|
||||||
CACHES = {
|
|
||||||
"default": {
|
|
||||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
|
||||||
"LOCATION": "unique-snowflake",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
@ -480,14 +474,3 @@ if DATA_POOL_MAPPING:
|
|||||||
else:
|
else:
|
||||||
DATA_POOL_MAPPING = {}
|
DATA_POOL_MAPPING = {}
|
||||||
|
|
||||||
PROXIES = {}
|
|
||||||
if os.environ.get("HTTP_PROXY"):
|
|
||||||
PROXIES["http"] = os.environ.get("HTTP_PROXY")
|
|
||||||
PROXIES["https"] = os.environ.get("HTTPS_PROXY")
|
|
||||||
|
|
||||||
# BB4g
|
|
||||||
BB4G_URL = os.environ.get("BB4G_URL")
|
|
||||||
BB4G_TENANT_ID = os.environ.get("BB4G_TENANT_ID")
|
|
||||||
BB4G_CLIENT_ID = os.environ.get("BB4G_CLIENT_ID")
|
|
||||||
BB4G_CLIENT_SECRET = os.environ.get("BB4G_CLIENT_SECRET")
|
|
||||||
BB4G_SCOPE = os.environ.get("BB4G_SCOPE")
|
|
||||||
|
|||||||
@ -5,5 +5,4 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("entra/login/", views.entra_login, name="entra_login"),
|
path("entra/login/", views.entra_login, name="entra_login"),
|
||||||
path("auth/redirect/", views.entra_callback, name="entra_callback"),
|
path("auth/redirect/", views.entra_callback, name="entra_callback"),
|
||||||
path("auth/token/", views.get_token, name="get_token"),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,11 +2,9 @@ import msal
|
|||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
from epdb.logic import UserManager, GroupManager
|
from epdb.logic import UserManager
|
||||||
from epdb.models import Group
|
|
||||||
|
|
||||||
|
|
||||||
def get_msal_app_with_cache(request):
|
def get_msal_app_with_cache(request):
|
||||||
@ -83,12 +81,10 @@ def entra_callback(request):
|
|||||||
login(request, u)
|
login(request, u)
|
||||||
|
|
||||||
# EDIT START
|
# EDIT START
|
||||||
|
|
||||||
# Ensure groups exists in eP
|
# Ensure groups exists in eP
|
||||||
for id, name in s.ENTRA_SECRET_GROUPS.items():
|
for id, name in s.ENTRA_SECRET_GROUPS.items():
|
||||||
if not Group.objects.filter(uuid=id).exists():
|
if not Group.objects.filter(uuid=id).exists():
|
||||||
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ",
|
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ", uuid=id)
|
||||||
uuid=id)
|
|
||||||
else:
|
else:
|
||||||
g = Group.objects.get(uuid=id)
|
g = Group.objects.get(uuid=id)
|
||||||
# Ensure its secret
|
# Ensure its secret
|
||||||
@ -97,8 +93,7 @@ def entra_callback(request):
|
|||||||
|
|
||||||
for id, name in s.ENTRA_GROUPS.items():
|
for id, name in s.ENTRA_GROUPS.items():
|
||||||
if not Group.objects.filter(uuid=id).exists():
|
if not Group.objects.filter(uuid=id).exists():
|
||||||
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ",
|
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ", uuid=id)
|
||||||
uuid=id)
|
|
||||||
else:
|
else:
|
||||||
g = Group.objects.get(uuid=id)
|
g = Group.objects.get(uuid=id)
|
||||||
|
|
||||||
@ -116,11 +111,6 @@ def get_access_token_from_request(request, scopes=None):
|
|||||||
"""
|
"""
|
||||||
Get an access token from the request using MSAL token cache.
|
Get an access token from the request using MSAL token cache.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check if auth via Access Token
|
|
||||||
if request.headers.get("Authorization"):
|
|
||||||
return {"access_token": request.headers.get("Authorization").split(" ")[1]}
|
|
||||||
|
|
||||||
if scopes is None:
|
if scopes is None:
|
||||||
scopes = s.MS_ENTRA_SCOPES
|
scopes = s.MS_ENTRA_SCOPES
|
||||||
|
|
||||||
@ -162,9 +152,3 @@ def get_access_token_from_request(request, scopes=None):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_token(request):
|
|
||||||
token = get_access_token_from_request(request)
|
|
||||||
msg = f"{token}"
|
|
||||||
return HttpResponse(msg, content_type='text/plain')
|
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
|
import hashlib
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
import nh3
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import nh3
|
||||||
from django.conf import settings as s
|
from django.conf import settings as s
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.cache import cache
|
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, JsonResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from jwt import InvalidIssuerError
|
|
||||||
from ninja import Field, Form, Query, Router, Schema
|
from ninja import Field, Form, Query, Router, Schema
|
||||||
|
from ninja.errors import HttpError
|
||||||
from ninja.security import HttpBearer
|
from ninja.security import HttpBearer
|
||||||
|
from ninja.security import SessionAuth
|
||||||
|
|
||||||
from utilities.chem import FormatConverter
|
from utilities.chem import FormatConverter
|
||||||
from utilities.misc import PackageExporter
|
from utilities.misc import PackageExporter
|
||||||
@ -49,26 +51,6 @@ from .models import (
|
|||||||
Package = s.GET_PACKAGE_MODEL()
|
Package = s.GET_PACKAGE_MODEL()
|
||||||
|
|
||||||
|
|
||||||
def get_cached_jwks(tenant_id: str, force=False) -> Dict:
|
|
||||||
"""Get JWKS using Django cache"""
|
|
||||||
cache_key = f"jwks_{tenant_id}"
|
|
||||||
|
|
||||||
jwks = cache.get(cache_key)
|
|
||||||
|
|
||||||
if jwks is None or force:
|
|
||||||
# Cache miss, fetch new keys
|
|
||||||
jwks_uri = f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
|
|
||||||
response = requests.get(jwks_uri)
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
jwks = response.json()
|
|
||||||
|
|
||||||
# Cache for 1 hour (3600 seconds)
|
|
||||||
cache.set(cache_key, jwks, 3600)
|
|
||||||
|
|
||||||
return jwks
|
|
||||||
|
|
||||||
|
|
||||||
def get_package_for_write(user, package_uuid):
|
def get_package_for_write(user, package_uuid):
|
||||||
p = PackageManager.get_package_by_id(user, package_uuid)
|
p = PackageManager.get_package_by_id(user, package_uuid)
|
||||||
if not PackageManager.writable(user, p):
|
if not PackageManager.writable(user, p):
|
||||||
@ -86,7 +68,9 @@ def validate_token(token: str) -> dict:
|
|||||||
TENANT_ID = s.MS_ENTRA_TENANT_ID
|
TENANT_ID = s.MS_ENTRA_TENANT_ID
|
||||||
CLIENT_ID = s.MS_ENTRA_CLIENT_ID
|
CLIENT_ID = s.MS_ENTRA_CLIENT_ID
|
||||||
|
|
||||||
jwks = get_cached_jwks(TENANT_ID)
|
# Fetch Microsoft's public keys
|
||||||
|
jwks_uri = f"https://login.microsoftonline.com/{TENANT_ID}/discovery/v2.0/keys"
|
||||||
|
jwks = requests.get(jwks_uri).json()
|
||||||
|
|
||||||
header = jwt.get_unverified_header(token)
|
header = jwt.get_unverified_header(token)
|
||||||
|
|
||||||
@ -94,21 +78,13 @@ def validate_token(token: str) -> dict:
|
|||||||
next(k for k in jwks["keys"] if k["kid"] == header["kid"])
|
next(k for k in jwks["keys"] if k["kid"] == header["kid"])
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle V1 and V2 tokens
|
claims = jwt.decode(
|
||||||
try:
|
token,
|
||||||
claims = jwt.decode(
|
public_key,
|
||||||
token,
|
algorithms=["RS256"],
|
||||||
public_key,
|
audience=[CLIENT_ID, f"api://{CLIENT_ID}"],
|
||||||
algorithms=["RS256"],
|
issuer=f"https://sts.windows.net/{TENANT_ID}/",
|
||||||
audience=[CLIENT_ID, f"api://{CLIENT_ID}"],
|
)
|
||||||
issuer=[
|
|
||||||
f"https://sts.windows.net/{TENANT_ID}/",
|
|
||||||
f"https://login.microsoftonline.com/{TENANT_ID}/v2.0"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
raise ValueError(f"Token verification failed! - {e}")
|
|
||||||
|
|
||||||
return claims
|
return claims
|
||||||
|
|
||||||
|
|
||||||
@ -820,7 +796,6 @@ class CreateCompound(Schema):
|
|||||||
compoundName: str | None = None
|
compoundName: str | None = None
|
||||||
compoundDescription: str | None = None
|
compoundDescription: str | None = None
|
||||||
inchi: str | None = None
|
inchi: str | None = None
|
||||||
pesLink: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/package/{uuid:package_uuid}/compound")
|
@router.post("/package/{uuid:package_uuid}/compound")
|
||||||
@ -832,28 +807,9 @@ def create_package_compound(
|
|||||||
try:
|
try:
|
||||||
p = get_package_for_write(request.user, package_uuid)
|
p = get_package_for_write(request.user, package_uuid)
|
||||||
# inchi is not used atm
|
# inchi is not used atm
|
||||||
|
c = Compound.create(
|
||||||
if c.pesLink is not None:
|
p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
|
||||||
from bayer.views import fetch_pes
|
)
|
||||||
from bayer.models import PESCompound
|
|
||||||
|
|
||||||
try:
|
|
||||||
pes_data = fetch_pes(request, c.pesLink)
|
|
||||||
except ValueError as e:
|
|
||||||
return 400, {"message": f"Could not fetch PES data for {c.pesLink}"}
|
|
||||||
|
|
||||||
classification = pes_data.get("classificationLevel", "")
|
|
||||||
if "secret" == classification.lower():
|
|
||||||
data_pools = pes_data.get("dataPools")
|
|
||||||
if data_pools:
|
|
||||||
if s.DATA_POOL_MAPPING[p.data_pool.name] not in data_pools:
|
|
||||||
return 400, { "messsage": f"PES data pool {s.DATA_POOL_MAPPING[p.data_pool.name]} not found in PES data"}
|
|
||||||
|
|
||||||
c = PESCompound.create(p, pes_data, c.compoundName, c.compoundDescription)
|
|
||||||
else:
|
|
||||||
c = Compound.create(
|
|
||||||
p, c.compoundSmiles, c.compoundName, c.compoundDescription, inchi=c.inchi
|
|
||||||
)
|
|
||||||
return redirect(c.url)
|
return redirect(c.url)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return 400, {"message": str(e)}
|
return 400, {"message": str(e)}
|
||||||
@ -1874,7 +1830,6 @@ class CreateNode(Schema):
|
|||||||
nodeName: str | None = None
|
nodeName: str | None = None
|
||||||
nodeReason: str | None = None
|
nodeReason: str | None = None
|
||||||
nodeDepth: str | None = None
|
nodeDepth: str | None = None
|
||||||
pesLink: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
@ -1886,43 +1841,14 @@ def add_pathway_node(request, package_uuid, pathway_uuid, n: Form[CreateNode]):
|
|||||||
p = get_package_for_write(request.user, package_uuid)
|
p = get_package_for_write(request.user, package_uuid)
|
||||||
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
pw = Pathway.objects.get(package=p, uuid=pathway_uuid)
|
||||||
|
|
||||||
if n.pesLink:
|
if n.nodeDepth is not None and n.nodeDepth.strip() != "":
|
||||||
from bayer.views import fetch_pes
|
node_depth = int(n.nodeDepth)
|
||||||
from bayer.models import PESCompound
|
|
||||||
|
|
||||||
try:
|
|
||||||
pes_data = fetch_pes(request, c.pesLink)
|
|
||||||
except ValueError as e:
|
|
||||||
return 400, {"message": f"Could not fetch PES data for {c.pesLink}"}
|
|
||||||
|
|
||||||
classification = pes_data.get("classificationLevel", "")
|
|
||||||
if "secret" == classification.lower():
|
|
||||||
data_pools = pes_data.get("dataPools")
|
|
||||||
if data_pools:
|
|
||||||
if s.DATA_POOL_MAPPING[p.data_pool.name] not in data_pools:
|
|
||||||
return 400, { "messsage": f"PES data pool {s.DATA_POOL_MAPPING[p.data_pool.name]} not found in PES data"}
|
|
||||||
|
|
||||||
c = PESCompound.create(p, pes_data, c.compoundName, c.compoundDescription)
|
|
||||||
|
|
||||||
node = Node()
|
|
||||||
node.stereo_removed = False
|
|
||||||
node.pathway = pw
|
|
||||||
node.depth = 0
|
|
||||||
|
|
||||||
node.default_node_label = c.default_structure
|
|
||||||
node.save()
|
|
||||||
|
|
||||||
node.node_labels.add(c.default_structure)
|
|
||||||
node.save()
|
|
||||||
else:
|
else:
|
||||||
if n.nodeDepth is not None and n.nodeDepth.strip() != "":
|
node_depth = -1
|
||||||
node_depth = int(n.nodeDepth)
|
|
||||||
else:
|
|
||||||
node_depth = -1
|
|
||||||
|
|
||||||
node = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
|
n = Node.create(pw, n.nodeAsSmiles, node_depth, n.nodeName, n.nodeReason)
|
||||||
|
|
||||||
return redirect(node.url)
|
return redirect(n.url)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 403, {"message": "Adding node failed!"}
|
return 403, {"message": "Adding node failed!"}
|
||||||
|
|
||||||
@ -2074,9 +2000,6 @@ def add_pathway_edge(request, package_uuid, pathway_uuid, e: Form[CreateEdge]):
|
|||||||
description=e.edgeReason,
|
description=e.edgeReason,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update depths as sideeffect of above operation
|
|
||||||
pw.update_depths()
|
|
||||||
|
|
||||||
return redirect(new_e.url)
|
return redirect(new_e.url)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 403, {"message": "Adding Edge failed!"}
|
return 403, {"message": "Adding Edge failed!"}
|
||||||
|
|||||||
@ -1065,9 +1065,52 @@ class PackageManager(object):
|
|||||||
|
|
||||||
print("Fixing Node depths...")
|
print("Fixing Node depths...")
|
||||||
total_pws = Pathway.objects.filter(package=pack).count()
|
total_pws = Pathway.objects.filter(package=pack).count()
|
||||||
|
|
||||||
for p, pw in enumerate(Pathway.objects.filter(package=pack)):
|
for p, pw in enumerate(Pathway.objects.filter(package=pack)):
|
||||||
pw.update_depths()
|
in_count = defaultdict(lambda: 0)
|
||||||
|
out_count = defaultdict(lambda: 0)
|
||||||
|
|
||||||
|
for e in pw.edges:
|
||||||
|
# TODO check if this will remain
|
||||||
|
for react in e.start_nodes.all():
|
||||||
|
out_count[str(react.uuid)] += 1
|
||||||
|
|
||||||
|
for prod in e.end_nodes.all():
|
||||||
|
in_count[str(prod.uuid)] += 1
|
||||||
|
|
||||||
|
root_nodes = []
|
||||||
|
for n in pw.nodes:
|
||||||
|
num_parents = in_count[str(n.uuid)]
|
||||||
|
if num_parents == 0:
|
||||||
|
# must be a root node or unconnected node
|
||||||
|
if n.depth != 0:
|
||||||
|
n.depth = 0
|
||||||
|
n.save()
|
||||||
|
|
||||||
|
# Only root node may have children
|
||||||
|
if out_count[str(n.uuid)] > 0:
|
||||||
|
root_nodes.append(n)
|
||||||
|
|
||||||
|
levels = [root_nodes]
|
||||||
|
seen = set()
|
||||||
|
# Do a bfs to determine depths starting with level 0 a.k.a. root nodes
|
||||||
|
for i, level_nodes in enumerate(levels):
|
||||||
|
new_level = []
|
||||||
|
for n in level_nodes:
|
||||||
|
for e in n.out_edges.all():
|
||||||
|
for prod in e.end_nodes.all():
|
||||||
|
if str(prod.uuid) not in seen:
|
||||||
|
old_depth = prod.depth
|
||||||
|
if old_depth != i + 1:
|
||||||
|
prod.depth = i + 1
|
||||||
|
prod.save()
|
||||||
|
|
||||||
|
new_level.append(prod)
|
||||||
|
|
||||||
|
seen.add(str(n.uuid))
|
||||||
|
|
||||||
|
if new_level:
|
||||||
|
levels.append(new_level)
|
||||||
|
|
||||||
print(f"{p + 1}/{total_pws} fixed.", end="\r")
|
print(f"{p + 1}/{total_pws} fixed.", end="\r")
|
||||||
|
|
||||||
return pack
|
return pack
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
# Generated by Django 6.0.3 on 2026-05-11 20:25
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
from envipy_additional_information import HalfLife, HalfLifeModel, HalfLifeWS
|
|
||||||
|
|
||||||
MAPPING = {
|
|
||||||
"": HalfLifeModel.OTHER,
|
|
||||||
"HS-SFO": HalfLifeModel.HS_SFO,
|
|
||||||
"FOMC": HalfLifeModel.FOMC,
|
|
||||||
"FOTC": HalfLifeModel.DFOP,
|
|
||||||
"FMOC": HalfLifeModel.FOMC,
|
|
||||||
"DFOP": HalfLifeModel.DFOP,
|
|
||||||
"SFO + SFO": HalfLifeModel.SFO_SFO,
|
|
||||||
"FOMC-SFO": HalfLifeModel.FOMC_SFO,
|
|
||||||
"first order kinetics": HalfLifeModel.SFO,
|
|
||||||
"SFO²": HalfLifeModel.SFO,
|
|
||||||
"HS": HalfLifeModel.HS,
|
|
||||||
"top down": HalfLifeModel.OTHER,
|
|
||||||
"SFO": HalfLifeModel.SFO,
|
|
||||||
"First Order": HalfLifeModel.SFO,
|
|
||||||
"SFO/SFO": HalfLifeModel.SFO_SFO,
|
|
||||||
"FOMC + SFO": HalfLifeModel.FOMC_SFO,
|
|
||||||
"true": HalfLifeModel.SFO,
|
|
||||||
"SFO-SFO": HalfLifeModel.SFO_SFO,
|
|
||||||
"DFOP-SFO": HalfLifeModel.DFOP_SFO,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def forward_func(apps, schema_editor):
|
|
||||||
AdditionalInformation = apps.get_model("epdb", "AdditionalInformation")
|
|
||||||
|
|
||||||
hls = AdditionalInformation.objects.filter(type="HalfLife")
|
|
||||||
|
|
||||||
for hl in hls:
|
|
||||||
data = hl.data
|
|
||||||
data["model"] = MAPPING[data["model"]].value
|
|
||||||
hl.data = HalfLife(**data).model_dump(mode="json")
|
|
||||||
hl.save()
|
|
||||||
|
|
||||||
hlws = AdditionalInformation.objects.filter(type="HalfLifeWS")
|
|
||||||
|
|
||||||
for hl in hlws:
|
|
||||||
data = hl.data
|
|
||||||
data["model"] = MAPPING[data["model"]].value
|
|
||||||
hl.data = HalfLifeWS(**data).model_dump(mode="json")
|
|
||||||
hl.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
("epdb", "0024_user_contacted"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RunPython(forward_func, reverse_code=migrations.RunPython.noop),
|
|
||||||
]
|
|
||||||
@ -2186,56 +2186,6 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMix
|
|||||||
):
|
):
|
||||||
return Edge.create(self, start_nodes, end_nodes, rule, name=name, description=description)
|
return Edge.create(self, start_nodes, end_nodes, rule, name=name, description=description)
|
||||||
|
|
||||||
def update_depths(self):
|
|
||||||
# Collect number of in and out links per node
|
|
||||||
in_count = defaultdict(lambda: 0)
|
|
||||||
out_count = defaultdict(lambda: 0)
|
|
||||||
|
|
||||||
for e in self.edges:
|
|
||||||
for react in e.start_nodes.all():
|
|
||||||
out_count[str(react.uuid)] += 1
|
|
||||||
|
|
||||||
for prod in e.end_nodes.all():
|
|
||||||
in_count[str(prod.uuid)] += 1
|
|
||||||
|
|
||||||
depth_map = {}
|
|
||||||
depth_map[0] = list()
|
|
||||||
|
|
||||||
for n in self.nodes:
|
|
||||||
num_parents = in_count[str(n.uuid)]
|
|
||||||
if num_parents == 0:
|
|
||||||
# must be a root node or unconnected node
|
|
||||||
if n.depth != 0:
|
|
||||||
n.depth = 0
|
|
||||||
n.save()
|
|
||||||
|
|
||||||
# Only root node may have children
|
|
||||||
if out_count[str(n.uuid)] > 0:
|
|
||||||
depth_map[0].append(n)
|
|
||||||
|
|
||||||
# At most depth len(nodes) is possible
|
|
||||||
for i in range(self.nodes.count()):
|
|
||||||
level_nodes = depth_map.get(i, [])
|
|
||||||
|
|
||||||
if len(level_nodes) == 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
unique_next_level = set()
|
|
||||||
for n in level_nodes:
|
|
||||||
for e in self.edges:
|
|
||||||
if n in e.start_nodes.all():
|
|
||||||
for p in e.end_nodes.all():
|
|
||||||
unique_next_level.add(p)
|
|
||||||
|
|
||||||
if len(unique_next_level) > 0:
|
|
||||||
depth_map[i + 1] = list(unique_next_level)
|
|
||||||
|
|
||||||
for depth, nodes in depth_map.items():
|
|
||||||
for n in nodes:
|
|
||||||
if n.depth != depth:
|
|
||||||
n.depth = depth
|
|
||||||
n.save()
|
|
||||||
|
|
||||||
|
|
||||||
class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin):
|
class Node(EnviPathModel, AliasMixin, ScenarioMixin, AdditionalInformationMixin):
|
||||||
pathway = models.ForeignKey(
|
pathway = models.ForeignKey(
|
||||||
|
|||||||
@ -937,14 +937,12 @@ 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():
|
||||||
@ -2539,9 +2537,6 @@ def package_pathway_edges(request, package_uuid, pathway_uuid):
|
|||||||
substrate_nodes, product_nodes, name=edge_name, description=edge_description
|
substrate_nodes, product_nodes, name=edge_name, description=edge_description
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update depths as sideeffect of above operation
|
|
||||||
current_pathway.update_depths()
|
|
||||||
|
|
||||||
return redirect(current_pathway.url)
|
return redirect(current_pathway.url)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -46,7 +46,7 @@ class PepperPrediction(PropertyPrediction):
|
|||||||
|
|
||||||
import matplotlib.patches as mpatches
|
import matplotlib.patches as mpatches
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from matplotlib.figure import Figure
|
from matplotlib import pyplot as plt
|
||||||
from scipy import stats
|
from scipy import stats
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -101,8 +101,7 @@ class PepperPrediction(PropertyPrediction):
|
|||||||
mask_red = x > vp
|
mask_red = x > vp
|
||||||
|
|
||||||
# Plot
|
# Plot
|
||||||
fig = Figure(figsize=(9, 5.5))
|
fig, ax = plt.subplots(figsize=(9, 5.5))
|
||||||
ax = fig.subplots()
|
|
||||||
ax.plot(x, y, color="#1f4e79", lw=2, label="Lognormal PDF")
|
ax.plot(x, y, color="#1f4e79", lw=2, label="Lognormal PDF")
|
||||||
|
|
||||||
if np.any(mask_green):
|
if np.any(mask_green):
|
||||||
@ -147,12 +146,13 @@ class PepperPrediction(PropertyPrediction):
|
|||||||
]
|
]
|
||||||
ax.legend(handles=patches, frameon=True)
|
ax.legend(handles=patches, frameon=True)
|
||||||
|
|
||||||
fig.tight_layout()
|
plt.tight_layout()
|
||||||
|
|
||||||
# --- Export to SVG string ---
|
# --- Export to SVG string ---
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
fig.savefig(buf, format="svg", bbox_inches="tight")
|
fig.savefig(buf, format="svg", bbox_inches="tight")
|
||||||
svg = buf.getvalue()
|
svg = buf.getvalue()
|
||||||
|
plt.close(fig)
|
||||||
buf.close()
|
buf.close()
|
||||||
|
|
||||||
return svg
|
return svg
|
||||||
|
|||||||
@ -187,9 +187,8 @@ class Pepper:
|
|||||||
groups = [group for group in dataset.group_by("structure_id")]
|
groups = [group for group in dataset.group_by("structure_id")]
|
||||||
|
|
||||||
# Unless explicitly set compute everything serial
|
# Unless explicitly set compute everything serial
|
||||||
n_threads = int(os.environ.get("N_PEPPER_THREADS", 1))
|
if os.environ.get("N_PEPPER_THREADS", 1) > 1:
|
||||||
if n_threads > 1:
|
results = Parallel(n_jobs=os.environ["N_PEPPER_THREADS"])(
|
||||||
results = Parallel(n_jobs=n_threads)(
|
|
||||||
delayed(compute_bayes_per_group)(group[1])
|
delayed(compute_bayes_per_group)(group[1])
|
||||||
for group in dataset.group_by("structure_id")
|
for group in dataset.group_by("structure_id")
|
||||||
)
|
)
|
||||||
|
|||||||
@ -605,36 +605,7 @@ function draw(pathway, elem) {
|
|||||||
// Check if target is pseudo and draw marker only if not pseudo
|
// Check if target is pseudo and draw marker only if not pseudo
|
||||||
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
.attr("class", d => d.target.pseudo ? "link_no_arrow" : "link")
|
||||||
.attr("marker-end", d => d.target.pseudo ? '' : d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)')
|
.attr("marker-end", d => d.target.pseudo ? '' : d.multi_step ? 'url(#doublearrow)' : 'url(#arrow)')
|
||||||
.on("click", function(event, d) {
|
|
||||||
const wasHighlighted = d3.select(this).classed("highlighted");
|
|
||||||
|
|
||||||
d3.selectAll("line").classed("highlighted", false);
|
|
||||||
|
|
||||||
if (!wasHighlighted) {
|
|
||||||
const toHighlight = [];
|
|
||||||
toHighlight.push(d.el);
|
|
||||||
|
|
||||||
if (d.source.pseudo || d.target.pseudo) {
|
|
||||||
if (d.target.pseudo) {
|
|
||||||
d3.selectAll("line").each(e => {
|
|
||||||
if (e !== undefined && e.source.id === d.target.id) {
|
|
||||||
toHighlight.push(e.el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
d3.selectAll("line").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) {
|
||||||
@ -653,13 +624,7 @@ function draw(pathway, elem) {
|
|||||||
.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.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
|
||||||
|
|
||||||
d3.selectAll('circle.highlighted').classed('highlighted', false);
|
|
||||||
|
|
||||||
if (!wasHighlighted) {
|
|
||||||
d3.select(this).select("circle").classed("highlighted", !d3.select(this).select("circle").classed("highlighted"));
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Kreise für die Knoten hinzufügen
|
// Kreise für die Knoten hinzufügen
|
||||||
|
|||||||
@ -9,6 +9,14 @@
|
|||||||
<i class="glyphicon glyphicon-plus"></i> Add Compound</a
|
<i class="glyphicon glyphicon-plus"></i> Add Compound</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
class="button"
|
||||||
|
onclick="document.getElementById('add_pathway_pes_node_modal').showModal(); return false;"
|
||||||
|
>
|
||||||
|
<i class="glyphicon glyphicon-plus"></i> Add PES</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
@ -22,7 +30,7 @@
|
|||||||
{% for tpl in action_button_templates %}
|
{% for tpl in action_button_templates %}
|
||||||
{% include tpl %}
|
{% include tpl %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li role="separator" class="divider h-px"></li>
|
<li role="separator" class="divider"></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@ -74,7 +82,7 @@
|
|||||||
Rules</a
|
Rules</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li role="separator" class="divider h-px"></li>
|
<li role="separator" class="divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
@ -99,16 +107,11 @@
|
|||||||
<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 h-px"></li>
|
<li role="separator" class="divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
onclick="
|
onclick="document.getElementById('delete_pathway_node_modal').showModal(); return false;"
|
||||||
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
|
||||||
>
|
>
|
||||||
@ -116,12 +119,7 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
class="button"
|
class="button"
|
||||||
onclick="
|
onclick="document.getElementById('delete_pathway_edge_modal').showModal(); return false;"
|
||||||
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
|
||||||
>
|
>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
{% if meta.can_edit or not meta.url_contains_package %}
|
{% if meta.can_edit %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -3,16 +3,14 @@
|
|||||||
{% block page_title %}Models{% endblock %}
|
{% block page_title %}Models{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit or not meta.url_contains_package %}
|
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
|
||||||
{% if meta.enabled_features.MODEL_BUILDING %}
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
class="btn btn-primary btn-sm"
|
||||||
class="btn btn-primary btn-sm"
|
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
||||||
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
>
|
||||||
>
|
New Model
|
||||||
New Model
|
</button>
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock action_button %}
|
{% endblock action_button %}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
{% block page_title %}Packages{% endblock %}
|
{% block page_title %}Packages{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
|
{% if meta.can_edit %}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -70,6 +71,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock action_button %}
|
{% endblock action_button %}
|
||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{% block page_title %}Pathways{% endblock %}
|
{% block page_title %}Pathways{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit or not meta.url_contains_package %}
|
{% if meta.can_edit %}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<a
|
<a
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{% block page_title %}Reactions{% endblock %}
|
{% block page_title %}Reactions{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit or not meta.url_contains_package %}
|
{% if meta.can_edit %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{% block page_title %}Rules{% endblock %}
|
{% block page_title %}Rules{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit or not meta.url_contains_package %}
|
{% if meta.can_edit %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
{% endblock action_modals %}
|
{% endblock action_modals %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit or not meta.url_contains_package %}
|
{% if meta.can_edit %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{% block page_title %}{{ page_title|default:"Structures" }}{% endblock %}
|
{% block page_title %}{{ page_title|default:"Structures" }}{% endblock %}
|
||||||
|
|
||||||
{% block action_button %}
|
{% block action_button %}
|
||||||
{% if meta.can_edit or not meta.url_contains_package %}
|
{% if meta.can_edit %}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-primary btn-sm"
|
class="btn btn-primary btn-sm"
|
||||||
|
|||||||
@ -203,11 +203,11 @@
|
|||||||
id="model-based-prediction-setting-threshold"
|
id="model-based-prediction-setting-threshold"
|
||||||
name="model-based-prediction-setting-threshold"
|
name="model-based-prediction-setting-threshold"
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
value="0.25"
|
placeholder="0.25"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
max="1"
|
max="1"
|
||||||
step="any"
|
step="0.05"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -4,25 +4,6 @@
|
|||||||
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('line.highlighted');
|
|
||||||
console.log(links);
|
|
||||||
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,22 +4,6 @@
|
|||||||
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">
|
||||||
|
|||||||
@ -77,6 +77,7 @@
|
|||||||
|
|
||||||
{% block action_modals %}
|
{% block action_modals %}
|
||||||
{% include "modals/objects/add_pathway_node_modal.html" %}
|
{% include "modals/objects/add_pathway_node_modal.html" %}
|
||||||
|
{% include "modals/objects/add_pathway_pes_node_modal.html" %}
|
||||||
{% include "modals/objects/add_pathway_edge_modal.html" %}
|
{% include "modals/objects/add_pathway_edge_modal.html" %}
|
||||||
{% epdb_slot_templates "epdb.modals.objects.pathway.add" as add_templates %}
|
{% epdb_slot_templates "epdb.modals.objects.pathway.add" as add_templates %}
|
||||||
{% for tpl in add_templates %}
|
{% for tpl in add_templates %}
|
||||||
@ -106,12 +107,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Graphical Representation -->
|
<!-- Graphical Representation -->
|
||||||
<div class="collapse-arrow bg-base-200 collapse overflow-y-auto">
|
<div class="collapse-arrow bg-base-200 collapse">
|
||||||
<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
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content ">
|
<div class="collapse-content">
|
||||||
<div class="bg-base-100 mb-2 rounded-lg p-2">
|
<div class="bg-base-100 mb-2 rounded-lg p-2">
|
||||||
<div class="navbar bg-base-100 rounded-lg">
|
<div class="navbar bg-base-100 rounded-lg">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
@ -140,7 +141,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content menu bg-base-100 rounded-box z-50 w-96 p-2"
|
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
|
||||||
>
|
>
|
||||||
{% include "actions/objects/pathway.html" %}
|
{% include "actions/objects/pathway.html" %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from envipy_additional_information import HalfLife, HalfLifeWS, HalfLifeModel
|
from envipy_additional_information import HalfLife, HalfLifeWS
|
||||||
from envipy_additional_information.information import Interval
|
from envipy_additional_information.information import Interval
|
||||||
from envipy_additional_information.parsers import (
|
from envipy_additional_information.parsers import (
|
||||||
AcidityParser,
|
AcidityParser,
|
||||||
@ -473,12 +473,17 @@ def build_additional_information_from_request(request, type_):
|
|||||||
|
|
||||||
comment = get_parameter_or_empty_string(request, "comment")
|
comment = get_parameter_or_empty_string(request, "comment")
|
||||||
source = get_parameter_or_empty_string(request, "source")
|
source = get_parameter_or_empty_string(request, "source")
|
||||||
# first_order = get_parameter_or_empty_string(request, "firstOrder")
|
first_order = get_parameter_or_empty_string(request, "firstOrder")
|
||||||
model = get_parameter_or_empty_string(request, "model")
|
model = get_parameter_or_empty_string(request, "model")
|
||||||
fit = get_parameter_or_empty_string(request, "fit")
|
fit = get_parameter_or_empty_string(request, "fit")
|
||||||
|
|
||||||
if model:
|
if first_order != "":
|
||||||
model = HalfLifeModel(model.upper())
|
if model != "":
|
||||||
|
raise ValueError("not both, model and firstOrder can be set!")
|
||||||
|
if first_order == "true":
|
||||||
|
model = "SFO"
|
||||||
|
else:
|
||||||
|
logger.info("firstOrder is set to false which is not meaningful")
|
||||||
|
|
||||||
return HalfLife(model=model, fit=fit, comment=comment, dt50=i, source=source)
|
return HalfLife(model=model, fit=fit, comment=comment, dt50=i, source=source)
|
||||||
|
|
||||||
@ -503,10 +508,6 @@ def build_additional_information_from_request(request, type_):
|
|||||||
comment_ws = get_parameter_or_empty_string(request, "comment_ws")
|
comment_ws = get_parameter_or_empty_string(request, "comment_ws")
|
||||||
source_ws = get_parameter_or_empty_string(request, "source_ws")
|
source_ws = get_parameter_or_empty_string(request, "source_ws")
|
||||||
model_ws = get_parameter_or_empty_string(request, "model_ws")
|
model_ws = get_parameter_or_empty_string(request, "model_ws")
|
||||||
|
|
||||||
if model_ws:
|
|
||||||
model_ws = HalfLifeModel(model_ws.upper())
|
|
||||||
|
|
||||||
fit_ws = get_parameter_or_empty_string(request, "fit_ws")
|
fit_ws = get_parameter_or_empty_string(request, "fit_ws")
|
||||||
|
|
||||||
dt50_total = IntervalParser.from_string(hl_ws_total)
|
dt50_total = IntervalParser.from_string(hl_ws_total)
|
||||||
|
|||||||
34
uv.lock
generated
34
uv.lock
generated
@ -894,7 +894,7 @@ provides-extras = ["ms-login", "dev", "pepper-plugin"]
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "envipy-additional-information"
|
name = "envipy-additional-information"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
source = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git?branch=develop#676dae1c5678539beac637b87e49b9dadfdfd85a" }
|
source = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git?branch=develop#0a608c85c73a6ef5c38afea87d2b57fb43f01a70" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "pydantic" },
|
{ name = "pydantic" },
|
||||||
]
|
]
|
||||||
@ -2763,9 +2763,9 @@ dependencies = [
|
|||||||
{ name = "typing-extensions", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
{ name = "typing-extensions", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:a47b7986bee3f61ad217d8a8ce24605809ab425baf349f97de758815edd2ef54", upload-time = "2025-10-01T23:35:50Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:a47b7986bee3f61ad217d8a8ce24605809ab425baf349f97de758815edd2ef54" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:fbe2e149c5174ef90d29a5f84a554dfaf28e003cb4f61fa2c8c024c17ec7ca58", upload-time = "2025-10-01T23:35:52Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:fbe2e149c5174ef90d29a5f84a554dfaf28e003cb4f61fa2c8c024c17ec7ca58" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:057efd30a6778d2ee5e2374cd63a63f63311aa6f33321e627c655df60abdd390", upload-time = "2025-10-01T23:35:55Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:057efd30a6778d2ee5e2374cd63a63f63311aa6f33321e627c655df60abdd390" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2785,19 +2785,19 @@ dependencies = [
|
|||||||
{ name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
{ name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||||
]
|
]
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:0e34e276722ab7dd0dffa9e12fe2135a9b34a0e300c456ed7ad6430229404eb5", upload-time = "2025-10-01T23:33:41Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:0e34e276722ab7dd0dffa9e12fe2135a9b34a0e300c456ed7ad6430229404eb5" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:610f600c102386e581327d5efc18c0d6edecb9820b4140d26163354a99cd800d", upload-time = "2025-10-01T23:33:45Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:610f600c102386e581327d5efc18c0d6edecb9820b4140d26163354a99cd800d" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:cb9a8ba8137ab24e36bf1742cb79a1294bd374db570f09fc15a5e1318160db4e", upload-time = "2025-10-01T23:33:48Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:cb9a8ba8137ab24e36bf1742cb79a1294bd374db570f09fc15a5e1318160db4e" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:2be20b2c05a0cce10430cc25f32b689259640d273232b2de357c35729132256d", upload-time = "2025-10-01T23:33:52Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:2be20b2c05a0cce10430cc25f32b689259640d273232b2de357c35729132256d" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:99fc421a5d234580e45957a7b02effbf3e1c884a5dd077afc85352c77bf41434", upload-time = "2025-10-01T23:34:10Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp312-cp312-win_arm64.whl", hash = "sha256:99fc421a5d234580e45957a7b02effbf3e1c884a5dd077afc85352c77bf41434" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:8b5882276633cf91fe3d2d7246c743b94d44a7e660b27f1308007fdb1bb89f7d", upload-time = "2025-10-01T23:34:15Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:8b5882276633cf91fe3d2d7246c743b94d44a7e660b27f1308007fdb1bb89f7d" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a5064b5e23772c8d164068cc7c12e01a75faf7b948ecd95a0d4007d7487e5f25", upload-time = "2025-10-01T23:34:19Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a5064b5e23772c8d164068cc7c12e01a75faf7b948ecd95a0d4007d7487e5f25" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8f81dedb4c6076ec325acc3b47525f9c550e5284a18eae1d9061c543f7b6e7de", upload-time = "2025-10-01T23:34:23Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8f81dedb4c6076ec325acc3b47525f9c550e5284a18eae1d9061c543f7b6e7de" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:e1ee1b2346ade3ea90306dfbec7e8ff17bc220d344109d189ae09078333b0856", upload-time = "2025-10-01T23:34:28Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:e1ee1b2346ade3ea90306dfbec7e8ff17bc220d344109d189ae09078333b0856" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:64c187345509f2b1bb334feed4666e2c781ca381874bde589182f81247e61f88", upload-time = "2025-10-01T23:34:45Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313-win_arm64.whl", hash = "sha256:64c187345509f2b1bb334feed4666e2c781ca381874bde589182f81247e61f88" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:af81283ac671f434b1b25c95ba295f270e72db1fad48831eb5e4748ff9840041", upload-time = "2025-10-01T23:34:50Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:af81283ac671f434b1b25c95ba295f270e72db1fad48831eb5e4748ff9840041" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:a9dbb6f64f63258bc811e2c0c99640a81e5af93c531ad96e95c5ec777ea46dab", upload-time = "2025-10-01T23:34:53Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:a9dbb6f64f63258bc811e2c0c99640a81e5af93c531ad96e95c5ec777ea46dab" },
|
||||||
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:6d93a7165419bc4b2b907e859ccab0dea5deeab261448ae9a5ec5431f14c0e64", upload-time = "2025-10-01T23:34:58Z" },
|
{ url = "https://download-r2.pytorch.org/whl/cpu/torch-2.8.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:6d93a7165419bc4b2b907e859ccab0dea5deeab261448ae9a5ec5431f14c0e64" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user