Skip to content

Commit

Permalink
Merge pull request #235 from pepkit/dev
Browse files Browse the repository at this point in the history
Optimize web interface fetching of PEP annotation data. New `/annotation` endpoint
  • Loading branch information
nleroy917 authored Dec 5, 2023
2 parents 60d5706 + 4abc9f3 commit 111138c
Show file tree
Hide file tree
Showing 21 changed files with 150 additions and 117 deletions.
11 changes: 11 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format.

## [0.10.5] - 12-04-2023

### Changed

- optimized web interface fetching of PEP annotation data.

### Added

- project annotation endpoint (#234)


# [0.10.4] - 10-02-2023

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion pephub/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.10.4"
__version__ = "0.10.5"
2 changes: 1 addition & 1 deletion pephub/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from platform import python_version
from fastapi import __version__ as fastapi_version
from pepdbagent import __version__ as pepdbagent_version
from pepdbagent.const import DEFAULT_TAG

from secrets import token_hex

Expand Down Expand Up @@ -131,3 +130,4 @@
CALLBACK_ENDPOINT = "/auth/callback"

DEFAULT_PEP_SCHEMA = "pep/2.1.0"
DEFAULT_TAG = "default"
20 changes: 6 additions & 14 deletions pephub/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@
from typing import Union, List, Optional, Dict, Any
from datetime import datetime, timedelta

from fastapi import Depends, Header, Form
from fastapi import Depends, Header
from fastapi.exceptions import HTTPException
from fastapi.security import HTTPBearer
from pydantic import BaseModel
from pepdbagent import PEPDatabaseAgent
from pepdbagent.const import DEFAULT_TAG
from pepdbagent.exceptions import ProjectNotFoundError
from pepdbagent.models import AnnotationModel, Namespace
from qdrant_client import QdrantClient
from qdrant_client.http.exceptions import ResponseHandlingException
from sentence_transformers import SentenceTransformer

from .routers.models import AnnotationModel, NamespaceList, Namespace, ForkRequest
from .routers.models import ForkRequest
from .const import (
DEFAULT_POSTGRES_HOST,
DEFAULT_POSTGRES_PASSWORD,
Expand Down Expand Up @@ -213,7 +214,6 @@ def get_project_annotation(
agent: PEPDatabaseAgent = Depends(get_db),
namespace_access_list: List[str] = Depends(get_namespace_access_list),
) -> AnnotationModel:
# TODO: Is just grabbing the first annotation the right thing to do?
try:
anno = agent.annotation.get(
namespace, project, tag, admin=namespace_access_list
Expand All @@ -226,14 +226,6 @@ def get_project_annotation(
)


# TODO: This isn't used; do we still need it?
def get_namespaces(
agent: PEPDatabaseAgent = Depends(get_db),
user: str = Depends(get_user_from_session_info),
) -> List[NamespaceList]:
yield agent.namespace.get(admin=user)


def verify_user_can_write_namespace(
namespace: str,
session_info: Union[dict, None] = Depends(read_authorization_header),
Expand All @@ -257,8 +249,8 @@ def verify_user_can_write_namespace(


def verify_user_can_read_project(
project: str,
namespace: str,
project: str,
tag: Optional[str] = DEFAULT_TAG,
project_annotation: AnnotationModel = Depends(get_project_annotation),
session_info: Union[dict, None] = Depends(read_authorization_header),
Expand Down Expand Up @@ -323,13 +315,13 @@ def verify_user_can_write_project(
if session_info is None:
raise HTTPException(
401,
f"Please authenticate before editing project.",
"Please authenticate before editing project.",
)
# AUTHORIZATION REQUIRED
if session_info["login"] != namespace and namespace not in orgs:
raise HTTPException(
403,
f"The current authenticated user does not have permission to edit this project.",
"The current authenticated user does not have permission to edit this project.",
)


Expand Down
5 changes: 2 additions & 3 deletions pephub/helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
from datetime import date
from typing import List, Union, Tuple
from fastapi import Response, Depends, UploadFile
from fastapi import Response, UploadFile
from fastapi.exceptions import HTTPException
from ubiquerg import VersionInHelpParser

Expand Down Expand Up @@ -54,7 +53,7 @@ def add_subparser(cmd, description):
"--config",
required=False,
dest="config",
help=f"A path to the pepserver config file",
help="A path to the pepserver config file",
)

sps["serve"].add_argument(
Expand Down
11 changes: 3 additions & 8 deletions pephub/main.py
Original file line number Diff line number Diff line change
@@ -1,38 +1,33 @@
import logging
import sys
import uvicorn
import coloredlogs

from fastapi import FastAPI
from fastapi.middleware import Middleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles

from ._version import __version__ as server_v
from .const import PKG_NAME, TAGS_METADATA, SPA_PATH
from .helpers import build_parser
from .routers.api.v1.base import api as api_base
from .routers.api.v1.namespace import namespace as api_namespace
from .routers.api.v1.project import project as api_project
from .routers.api.v1.user import user as api_user
from .routers.api.v1.search import search as api_search
from .routers.auth.base import auth as auth_router
from .routers.eido.eido import router as eido_router
from .middleware import SPA, EnvironmentMiddleware
from .const import LOG_LEVEL_MAP
from .middleware import SPA

_LOGGER_PEPDBAGENT = logging.getLogger("pepdbagent")
coloredlogs.install(
logger=_LOGGER_PEPDBAGENT,
level=logging.WARNING,
level=logging.INFO,
datefmt="%b %d %Y %H:%M:%S",
fmt="[%(levelname)s] [%(asctime)s] [PEPDBAGENT] %(message)s",
)

_LOGGER_PEPPY = logging.getLogger("peppy")
coloredlogs.install(
logger=_LOGGER_PEPPY,
level=logging.WARNING,
level=logging.ERROR,
datefmt="%b %d %Y %H:%M:%S",
fmt="[%(levelname)s] [%(asctime)s] [PEPPY] %(message)s",
)
Expand Down
2 changes: 1 addition & 1 deletion pephub/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from fastapi.responses import FileResponse
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
from starlette.types import ASGIApp, Receive, Scope, Send
from starlette.types import Send

from .const import SPA_PATH

Expand Down
1 change: 0 additions & 1 deletion pephub/routers/api/v1/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from fastapi import APIRouter

from ....dependencies import *
from ....const import ALL_VERSIONS


Expand Down
23 changes: 18 additions & 5 deletions pephub/routers/api/v1/namespace.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
import tempfile
import shutil
import json
from typing import List, Optional, Union

from fastapi import APIRouter, File, UploadFile, Request, Depends, Form, Body
import peppy

from fastapi import APIRouter, File, UploadFile, Request, Depends, Form
from fastapi.responses import JSONResponse
from peppy import Project
from peppy.const import DESC_KEY, NAME_KEY
from pepdbagent import PEPDatabaseAgent
from pepdbagent.exceptions import ProjectUniqueNameError
from pepdbagent.const import DEFAULT_LIMIT_INFO
from pepdbagent.models import ListOfNamespaceInfo
from pepdbagent.models import ListOfNamespaceInfo, Namespace
from typing import Literal
from typing_extensions import Annotated

from ....dependencies import *
from ....dependencies import (
get_db,
get_namespace_info,
get_user_from_session_info,
read_authorization_header,
get_organizations_from_session_info,
get_namespace_access_list,
verify_user_can_write_namespace,
)
from ....helpers import parse_user_file_upload, split_upload_files_on_init_file
from ....const import (
DEFAULT_TAG,
Expand Down Expand Up @@ -292,7 +305,7 @@ async def upload_raw_pep(
except Exception as e:
return JSONResponse(
content={
"message": f"Incorrect raw project was provided. Couldn't initiate peppy object.",
"message": "Incorrect raw project was provided. Couldn't initiate peppy object.",
"error": f"{e}",
"status_code": 417,
},
Expand All @@ -313,7 +326,7 @@ async def upload_raw_pep(
return JSONResponse(
content={
"message": f"Project '{namespace}/{p_project.name}:{tag}' already exists in namespace",
"error": f"Set overwrite=True to overwrite or update project",
"error": "Set overwrite=True to overwrite or update project",
"status_code": 409,
},
status_code=409,
Expand Down
49 changes: 36 additions & 13 deletions pephub/routers/api/v1/project.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import eido
import yaml
import pandas as pd
from io import StringIO
from typing import Callable, Literal, Union
from fastapi import APIRouter
import peppy
from typing import Callable, Literal, Union, Optional
from fastapi import APIRouter, Depends
from fastapi.exceptions import HTTPException
from fastapi.responses import JSONResponse, PlainTextResponse
from peppy import Project
from peppy.const import (
Expand All @@ -13,15 +14,27 @@
SUBSAMPLE_RAW_LIST_KEY,
)

from ...models import ProjectOptional, ProjectRawModel
from ....helpers import zip_conv_result, get_project_sample_names, zip_pep
from ....dependencies import *
from ....const import SAMPLE_CONVERSION_FUNCTIONS, VALID_UPDATE_KEYS

from pepdbagent import PEPDatabaseAgent
from pepdbagent.exceptions import ProjectUniqueNameError
from pepdbagent.models import AnnotationModel

from dotenv import load_dotenv


from ...models import ProjectOptional, ProjectRawModel, ForkRequest
from ....helpers import zip_conv_result, get_project_sample_names, zip_pep
from ....dependencies import (
get_db,
get_project,
get_project_annotation,
get_namespace_access_list,
verify_user_can_fork,
verify_user_can_read_project,
DEFAULT_TAG,
)
from ....const import SAMPLE_CONVERSION_FUNCTIONS, VALID_UPDATE_KEYS


load_dotenv()

project = APIRouter(
Expand Down Expand Up @@ -51,7 +64,7 @@ async def get_a_pep(
try:
raw_project = ProjectRawModel(**proj)
except Exception:
raise HTTPException(500, f"Unexpected project error!")
raise HTTPException(500, "Unexpected project error!")
return raw_project.dict(by_alias=False)
samples = [s.to_dict() for s in proj.samples]
sample_table_index = proj.sample_table_index
Expand Down Expand Up @@ -92,8 +105,8 @@ async def get_a_pep(
summary="Update a PEP",
)
async def update_a_pep(
project: str,
namespace: str,
project: str,
updated_project: ProjectOptional,
tag: Optional[str] = DEFAULT_TAG,
agent: PEPDatabaseAgent = Depends(get_db),
Expand Down Expand Up @@ -160,10 +173,10 @@ async def update_a_pep(
eido.validate_project(
new_project, "http://schema.databio.org/pep/2.1.0.yaml"
)
except Exception as e:
except Exception as _:
raise HTTPException(
status_code=400,
detail=f"",
detail="Could not validate PEP. Please check your PEP and try again.",
)

# if we get through all samples, then update project in the database
Expand Down Expand Up @@ -308,7 +321,7 @@ async def get_pep_samples(


@project.get("/config", summary="Get project configuration file")
async def get_pep_samples(
async def get_pep_config(
proj: Union[peppy.Project, dict] = Depends(get_project),
format: Optional[Literal["JSON", "String"]] = "JSON",
raw: Optional[bool] = False,
Expand Down Expand Up @@ -507,3 +520,13 @@ async def fork_pep_to_namespace(
},
status_code=202,
)


@project.get("/annotation")
async def get_project_annotation(
proj_annotation: AnnotationModel = Depends(get_project_annotation),
):
"""
Get project annotation from a certain project and namespace
"""
return proj_annotation
20 changes: 13 additions & 7 deletions pephub/routers/api/v1/search.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
from enum import Enum
from fastapi import APIRouter, __version__ as fastapi_version
from typing import List

from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from peppy import __version__ as peppy_version
from platform import python_version
from pepdbagent import PEPDatabaseAgent

from ...._version import __version__ as pephub_version
from ....dependencies import *
from sentence_transformers import SentenceTransformer
from qdrant_client import QdrantClient

from ....dependencies import (
get_db,
get_qdrant,
get_sentence_transformer,
get_namespace_access_list,
)
from ...models import SearchQuery
from ....const import DEFAULT_QDRANT_COLLECTION_NAME, ALL_VERSIONS
from ....const import DEFAULT_QDRANT_COLLECTION_NAME


from dotenv import load_dotenv
Expand Down
11 changes: 8 additions & 3 deletions pephub/routers/api/v1/user.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from fastapi import APIRouter, Request
from fastapi import APIRouter, Request, Depends
from fastapi.templating import Jinja2Templates
from fastapi.responses import RedirectResponse, JSONResponse
from platform import python_version

from pepdbagent import PEPDatabaseAgent

from ...._version import __version__ as pephub_version
from ....dependencies import *
from ....dependencies import (
get_db,
read_authorization_header,
)
from ....const import BASE_TEMPLATES_PATH

user = APIRouter(prefix="/api/v1/me", tags=["profile"])
Expand Down Expand Up @@ -36,7 +41,7 @@ def profile_data(


@user.get("/data")
def profile_data(
def profile_data2(
request: Request,
session_info=Depends(read_authorization_header),
agent: PEPDatabaseAgent = Depends(get_db),
Expand Down
Loading

0 comments on commit 111138c

Please sign in to comment.