Skip to content

Commit 67b0d4d

Browse files
Merge pull request #455 from pepkit/peprs
Peprs
2 parents 0fb2b96 + bcd52b9 commit 67b0d4d

31 files changed

+761
-361
lines changed

pephub/const.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,20 @@
66
import pandas as pd
77
from fastapi import __version__ as fastapi_version
88
from pepdbagent import __version__ as pepdbagent_version
9-
from peppy import __version__ as peppy_version
10-
from peppy.const import PEP_LATEST_VERSION
119

1210
from ._version import __version__ as pephub_version
1311

12+
# peprs has no __version__ attribute and no PEP_LATEST_VERSION constant.
13+
PEP_LATEST_VERSION = "2.1.0"
14+
peprs_version = "unknown"
15+
1416
PKG_NAME = "pephub"
1517
DATA_REPO = "https://github.com/pepkit/data.pephub.git"
1618

1719

1820
ALL_VERSIONS = {
1921
"pephub_version": pephub_version,
20-
"peppy_version": peppy_version,
22+
"peprs_version": peprs_version,
2123
"python_version": python_version(),
2224
"fastapi_version": fastapi_version,
2325
"pepdbagent_version": pepdbagent_version,

pephub/helpers.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@
99
import json
1010
from fastapi import Response, UploadFile
1111
from fastapi.exceptions import HTTPException
12-
from peppy.const import (
13-
CFG_SAMPLE_TABLE_KEY,
14-
CFG_SUBSAMPLE_TABLE_KEY,
12+
from peprs.const import (
1513
CONFIG_KEY,
16-
NAME_KEY,
1714
SAMPLE_RAW_DICT_KEY,
18-
SUBSAMPLE_RAW_LIST_KEY,
15+
SUBSAMPLE_RAW_DICT_KEY,
1916
)
2017
from .const import JWT_EXPIRATION, JWT_SECRET
2118

19+
# peprs.const does not export these — they are PEP config schema strings.
20+
CFG_SAMPLE_TABLE_KEY = "sample_table"
21+
CFG_SUBSAMPLE_TABLE_KEY = "subsample_table"
22+
NAME_KEY = "name"
23+
2224

2325
def jwt_encode_user_data(user_data: dict, exp: datetime = None) -> str:
2426
"""
@@ -53,15 +55,15 @@ def zip_pep(project: Dict[str, Any]) -> Response:
5355
project[SAMPLE_RAW_DICT_KEY]
5456
).to_csv(index=False)
5557

56-
if project[SUBSAMPLE_RAW_LIST_KEY] is not None:
57-
if not isinstance(project[SUBSAMPLE_RAW_LIST_KEY], list):
58+
if project[SUBSAMPLE_RAW_DICT_KEY] is not None:
59+
if not isinstance(project[SUBSAMPLE_RAW_DICT_KEY], list):
5860
config[CFG_SUBSAMPLE_TABLE_KEY] = ["subsample_table1.csv"]
5961
content_to_zip["subsample_table1.csv"] = pd.DataFrame(
60-
project[SUBSAMPLE_RAW_LIST_KEY]
62+
project[SUBSAMPLE_RAW_DICT_KEY]
6163
).to_csv(index=False)
6264
else:
6365
config[CFG_SUBSAMPLE_TABLE_KEY] = []
64-
for number, file in enumerate(project[SUBSAMPLE_RAW_LIST_KEY]):
66+
for number, file in enumerate(project[SUBSAMPLE_RAW_DICT_KEY]):
6567
file_name = f"subsample_table{number + 1}.csv"
6668
config[CFG_SUBSAMPLE_TABLE_KEY].append(file_name)
6769
content_to_zip[file_name] = pd.DataFrame(file).to_csv(index=False)

pephub/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
fmt="[%(levelname)s] [%(asctime)s] [PEPDBAGENT] %(message)s",
2929
)
3030

31-
_LOGGER_PEPPY = logging.getLogger("peppy")
31+
_LOGGER_PEPRS = logging.getLogger("peprs")
3232
coloredlogs.install(
33-
logger=_LOGGER_PEPPY,
33+
logger=_LOGGER_PEPRS,
3434
level=logging.ERROR,
3535
datefmt="%b %d %Y %H:%M:%S",
36-
fmt="[%(levelname)s] [%(asctime)s] [PEPPY] %(message)s",
36+
fmt="[%(levelname)s] [%(asctime)s] [PEPRS] %(message)s",
3737
)
3838

3939
_LOGGER_PEPHUB = logging.getLogger("uvicorn.access")

pephub/routers/api/v1/helpers.py

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import logging
22

3-
import eido
4-
from eido.validation import validate_config
5-
from eido.exceptions import EidoValidationError
6-
import peppy
3+
import peprs
74
import yaml
85
from fastapi.exceptions import HTTPException
9-
from peppy import Project
10-
from peppy.const import (
6+
from peprs import Project
7+
from peprs.eido import EidoValidationError, validate_config, validate_project
8+
from peprs.const import (
119
CONFIG_KEY,
1210
SAMPLE_RAW_DICT_KEY,
13-
SUBSAMPLE_RAW_LIST_KEY,
11+
SUBSAMPLE_RAW_DICT_KEY,
1412
)
1513
from ....dependencies import (
1614
get_db,
@@ -22,7 +20,7 @@
2220
DEFAULT_SCHEMA_VERSION = "2.1.0"
2321

2422

25-
async def verify_updated_project(updated_project) -> peppy.Project:
23+
async def verify_updated_project(updated_project) -> peprs.Project:
2624
new_raw_project = {}
2725

2826
agent = get_db()
@@ -37,43 +35,23 @@ async def verify_updated_project(updated_project) -> peppy.Project:
3735
status_code=400,
3836
detail="Please provide a sample table and project config yaml to update project",
3937
)
40-
try:
41-
validate_config(
42-
yaml.safe_load(updated_project.project_config_yaml), default_schema
43-
)
44-
except EidoValidationError as e:
45-
raise HTTPException(
46-
status_code=400,
47-
detail=f"Config structure error: {', '.join(list(e.errors_by_type.keys()))}. Please check schema definition and try again.",
48-
)
49-
# sample table update
50-
new_raw_project[SAMPLE_RAW_DICT_KEY] = updated_project.sample_table
5138

5239
try:
5340
yaml_dict = yaml.safe_load(updated_project.project_config_yaml)
54-
new_raw_project[CONFIG_KEY] = yaml_dict
5541
except yaml.scanner.ScannerError as e:
5642
raise HTTPException(
5743
status_code=400,
5844
detail=f"Could not parse provided yaml. Error: {e}",
5945
)
6046

61-
# sample_table_index_col = yaml_dict.get(
62-
# SAMPLE_TABLE_INDEX_KEY, SAMPLE_NAME_ATTR # default to sample_name
63-
# )
64-
65-
# await check_sample_names(
66-
# new_raw_project[SAMPLE_RAW_DICT_KEY], sample_table_index_col
67-
# )
47+
new_raw_project[CONFIG_KEY] = yaml_dict
48+
new_raw_project[SAMPLE_RAW_DICT_KEY] = updated_project.sample_table
6849

6950
# subsample table update
7051
if updated_project.subsample_tables is not None:
7152
subsamples = list(updated_project.subsample_tables[0][0].values())
72-
new_raw_project[SUBSAMPLE_RAW_LIST_KEY] = (
73-
updated_project.subsample_tables
74-
if len(subsamples) > 0 and subsamples[0]
75-
else None
76-
)
53+
if len(subsamples) > 0 and subsamples[0]:
54+
new_raw_project[SUBSAMPLE_RAW_DICT_KEY] = updated_project.subsample_tables
7755

7856
try:
7957
new_project = Project.from_dict(new_raw_project)
@@ -83,9 +61,19 @@ async def verify_updated_project(updated_project) -> peppy.Project:
8361
detail=f"Could not create PEP from provided data. Error: {e}",
8462
)
8563

64+
# peprs.eido.validate_config takes a Project (not a raw dict like eido did),
65+
# so we validate after constructing the Project.
66+
try:
67+
validate_config(new_project, default_schema)
68+
except EidoValidationError as e:
69+
raise HTTPException(
70+
status_code=400,
71+
detail=f"Config structure error: {', '.join(list(e.errors_by_type.keys()))}. Please check schema definition and try again.",
72+
)
73+
8674
try:
8775
# validate project (it will also validate samples)
88-
eido.validate_project(new_project, default_schema)
76+
validate_project(new_project, default_schema)
8977
except Exception as _:
9078
raise HTTPException(
9179
status_code=400,

pephub/routers/api/v1/namespace.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import List, Literal, Optional, Union
44
import os
55

6-
import peppy
6+
import peprs
77
from dotenv import load_dotenv
88
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile, Request
99
from fastapi.responses import JSONResponse
@@ -22,10 +22,13 @@
2222
NamespaceStats,
2323
TarNamespaceModelReturn,
2424
)
25-
from peppy import Project
26-
from peppy.const import DESC_KEY, NAME_KEY
25+
from peprs import Project
2726
from typing_extensions import Annotated
2827

28+
# peprs.const does not export these — they are PEP config schema strings.
29+
NAME_KEY = "name"
30+
DESC_KEY = "description"
31+
2932
from ....const import (
3033
DEFAULT_TAG,
3134
ARCHIVE_URL_PATH,
@@ -213,7 +216,7 @@ async def create_pep(
213216
},
214217
status_code=202,
215218
)
216-
# create a blank peppy.Project object with fake files
219+
# create a blank peprs.Project object with fake files
217220
else:
218221
raise HTTPException(
219222
detail="Project files were not provided",
@@ -258,21 +261,23 @@ async def upload_raw_pep(
258261
# This configurations needed due to Issue #124 Should be removed in the future
259262
project_dict = ProjectRawModel(**project_from_json.pep_dict.dict())
260263
ff = project_dict.model_dump(by_alias=True)
261-
p_project = peppy.Project().from_dict(ff)
264+
p_project = peprs.Project.from_dict(ff)
262265

263-
p_project.namespace = name
266+
# peprs.Project has no `namespace` attribute, so we set the registry name
267+
# in the config and pass `name` separately to agent.project.create.
268+
p_project.name = name
264269
p_project.description = description
265270

266271
except Exception as e:
267272
raise HTTPException(
268-
detail=f"Incorrect raw project was provided. Couldn't initiate peppy object: {e}",
273+
detail=f"Incorrect raw project was provided. Couldn't initiate peprs object: {e}",
269274
status_code=417,
270275
)
271276
try:
272277
agent.project.create(
273278
p_project,
274279
namespace=namespace,
275-
name=p_project.namespace,
280+
name=name,
276281
tag=tag,
277282
description=description,
278283
is_private=is_private,
@@ -282,15 +287,15 @@ async def upload_raw_pep(
282287
)
283288
except ProjectUniqueNameError:
284289
raise HTTPException(
285-
detail=f"Project '{namespace}/{p_project.namespace}:{tag}' already exists in namespace",
290+
detail=f"Project '{namespace}/{name}:{tag}' already exists in namespace",
286291
status_code=400,
287292
)
288293
return JSONResponse(
289294
content={
290295
"namespace": namespace,
291-
"name": p_project.namespace,
296+
"name": name,
292297
"tag": tag,
293-
"registry_path": f"{namespace}/{p_project.namespace}:{tag}",
298+
"registry_path": f"{namespace}/{name}:{tag}",
294299
},
295300
status_code=202,
296301
)

pephub/routers/api/v1/project.py

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import logging
22
from typing import Annotated, Any, Literal, Dict, List, Optional, Union
33

4-
import eido
54
import numpy as np
65
import pandas as pd
7-
import peppy
6+
import peprs
87
import yaml
98
from dotenv import load_dotenv
109
from fastapi import APIRouter, Body, Depends, Query
@@ -29,7 +28,7 @@
2928
ProjectViews,
3029
HistoryAnnotationModel,
3130
)
32-
from peppy.const import SAMPLE_RAW_DICT_KEY
31+
from peprs.const import SAMPLE_RAW_DICT_KEY
3332

3433
# from ....const import SAMPLE_CONVERSION_FUNCTIONS
3534
from ....dependencies import (
@@ -262,25 +261,23 @@ async def get_pep_samples(
262261
)
263262

264263
if isinstance(proj, dict):
265-
if len(proj["_sample_dict"]) > MAX_PROCESSED_PROJECT_SIZE:
264+
if len(proj[SAMPLE_RAW_DICT_KEY]) > MAX_PROCESSED_PROJECT_SIZE:
266265
raise HTTPException(
267266
status_code=400,
268267
detail=f"Project is too large. View raw samples, or create a view. Limit is {MAX_PROCESSED_PROJECT_SIZE} samples.",
269268
)
270-
proj = peppy.Project.from_dict(proj)
269+
proj = peprs.Project.from_dict(proj)
271270

272271
if format == "json":
273272
return {
274273
"samples": [sample.to_dict() for sample in proj.samples],
275274
}
276275
elif format == "csv":
277-
return PlainTextResponse(eido.convert_project(proj, "csv")["samples"])
276+
return PlainTextResponse(proj.to_csv_string())
278277
elif format == "yaml":
279-
return PlainTextResponse(
280-
eido.convert_project(proj, "yaml-samples")["samples"]
281-
)
278+
return PlainTextResponse(proj.to_yaml_string())
282279
elif format == "basic":
283-
return eido.convert_project(proj, "basic")
280+
return proj.to_dict()
284281

285282
if raw:
286283
df = pd.DataFrame(proj[SAMPLE_RAW_DICT_KEY])
@@ -289,12 +286,12 @@ async def get_pep_samples(
289286
items=df.replace({np.nan: None}).to_dict(orient="records"),
290287
)
291288
if isinstance(proj, dict):
292-
if len(proj["_sample_dict"]) > MAX_PROCESSED_PROJECT_SIZE:
289+
if len(proj[SAMPLE_RAW_DICT_KEY]) > MAX_PROCESSED_PROJECT_SIZE:
293290
raise HTTPException(
294291
status_code=400,
295292
detail=f"Project is too large. View raw samples, or create a view. Limit is {MAX_PROCESSED_PROJECT_SIZE} samples.",
296293
)
297-
proj = peppy.Project.from_dict(proj)
294+
proj = peprs.Project.from_dict(proj)
298295
return [sample.to_dict() for sample in proj.samples]
299296

300297

@@ -500,7 +497,7 @@ async def delete_sample(
500497

501498
@project.get("/subsamples", response_model=SamplesResponseModel)
502499
async def get_subsamples_endpoint(
503-
subsamples: peppy.Project = Depends(get_subsamples),
500+
subsamples: list = Depends(get_subsamples),
504501
download: bool = False,
505502
):
506503
"""
@@ -543,11 +540,8 @@ async def convert_pep(
543540
format: Optional[str] = "plain",
544541
):
545542
"""
546-
Convert a PEP to a specific format, f. For a list of available formats/filters,
547-
see /eido/filters.
548-
549-
See, http://eido.databio.org/en/latest/filters/#convert-a-pep-into-an-alternative-format-with-a-filter
550-
for more information.
543+
Convert a PEP to a specific format. Supported filters are: basic, csv, yaml,
544+
yaml-samples, json.
551545
552546
Don't have a namespace, or project?
553547
@@ -559,18 +553,26 @@ async def convert_pep(
559553
"""
560554
# default to basic
561555
if filter is None:
562-
filter = "basic" # default to basic
556+
filter = "basic"
563557

564-
# validate filter exists
565-
filter_list = eido.get_available_pep_filters()
566-
if filter not in filter_list:
558+
# eido filter infrastructure is not in peprs; emulate the previously supported
559+
# filters using peprs Project conversion methods.
560+
available_filters = ["basic", "csv", "yaml", "yaml-samples", "json"]
561+
if filter not in available_filters:
567562
raise HTTPException(
568-
400, f"Unknown filter '{filter}'. Available filters: {filter_list}"
563+
400, f"Unknown filter '{filter}'. Available filters: {available_filters}"
569564
)
570565

571-
# generate result
572-
peppy_project = peppy.Project.from_dict(proj)
573-
conv_result = eido.run_filter(peppy_project, filter, verbose=False)
566+
peprs_project = peprs.Project.from_dict(proj)
567+
568+
if filter == "basic":
569+
conv_result = {"project_config.yaml": peprs_project.to_yaml_string()}
570+
elif filter == "csv":
571+
conv_result = {"sample_table.csv": peprs_project.to_csv_string()}
572+
elif filter in ("yaml", "yaml-samples"):
573+
conv_result = {"sample_table.yaml": peprs_project.to_yaml_string()}
574+
else: # json
575+
conv_result = {"project.json": peprs_project.to_json_string()}
574576

575577
if format == "plain":
576578
return_str = "\n".join([conv_result[k] for k in conv_result])
@@ -996,7 +998,7 @@ def get_project_history_by_id(
996998
with_id=True,
997999
)
9981000
# convert the config to a yaml string
999-
project_at_history["_config"] = yaml.dump(project_at_history["_config"])
1001+
project_at_history["config"] = yaml.dump(project_at_history["config"])
10001002
return project_at_history
10011003

10021004
except ProjectNotFoundError:

0 commit comments

Comments
 (0)