diff --git a/pephub/_version.py b/pephub/_version.py index 1748769b..5a313cc7 100644 --- a/pephub/_version.py +++ b/pephub/_version.py @@ -1 +1 @@ -__version__ = "0.15.8" +__version__ = "0.16.0" diff --git a/pephub/const.py b/pephub/const.py index 739fdbba..fc03c944 100644 --- a/pephub/const.py +++ b/pephub/const.py @@ -6,18 +6,20 @@ import pandas as pd from fastapi import __version__ as fastapi_version from pepdbagent import __version__ as pepdbagent_version -from peppy import __version__ as peppy_version -from peppy.const import PEP_LATEST_VERSION from ._version import __version__ as pephub_version +# peprs has no __version__ attribute and no PEP_LATEST_VERSION constant. +PEP_LATEST_VERSION = "2.1.0" +peprs_version = "unknown" + PKG_NAME = "pephub" DATA_REPO = "https://github.com/pepkit/data.pephub.git" ALL_VERSIONS = { "pephub_version": pephub_version, - "peppy_version": peppy_version, + "peprs_version": peprs_version, "python_version": python_version(), "fastapi_version": fastapi_version, "pepdbagent_version": pepdbagent_version, diff --git a/pephub/helpers.py b/pephub/helpers.py index a1f13613..9247b4aa 100644 --- a/pephub/helpers.py +++ b/pephub/helpers.py @@ -9,16 +9,18 @@ import json from fastapi import Response, UploadFile from fastapi.exceptions import HTTPException -from peppy.const import ( - CFG_SAMPLE_TABLE_KEY, - CFG_SUBSAMPLE_TABLE_KEY, +from peprs.const import ( CONFIG_KEY, - NAME_KEY, SAMPLE_RAW_DICT_KEY, - SUBSAMPLE_RAW_LIST_KEY, + SUBSAMPLE_RAW_DICT_KEY, ) from .const import JWT_EXPIRATION, JWT_SECRET +# peprs.const does not export these — they are PEP config schema strings. +CFG_SAMPLE_TABLE_KEY = "sample_table" +CFG_SUBSAMPLE_TABLE_KEY = "subsample_table" +NAME_KEY = "name" + def jwt_encode_user_data(user_data: dict, exp: datetime = None) -> str: """ @@ -53,15 +55,15 @@ def zip_pep(project: Dict[str, Any]) -> Response: project[SAMPLE_RAW_DICT_KEY] ).to_csv(index=False) - if project[SUBSAMPLE_RAW_LIST_KEY] is not None: - if not isinstance(project[SUBSAMPLE_RAW_LIST_KEY], list): + if project[SUBSAMPLE_RAW_DICT_KEY] is not None: + if not isinstance(project[SUBSAMPLE_RAW_DICT_KEY], list): config[CFG_SUBSAMPLE_TABLE_KEY] = ["subsample_table1.csv"] content_to_zip["subsample_table1.csv"] = pd.DataFrame( - project[SUBSAMPLE_RAW_LIST_KEY] + project[SUBSAMPLE_RAW_DICT_KEY] ).to_csv(index=False) else: config[CFG_SUBSAMPLE_TABLE_KEY] = [] - for number, file in enumerate(project[SUBSAMPLE_RAW_LIST_KEY]): + for number, file in enumerate(project[SUBSAMPLE_RAW_DICT_KEY]): file_name = f"subsample_table{number + 1}.csv" config[CFG_SUBSAMPLE_TABLE_KEY].append(file_name) content_to_zip[file_name] = pd.DataFrame(file).to_csv(index=False) diff --git a/pephub/main.py b/pephub/main.py index 7b9bd491..6dc91cc1 100644 --- a/pephub/main.py +++ b/pephub/main.py @@ -28,12 +28,12 @@ fmt="[%(levelname)s] [%(asctime)s] [PEPDBAGENT] %(message)s", ) -_LOGGER_PEPPY = logging.getLogger("peppy") +_LOGGER_PEPRS = logging.getLogger("peprs") coloredlogs.install( - logger=_LOGGER_PEPPY, + logger=_LOGGER_PEPRS, level=logging.ERROR, datefmt="%b %d %Y %H:%M:%S", - fmt="[%(levelname)s] [%(asctime)s] [PEPPY] %(message)s", + fmt="[%(levelname)s] [%(asctime)s] [PEPRS] %(message)s", ) _LOGGER_PEPHUB = logging.getLogger("uvicorn.access") diff --git a/pephub/routers/api/v1/helpers.py b/pephub/routers/api/v1/helpers.py index 1610a249..70eae68a 100644 --- a/pephub/routers/api/v1/helpers.py +++ b/pephub/routers/api/v1/helpers.py @@ -1,16 +1,14 @@ import logging -import eido -from eido.validation import validate_config -from eido.exceptions import EidoValidationError -import peppy +import peprs import yaml from fastapi.exceptions import HTTPException -from peppy import Project -from peppy.const import ( +from peprs import Project +from peprs.eido import EidoValidationError, validate_config, validate_project +from peprs.const import ( CONFIG_KEY, SAMPLE_RAW_DICT_KEY, - SUBSAMPLE_RAW_LIST_KEY, + SUBSAMPLE_RAW_DICT_KEY, ) from ....dependencies import ( get_db, @@ -22,7 +20,7 @@ DEFAULT_SCHEMA_VERSION = "2.1.0" -async def verify_updated_project(updated_project) -> peppy.Project: +async def verify_updated_project(updated_project) -> peprs.Project: new_raw_project = {} agent = get_db() @@ -37,43 +35,23 @@ async def verify_updated_project(updated_project) -> peppy.Project: status_code=400, detail="Please provide a sample table and project config yaml to update project", ) - try: - validate_config( - yaml.safe_load(updated_project.project_config_yaml), default_schema - ) - except EidoValidationError as e: - raise HTTPException( - status_code=400, - detail=f"Config structure error: {', '.join(list(e.errors_by_type.keys()))}. Please check schema definition and try again.", - ) - # sample table update - new_raw_project[SAMPLE_RAW_DICT_KEY] = updated_project.sample_table try: yaml_dict = yaml.safe_load(updated_project.project_config_yaml) - new_raw_project[CONFIG_KEY] = yaml_dict except yaml.scanner.ScannerError as e: raise HTTPException( status_code=400, detail=f"Could not parse provided yaml. Error: {e}", ) - # sample_table_index_col = yaml_dict.get( - # SAMPLE_TABLE_INDEX_KEY, SAMPLE_NAME_ATTR # default to sample_name - # ) - - # await check_sample_names( - # new_raw_project[SAMPLE_RAW_DICT_KEY], sample_table_index_col - # ) + new_raw_project[CONFIG_KEY] = yaml_dict + new_raw_project[SAMPLE_RAW_DICT_KEY] = updated_project.sample_table # subsample table update if updated_project.subsample_tables is not None: subsamples = list(updated_project.subsample_tables[0][0].values()) - new_raw_project[SUBSAMPLE_RAW_LIST_KEY] = ( - updated_project.subsample_tables - if len(subsamples) > 0 and subsamples[0] - else None - ) + if len(subsamples) > 0 and subsamples[0]: + new_raw_project[SUBSAMPLE_RAW_DICT_KEY] = updated_project.subsample_tables try: new_project = Project.from_dict(new_raw_project) @@ -83,9 +61,19 @@ async def verify_updated_project(updated_project) -> peppy.Project: detail=f"Could not create PEP from provided data. Error: {e}", ) + # peprs.eido.validate_config takes a Project (not a raw dict like eido did), + # so we validate after constructing the Project. + try: + validate_config(new_project, default_schema) + except EidoValidationError as e: + raise HTTPException( + status_code=400, + detail=f"Config structure error: {', '.join(list(e.errors_by_type.keys()))}. Please check schema definition and try again.", + ) + try: # validate project (it will also validate samples) - eido.validate_project(new_project, default_schema) + validate_project(new_project, default_schema) except Exception as _: raise HTTPException( status_code=400, diff --git a/pephub/routers/api/v1/namespace.py b/pephub/routers/api/v1/namespace.py index ac357566..022b60ca 100644 --- a/pephub/routers/api/v1/namespace.py +++ b/pephub/routers/api/v1/namespace.py @@ -3,7 +3,7 @@ from typing import List, Literal, Optional, Union import os -import peppy +import peprs from dotenv import load_dotenv from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, Request from fastapi.responses import JSONResponse @@ -22,10 +22,13 @@ NamespaceStats, TarNamespaceModelReturn, ) -from peppy import Project -from peppy.const import DESC_KEY, NAME_KEY +from peprs import Project from typing_extensions import Annotated +# peprs.const does not export these — they are PEP config schema strings. +NAME_KEY = "name" +DESC_KEY = "description" + from ....const import ( DEFAULT_TAG, ARCHIVE_URL_PATH, @@ -213,7 +216,7 @@ async def create_pep( }, status_code=202, ) - # create a blank peppy.Project object with fake files + # create a blank peprs.Project object with fake files else: raise HTTPException( detail="Project files were not provided", @@ -258,21 +261,23 @@ async def upload_raw_pep( # This configurations needed due to Issue #124 Should be removed in the future project_dict = ProjectRawModel(**project_from_json.pep_dict.dict()) ff = project_dict.model_dump(by_alias=True) - p_project = peppy.Project().from_dict(ff) + p_project = peprs.Project.from_dict(ff) - p_project.namespace = name + # peprs.Project has no `namespace` attribute, so we set the registry name + # in the config and pass `name` separately to agent.project.create. + p_project.name = name p_project.description = description except Exception as e: raise HTTPException( - detail=f"Incorrect raw project was provided. Couldn't initiate peppy object: {e}", + detail=f"Incorrect raw project was provided. Couldn't initiate peprs object: {e}", status_code=417, ) try: agent.project.create( p_project, namespace=namespace, - name=p_project.namespace, + name=name, tag=tag, description=description, is_private=is_private, @@ -282,15 +287,15 @@ async def upload_raw_pep( ) except ProjectUniqueNameError: raise HTTPException( - detail=f"Project '{namespace}/{p_project.namespace}:{tag}' already exists in namespace", + detail=f"Project '{namespace}/{name}:{tag}' already exists in namespace", status_code=400, ) return JSONResponse( content={ "namespace": namespace, - "name": p_project.namespace, + "name": name, "tag": tag, - "registry_path": f"{namespace}/{p_project.namespace}:{tag}", + "registry_path": f"{namespace}/{name}:{tag}", }, status_code=202, ) diff --git a/pephub/routers/api/v1/project.py b/pephub/routers/api/v1/project.py index ea181b92..7ac5d8f6 100644 --- a/pephub/routers/api/v1/project.py +++ b/pephub/routers/api/v1/project.py @@ -1,10 +1,9 @@ import logging from typing import Annotated, Any, Literal, Dict, List, Optional, Union -import eido import numpy as np import pandas as pd -import peppy +import peprs import yaml from dotenv import load_dotenv from fastapi import APIRouter, Body, Depends, Query @@ -29,7 +28,7 @@ ProjectViews, HistoryAnnotationModel, ) -from peppy.const import SAMPLE_RAW_DICT_KEY +from peprs.const import SAMPLE_RAW_DICT_KEY # from ....const import SAMPLE_CONVERSION_FUNCTIONS from ....dependencies import ( @@ -262,25 +261,23 @@ async def get_pep_samples( ) if isinstance(proj, dict): - if len(proj["_sample_dict"]) > MAX_PROCESSED_PROJECT_SIZE: + if len(proj[SAMPLE_RAW_DICT_KEY]) > MAX_PROCESSED_PROJECT_SIZE: raise HTTPException( status_code=400, detail=f"Project is too large. View raw samples, or create a view. Limit is {MAX_PROCESSED_PROJECT_SIZE} samples.", ) - proj = peppy.Project.from_dict(proj) + proj = peprs.Project.from_dict(proj) if format == "json": return { "samples": [sample.to_dict() for sample in proj.samples], } elif format == "csv": - return PlainTextResponse(eido.convert_project(proj, "csv")["samples"]) + return PlainTextResponse(proj.to_csv_string()) elif format == "yaml": - return PlainTextResponse( - eido.convert_project(proj, "yaml-samples")["samples"] - ) + return PlainTextResponse(proj.to_yaml_string()) elif format == "basic": - return eido.convert_project(proj, "basic") + return proj.to_dict() if raw: df = pd.DataFrame(proj[SAMPLE_RAW_DICT_KEY]) @@ -289,12 +286,12 @@ async def get_pep_samples( items=df.replace({np.nan: None}).to_dict(orient="records"), ) if isinstance(proj, dict): - if len(proj["_sample_dict"]) > MAX_PROCESSED_PROJECT_SIZE: + if len(proj[SAMPLE_RAW_DICT_KEY]) > MAX_PROCESSED_PROJECT_SIZE: raise HTTPException( status_code=400, detail=f"Project is too large. View raw samples, or create a view. Limit is {MAX_PROCESSED_PROJECT_SIZE} samples.", ) - proj = peppy.Project.from_dict(proj) + proj = peprs.Project.from_dict(proj) return [sample.to_dict() for sample in proj.samples] @@ -500,7 +497,7 @@ async def delete_sample( @project.get("/subsamples", response_model=SamplesResponseModel) async def get_subsamples_endpoint( - subsamples: peppy.Project = Depends(get_subsamples), + subsamples: list = Depends(get_subsamples), download: bool = False, ): """ @@ -543,11 +540,8 @@ async def convert_pep( format: Optional[str] = "plain", ): """ - Convert a PEP to a specific format, f. For a list of available formats/filters, - see /eido/filters. - - See, http://eido.databio.org/en/latest/filters/#convert-a-pep-into-an-alternative-format-with-a-filter - for more information. + Convert a PEP to a specific format. Supported filters are: basic, csv, yaml, + yaml-samples, json. Don't have a namespace, or project? @@ -559,18 +553,26 @@ async def convert_pep( """ # default to basic if filter is None: - filter = "basic" # default to basic + filter = "basic" - # validate filter exists - filter_list = eido.get_available_pep_filters() - if filter not in filter_list: + # eido filter infrastructure is not in peprs; emulate the previously supported + # filters using peprs Project conversion methods. + available_filters = ["basic", "csv", "yaml", "yaml-samples", "json"] + if filter not in available_filters: raise HTTPException( - 400, f"Unknown filter '{filter}'. Available filters: {filter_list}" + 400, f"Unknown filter '{filter}'. Available filters: {available_filters}" ) - # generate result - peppy_project = peppy.Project.from_dict(proj) - conv_result = eido.run_filter(peppy_project, filter, verbose=False) + peprs_project = peprs.Project.from_dict(proj) + + if filter == "basic": + conv_result = {"project_config.yaml": peprs_project.to_yaml_string()} + elif filter == "csv": + conv_result = {"sample_table.csv": peprs_project.to_csv_string()} + elif filter in ("yaml", "yaml-samples"): + conv_result = {"sample_table.yaml": peprs_project.to_yaml_string()} + else: # json + conv_result = {"project.json": peprs_project.to_json_string()} if format == "plain": return_str = "\n".join([conv_result[k] for k in conv_result]) @@ -996,7 +998,7 @@ def get_project_history_by_id( with_id=True, ) # convert the config to a yaml string - project_at_history["_config"] = yaml.dump(project_at_history["_config"]) + project_at_history["config"] = yaml.dump(project_at_history["config"]) return project_at_history except ProjectNotFoundError: diff --git a/pephub/routers/eido/eido.py b/pephub/routers/eido/eido.py index 248b29a1..c9108b8b 100644 --- a/pephub/routers/eido/eido.py +++ b/pephub/routers/eido/eido.py @@ -2,8 +2,7 @@ import tempfile from typing import List, Optional, Tuple -import eido -import peppy +import peprs import requests import yaml from fastapi import APIRouter, Depends, Form, UploadFile @@ -13,6 +12,7 @@ from pepdbagent.exceptions import SchemaDoesNotExistError from pepdbagent.utils import schema_path_converter from pepdbagent.const import LATEST_SCHEMA_VERSION +from peprs.eido import EidoValidationError, validate_project from starlette.requests import Request from starlette.responses import JSONResponse @@ -31,6 +31,20 @@ async def status(): return JSONResponse(schemas_to_test) +def _read_schema(path_or_url: str) -> dict: + """ + Fetch and parse an eido YAML schema from a local file path or URL. + peprs.eido does not yet expose a read_schema helper, so we inline a + minimal implementation here. + """ + if path_or_url.startswith(("http://", "https://")): + resp = requests.get(path_or_url) + resp.raise_for_status() + return yaml.safe_load(resp.text) + with open(path_or_url) as f: + return yaml.safe_load(f) + + @router.get("/schemas/{namespace}/{project}") async def get_schema(request: Request, namespace: str, project: str): """ @@ -41,15 +55,16 @@ async def get_schema(request: Request, namespace: str, project: str): # like pipelines/ProseqPEP.yaml try: - schema = eido.read_schema( - f"https://schema.databio.org/{namespace}/{project}.yaml" - )[0] - except IndexError: + schema = _read_schema(f"https://schema.databio.org/{namespace}/{project}.yaml") + except Exception: raise HTTPException(status_code=404, detail="Schema not found") return schema +# Note: The pephub web UI now runs validation client-side via the +# @pepkit/peprs WASM bindings. This server-side endpoint is kept for +# programmatic/API consumers. See validation_wasm_plan.md. @router.post("/validate") async def validate( # accept both pep_registry and pep_files, both should be optional @@ -90,12 +105,12 @@ async def validate( pep_annot = agent.annotation.get(namespace=namespace, name=name, tag=tag) - if pep_annot.results[0].number_of_samples > MAX_PROCESSED_PROJECT_SIZE: - return { - "valid": False, - "error_type": "Project size", - "errors": ["Project is too large. Can't validate."], - } + # if pep_annot.results[0].number_of_samples > MAX_PROCESSED_PROJECT_SIZE: + # return { + # "valid": False, + # "error_type": "Project size", + # "errors": ["Project is too large. Can't validate."], + # } p = agent.project.get(namespace, name, tag, raw=False) else: @@ -115,7 +130,7 @@ async def validate( with open(f"{dirpath}/{upload_file.filename}", "wb") as local_tmpf: shutil.copyfileobj(upload_file.file, local_tmpf) - p = peppy.Project(f"{dirpath}/{init_file.filename}") + p = peprs.Project(f"{dirpath}/{init_file.filename}") if schema is None and schema_registry is None and schema_file is None: raise HTTPException( @@ -156,28 +171,28 @@ async def validate( contents = schema_file.file.read() schema_dict = yaml.safe_load(contents) else: - # save schema string to temp file, then read in with eido - with tempfile.NamedTemporaryFile(mode="w") as schema_file: - schema_file.write(schema) - schema_file.flush() - try: - schema_dict = eido.read_schema(schema_file.name)[0] - except eido.exceptions.EidoSchemaInvalidError as e: - raise HTTPException( - status_code=200, - detail={"error": f"Schema is invalid: {str(e)}"}, - ) + # parse the schema string directly; peprs.eido has no read_schema / + # EidoSchemaInvalidError, so any parse failure is reported as a schema error. + try: + schema_dict = yaml.safe_load(schema) + if not isinstance(schema_dict, dict): + raise ValueError("Schema must parse to a YAML mapping") + except Exception as e: + raise HTTPException( + status_code=200, + detail={"error": f"Schema is invalid: {str(e)}"}, + ) # validate project try: - eido.validate_project( + validate_project( p, schema_dict, ) # while we catch this, its still a 200 response since we want to # return the validation errors - except eido.exceptions.EidoValidationError as e: + except EidoValidationError as e: error_type, property_names = await eido_error_string_converter(e) return {"valid": False, "error_type": error_type, "errors": property_names} @@ -193,33 +208,57 @@ async def validate( async def eido_error_string_converter( - e: eido.exceptions.EidoValidationError, + e: EidoValidationError, ) -> Tuple[str, List[str]]: """ Convert eido error into nice modified string - :param e: eido Validation error + peprs.eido.EidoValidationError.errors_by_type has the shape: + { + "": [ + {"path": ..., "message": ..., "sample_names": [...optional]}, + ... + ], + ... + } + An item without "sample_names" is a project-level error; otherwise it + is a sample-level error affecting the listed samples. + + :param e: peprs.eido Validation error :return: error_type, property_names """ - property_names = [] - error_type_list = [] - for item_list in e.errors_by_type.values(): - property_type = item_list[0]["type"] - property_name_list = [] + property_names: List[str] = [] + error_type_set = set() + messages = [] + + for error_type_key, item_list in e.errors_by_type.items(): + sample_names_all: List[str] = [] + has_project_level = False for item in item_list: - if item["sample_name"] == "project": - error_type_list.append("Project") - break + sample_names = item.get("sample_names") or [] + if sample_names: + sample_names_all.extend(sample_names) + messages.append(item.get("message")) + else: + has_project_level = True + + if sample_names_all: + error_type_set.add("Samples") + if len(sample_names_all) > 20: + property_names.append( + f"{error_type_key}: more than 20 samples have encountered errors." + ) else: - error_type_list.append("Samples") - if len(item_list) > 20: - property_names = ["More than 20 samples have encountered errors."] - else: - property_name_list.append(item["sample_name"]) - - if len(property_name_list) > 0: - property_names.append(f"{property_type} ({', '.join(property_name_list)})") - else: - property_names.append(f"{property_type} in the project") - error_type = " and ".join(set(error_type_list)) + property_names.append( + f"{error_type_key} ({', '.join(sample_names_all)})" + ) + if not sample_names_all: + property_names = messages + + if has_project_level: + error_type_set.add("Project") + property_names.append(f"{error_type_key} in the project") + + error_type = " and ".join(sorted(error_type_set)) + return error_type, property_names diff --git a/pephub/routers/models.py b/pephub/routers/models.py index 8e52579b..f649a545 100644 --- a/pephub/routers/models.py +++ b/pephub/routers/models.py @@ -74,25 +74,25 @@ class JWTDeviceTokenResponse(BaseModel): class ProjectRawModel(BaseModel): - config: dict = Field(alias="_config") - subsample_list: Optional[list] = Field(alias="_subsample_list", default=None) - sample_list: list[dict] = Field(alias="_sample_dict") + config: dict + subsamples: Optional[list] = None + samples: list[dict] model_config = ConfigDict(populate_by_name=True) class ProjectHistoryResponse(BaseModel): - config: str = Field(alias="_config") - subsample_list: Optional[list] = Field(alias="_subsample_list", default=None) - sample_list: list[dict] = Field(alias="_sample_dict") + config: str + subsamples: Optional[list] = None + samples: list[dict] model_config = ConfigDict(populate_by_name=True) class ProjectRawRequest(BaseModel): config: dict - subsample_list: Optional[List[List[dict]]] = None - sample_list: List[dict] + subsamples: Optional[List[List[dict]]] = None + samples: List[dict] model_config = ConfigDict(populate_by_name=True, extra="allow") @@ -120,7 +120,7 @@ class DeveloperKey(BaseModel): class VersionResponseModel(BaseModel): pephub_version: str - peppy_version: str + peprs_version: str python_version: str fastapi_version: str pepdbagent_version: str diff --git a/pephub/routers/views/eido.py b/pephub/routers/views/eido.py index d57fff81..7c3bc55c 100644 --- a/pephub/routers/views/eido.py +++ b/pephub/routers/views/eido.py @@ -1,15 +1,16 @@ from platform import python_version -import eido import jinja2 +import requests +import yaml from dotenv import load_dotenv from fastapi import APIRouter, Request +from fastapi.exceptions import HTTPException from fastapi.responses import HTMLResponse -from peppy import __version__ as peppy_version from starlette.templating import Jinja2Templates from ..._version import __version__ as pephub_version -from ...const import EIDO_TEMPLATES_PATH +from ...const import EIDO_TEMPLATES_PATH, peprs_version load_dotenv() @@ -18,7 +19,7 @@ ALL_VERSIONS = { "pephub_version": pephub_version, - "peppy_version": peppy_version, + "peprs_version": peprs_version, "python_version": python_version(), "api_version": 1, } @@ -35,7 +36,13 @@ async def get_schema(request: Request, namespace: str, project: str): # endpoint to schema.databio.org/... # like pipelines/ProseqPEP.yaml - schema = eido.read_schema(f"http://schema.databio.org/{namespace}/{project}") + # peprs.eido has no read_schema helper, so fetch and parse the YAML directly. + try: + resp = requests.get(f"http://schema.databio.org/{namespace}/{project}") + resp.raise_for_status() + schema = yaml.safe_load(resp.text) + except Exception: + raise HTTPException(status_code=404, detail="Schema not found") return templates.TemplateResponse( "schema.html", diff --git a/requirements/requirements-all.txt b/requirements/requirements-all.txt index b1b12340..302998fa 100644 --- a/requirements/requirements-all.txt +++ b/requirements/requirements-all.txt @@ -1,9 +1,8 @@ fastapi>=0.108.0 psycopg>=3.1.15 -pepdbagent>=0.12.4 -# pepdbagent @ git+https://github.com/pepkit/pepdbagent.git@dev#egg=pepdbagent -peppy>=0.40.7 -eido>=0.2.4 +#pepdbagent>=0.12.4 + pepdbagent @ git+https://github.com/pepkit/pepdbagent.git@peprs#egg=pepdbagent +peprs>=0.2.0 jinja2>=3.1.2 python-multipart>=0.0.5 uvicorn diff --git a/tests/conftest.py b/tests/conftest.py index 2ab8d763..8cf53b2e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import pytest import requests from pepdbagent import PEPDatabaseAgent -from peppy import Project +from peprs import Project @pytest.fixture diff --git a/tests/test_validation.py b/tests/test_validation.py index b2b79f43..3b0c8667 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,4 +1,4 @@ -import eido +from peprs import eido import pytest from pephub.dependencies import * @@ -13,7 +13,7 @@ def test_file_file_validate_valid(project_object_file, schema_file_path): # test project file on schema file validation failure def test_file_file_validate_invalid(project_object_file, schema_file_path_invalid): - with pytest.raises(eido.exceptions.EidoValidationError): + with pytest.raises(eido.EidoValidationError): eido.validate_project( project=project_object_file, schema=schema_file_path_invalid ) @@ -26,7 +26,7 @@ def test_file_string_validate_valid(project_object_file, schema_paste): # test project file on schema string validation failure def test_file_string_validate_invalid(project_object_file, schema_paste_invalid): - with pytest.raises(eido.exceptions.EidoValidationError): + with pytest.raises(eido.EidoValidationError): eido.validate_project(project=project_object_file, schema=schema_paste_invalid) @@ -37,7 +37,7 @@ def test_file_url_validate_valid(project_object_file, schema_from_url_valid): # test project file on schema url validation failure def test_file_url_validate_invalid(project_object_file, schema_from_url_invalid): - with pytest.raises(eido.exceptions.EidoValidationError): + with pytest.raises(eido.EidoValidationError): eido.validate_project( project=project_object_file, schema=schema_from_url_invalid ) @@ -50,7 +50,7 @@ def test_registry_paste_valid(db, schema_paste): def test_registry_paste_invalid(db, schema_paste_invalid): p = db.project.get("ayobi", "new-project-test12345", "default") - with pytest.raises(eido.exceptions.EidoValidationError): + with pytest.raises(eido.EidoValidationError): eido.validate_project(project=p, schema=schema_paste_invalid) @@ -61,7 +61,7 @@ def test_registry_file_valid(db, schema_file_path): def test_registry_file_invalid(db, schema_file_path_invalid): p = db.project.get("ayobi", "new-project-test12345", "default") - with pytest.raises(eido.exceptions.EidoValidationError): + with pytest.raises(eido.EidoValidationError): eido.validate_project(project=p, schema=schema_file_path_invalid) @@ -72,5 +72,5 @@ def test_registry_url_valid(db, schema_from_url_valid): def test_registry_url_invalid(db, schema_from_url_invalid): p = db.project.get("ayobi", "new-project-test12345", "default") - with pytest.raises(eido.exceptions.EidoValidationError): + with pytest.raises(eido.EidoValidationError): eido.validate_project(project=p, schema=schema_from_url_invalid) diff --git a/web/package.json b/web/package.json index c0a240fe..d4427736 100644 --- a/web/package.json +++ b/web/package.json @@ -1,7 +1,7 @@ { "name": "web", "private": true, - "version": "0.15.4", + "version": "0.16.0", "type": "module", "scripts": { "dev": "vite", @@ -15,6 +15,7 @@ "@hookform/error-message": "^2.0.1", "@mdx-js/react": "^3.0.0", "@monaco-editor/react": "^4.4.6", + "@databio/peprs": "^0.2.0", "@popperjs/core": "^2.11.6", "@tanstack/react-query": "^5.0.0", "@tanstack/react-query-devtools": "^5.0.0", diff --git a/web/src/api/namespace.ts b/web/src/api/namespace.ts index 933c2dff..663d0476 100644 --- a/web/src/api/namespace.ts +++ b/web/src/api/namespace.ts @@ -219,7 +219,7 @@ export const submitProjectJSON = ( { pep_dict: { config: config_json, - sample_list: sample_table, + samples: sample_table, }, description: description || '', name: name, @@ -269,7 +269,7 @@ export const submitPop = ( { pep_dict: { config: config_json, - sample_list: peps, + samples: peps, pep_schema: pep_schema, }, description: description || '', diff --git a/web/src/api/project.ts b/web/src/api/project.ts index 540c1dde..6793f24b 100644 --- a/web/src/api/project.ts +++ b/web/src/api/project.ts @@ -25,7 +25,7 @@ type ProjectUpdateMetadata = ProjectUpdateItems & { sample_table?: Sample[] | null; project_config_yaml?: string | null; description?: string | null; - subsample_list?: string[] | null; + subsample_tables?: any[][] | null; }; export type SampleTableResponse = { count: number; diff --git a/web/src/api/server.ts b/web/src/api/server.ts index ef3ab976..19e756cf 100644 --- a/web/src/api/server.ts +++ b/web/src/api/server.ts @@ -2,7 +2,7 @@ import axios from 'axios'; export interface ApiBase { pephub_version: string; - peppy_version: string; + peprs_version: string; python_version: string; fastapi_version: string; pepdbagent_version: string; diff --git a/web/src/components/forms/components/schemas-databio-dropdown.tsx b/web/src/components/forms/components/schemas-databio-dropdown.tsx index 6a03a93a..f2cc4b39 100644 --- a/web/src/components/forms/components/schemas-databio-dropdown.tsx +++ b/web/src/components/forms/components/schemas-databio-dropdown.tsx @@ -12,7 +12,7 @@ interface Props { defaultValue?: string; } -const SchemaDropdown: FC = ({ value, onChange, showDownload = true, defaultValue = 'databio/pep'}) => { +const SchemaDropdown: FC = ({ value, onChange, showDownload = true, defaultValue }) => { const { data: schemas, isFetching: isLoading } = useAllSchemas({}); const options = (schemas?.results || []).map((schema) => ({ @@ -20,14 +20,16 @@ const SchemaDropdown: FC = ({ value, onChange, showDownload = true, defau value: `${schema.namespace}/${schema.schema_name}`, })); - const defaultSchema = defaultValue; - const valueForSelect = options.find((option) => option.value === value); + const valueForSelect = options.find((option) => option.value === value) ?? null; + const defaultValueForSelect = defaultValue + ? { label: defaultValue, value: defaultValue } + : undefined; return (