forked from enviPath/enviPy
develop-bayer #1
@ -30,12 +30,13 @@ RUN mkdir -p -m 0700 /root/.ssh \
|
||||
&& 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:
|
||||
# docker build --ssh default -t envipath/envipy:1.0 .
|
||||
# docker build --ssh default -t envipath/envipy-bayer:1.0 .
|
||||
RUN --mount=type=ssh \
|
||||
uv sync --locked --extra ms-login --extra pepper-plugin
|
||||
|
||||
# Now copy source and do a final sync to install the project itself
|
||||
# Ensure .dockerignore is reasonable
|
||||
COPY bb4g bb4g
|
||||
COPY biotransformer biotransformer
|
||||
COPY bayer bayer
|
||||
COPY bridge bridge
|
||||
|
||||
@ -103,20 +103,15 @@ def create_pes_node(request, package_uuid, pathway_uuid):
|
||||
|
||||
|
||||
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
|
||||
token = get_access_token_from_request(request)
|
||||
|
||||
if token or True:
|
||||
if token:
|
||||
for k, v in s.PES_API_MAPPING.items():
|
||||
if pes_url.startswith(k):
|
||||
pes_id = pes_url.split('/')[-1]
|
||||
|
||||
if pes_id == 'dummy' or True:
|
||||
if pes_id == 'dummy':
|
||||
import json
|
||||
res_data = json.load(open(s.BASE_DIR / "fixtures/pes.json"))
|
||||
res_data["pes_url"] = pes_url
|
||||
@ -125,7 +120,7 @@ def fetch_pes(request, pes_url) -> dict:
|
||||
headers = {"Authorization": f"Bearer {token['access_token']}"}
|
||||
params = {"pes_reg_entity_corporate_id": pes_id}
|
||||
|
||||
res = requests.get(v, headers=headers, params=params, proxies=proxies)
|
||||
res = requests.get(v, headers=headers, params=params, proxies=s.PROXIES or None)
|
||||
|
||||
try:
|
||||
res.raise_for_status()
|
||||
|
||||
@ -2,9 +2,11 @@ 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 (
|
||||
@ -15,12 +17,36 @@ from bridge.dto import (
|
||||
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 = None
|
||||
Config = BB4GConfig
|
||||
|
||||
def __init__(self, config=None):
|
||||
def __init__(self, config: BB4GConfig | None = None):
|
||||
super().__init__(config)
|
||||
self.url = f"{s.BB4G_URL}"
|
||||
|
||||
@ -29,10 +55,6 @@ class BB4G(Classifier):
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
self.proxies = {
|
||||
"http": s.HTTP_PROXY,
|
||||
"https": s.HTTPS_PROXY,
|
||||
}
|
||||
|
||||
def acquire_token(self):
|
||||
BB4G_TENANT_ID = s.BB4G_TENANT_ID
|
||||
@ -65,7 +87,7 @@ class BB4G(Classifier):
|
||||
started = False
|
||||
retries = 0
|
||||
while not started and retries < 5:
|
||||
res = requests.post(f"{self.url}/start", headers=header, data={}, proxies=self.proxies)
|
||||
res = requests.post(f"{self.url}/start", headers=header, data={}, proxies=s.PROXIES or None)
|
||||
|
||||
if res.status_code == 200:
|
||||
started = True
|
||||
@ -140,11 +162,11 @@ class BB4G(Classifier):
|
||||
for smi in smiles:
|
||||
data = {
|
||||
"smiles": smi,
|
||||
"sampling_alg": "exact",
|
||||
"cutoff": -5,
|
||||
"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=self.proxies)
|
||||
resp = requests.post(f"{self.url}/compute", headers=header, data=json.dumps(data), proxies=s.PROXIES or None)
|
||||
|
||||
resp.raise_for_status()
|
||||
|
||||
|
||||
@ -254,7 +254,15 @@ class Classifier(Plugin):
|
||||
def parse_config(cls, data: dict | None = None) -> EnviPyModel | None:
|
||||
if cls.Config is 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
|
||||
def create(cls, data: dict | None = None):
|
||||
|
||||
@ -143,6 +143,12 @@ if os.environ.get("USE_TEMPLATE_DB", False) == "True":
|
||||
"TEMPLATE": os.environ["TEMPLATE_DB"],
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"LOCATION": "unique-snowflake",
|
||||
}
|
||||
}
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||
@ -474,3 +480,14 @@ if DATA_POOL_MAPPING:
|
||||
else:
|
||||
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,4 +5,5 @@ from . import views
|
||||
urlpatterns = [
|
||||
path("entra/login/", views.entra_login, name="entra_login"),
|
||||
path("auth/redirect/", views.entra_callback, name="entra_callback"),
|
||||
path("auth/token/", views.get_token, name="get_token"),
|
||||
]
|
||||
|
||||
@ -2,9 +2,11 @@ import msal
|
||||
from django.conf import settings as s
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth import login
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from epdb.logic import UserManager
|
||||
from epdb.logic import UserManager, GroupManager
|
||||
from epdb.models import Group
|
||||
|
||||
|
||||
def get_msal_app_with_cache(request):
|
||||
@ -81,10 +83,12 @@ def entra_callback(request):
|
||||
login(request, u)
|
||||
|
||||
# EDIT START
|
||||
|
||||
# Ensure groups exists in eP
|
||||
for id, name in s.ENTRA_SECRET_GROUPS.items():
|
||||
if not Group.objects.filter(uuid=id).exists():
|
||||
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ", uuid=id)
|
||||
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ",
|
||||
uuid=id)
|
||||
else:
|
||||
g = Group.objects.get(uuid=id)
|
||||
# Ensure its secret
|
||||
@ -93,7 +97,8 @@ def entra_callback(request):
|
||||
|
||||
for id, name in s.ENTRA_GROUPS.items():
|
||||
if not Group.objects.filter(uuid=id).exists():
|
||||
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ", uuid=id)
|
||||
g = GroupManager.create_group(User.objects.get(username="admin"), name, f"Synced Entra Group {name} ",
|
||||
uuid=id)
|
||||
else:
|
||||
g = Group.objects.get(uuid=id)
|
||||
|
||||
@ -152,3 +157,9 @@ def get_access_token_from_request(request, scopes=None):
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_token(request):
|
||||
token = get_access_token_from_request(request)
|
||||
msg = f"{token}"
|
||||
return HttpResponse(msg, content_type='text/plain')
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
import hashlib
|
||||
from collections import defaultdict
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import jwt
|
||||
import requests
|
||||
|
||||
import nh3
|
||||
import requests
|
||||
from django.conf import settings as s
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.cache import cache
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import redirect
|
||||
from jwt import InvalidIssuerError
|
||||
from ninja import Field, Form, Query, Router, Schema
|
||||
from ninja.errors import HttpError
|
||||
from ninja.security import HttpBearer
|
||||
from ninja.security import SessionAuth
|
||||
|
||||
from utilities.chem import FormatConverter
|
||||
from utilities.misc import PackageExporter
|
||||
@ -51,6 +49,26 @@ from .models import (
|
||||
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):
|
||||
p = PackageManager.get_package_by_id(user, package_uuid)
|
||||
if not PackageManager.writable(user, p):
|
||||
@ -68,9 +86,7 @@ def validate_token(token: str) -> dict:
|
||||
TENANT_ID = s.MS_ENTRA_TENANT_ID
|
||||
CLIENT_ID = s.MS_ENTRA_CLIENT_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()
|
||||
jwks = get_cached_jwks(TENANT_ID)
|
||||
|
||||
header = jwt.get_unverified_header(token)
|
||||
|
||||
@ -78,13 +94,21 @@ def validate_token(token: str) -> dict:
|
||||
next(k for k in jwks["keys"] if k["kid"] == header["kid"])
|
||||
)
|
||||
|
||||
claims = jwt.decode(
|
||||
token,
|
||||
public_key,
|
||||
algorithms=["RS256"],
|
||||
audience=[CLIENT_ID, f"api://{CLIENT_ID}"],
|
||||
issuer=f"https://sts.windows.net/{TENANT_ID}/",
|
||||
)
|
||||
# Handle V1 and V2 tokens
|
||||
try:
|
||||
claims = jwt.decode(
|
||||
token,
|
||||
public_key,
|
||||
algorithms=["RS256"],
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -937,12 +937,14 @@ def package_models(request, package_uuid):
|
||||
"requires_rule_packages": True,
|
||||
"requires_data_packages": True,
|
||||
},
|
||||
"EnviFormer": {
|
||||
}
|
||||
|
||||
if s.ENVIFORMER_PRESENT:
|
||||
context["model_types"]["EnviFormer"] = {
|
||||
"type": "enviformer",
|
||||
"requires_rule_packages": False,
|
||||
"requires_data_packages": True,
|
||||
},
|
||||
}
|
||||
|
||||
if s.FLAGS.get("PLUGINS", False):
|
||||
for k, v in s.CLASSIFIER_PLUGINS.items():
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
{% block action_button %}
|
||||
<div class="flex items-center gap-2">
|
||||
{% if meta.can_edit %}
|
||||
{% if meta.can_edit or not meta.url_contains_package %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
|
||||
@ -3,14 +3,16 @@
|
||||
{% block page_title %}Models{% endblock %}
|
||||
|
||||
{% block action_button %}
|
||||
{% if meta.can_edit and meta.enabled_features.MODEL_BUILDING %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
||||
>
|
||||
New Model
|
||||
</button>
|
||||
{% if meta.can_edit or not meta.url_contains_package %}
|
||||
{% if meta.enabled_features.MODEL_BUILDING %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
onclick="document.getElementById('new_model_modal').showModal(); return false;"
|
||||
>
|
||||
New Model
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock action_button %}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
{% block page_title %}Packages{% endblock %}
|
||||
|
||||
{% block action_button %}
|
||||
{% if meta.can_edit %}
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@ -71,7 +70,6 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock action_button %}
|
||||
|
||||
{% block action_modals %}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
{% block page_title %}Pathways{% endblock %}
|
||||
|
||||
{% block action_button %}
|
||||
{% if meta.can_edit %}
|
||||
{% if meta.can_edit or not meta.url_contains_package %}
|
||||
<div class="flex items-center gap-2">
|
||||
<a
|
||||
class="btn btn-primary btn-sm"
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
{% block page_title %}Reactions{% endblock %}
|
||||
|
||||
{% block action_button %}
|
||||
{% if meta.can_edit %}
|
||||
{% if meta.can_edit or not meta.url_contains_package %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
{% block page_title %}Rules{% endblock %}
|
||||
|
||||
{% block action_button %}
|
||||
{% if meta.can_edit %}
|
||||
{% if meta.can_edit or not meta.url_contains_package %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
{% endblock action_modals %}
|
||||
|
||||
{% block action_button %}
|
||||
{% if meta.can_edit %}
|
||||
{% if meta.can_edit or not meta.url_contains_package %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
{% block page_title %}{{ page_title|default:"Structures" }}{% endblock %}
|
||||
|
||||
{% block action_button %}
|
||||
{% if meta.can_edit %}
|
||||
{% if meta.can_edit or not meta.url_contains_package %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm"
|
||||
|
||||
Reference in New Issue
Block a user