Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 0.14.2 #403

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pephub/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.14.1"
__version__ = "0.14.2"
6 changes: 6 additions & 0 deletions pephub/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,9 @@
)

ARCHIVE_URL_PATH = "https://cloud2.databio.org/pephub/"

MAX_PROCESSED_PROJECT_SIZE = 5000

MAX_STANDARDIZED_PROJECT_SIZE = 100

BEDMS_REPO_URL = "databio/attribute-standardizer-model6"
61 changes: 47 additions & 14 deletions pephub/routers/api/v1/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
ConfigResponseModel,
StandardizerResponse,
)
from ....const import (
MAX_PROCESSED_PROJECT_SIZE,
BEDMS_REPO_URL,
MAX_STANDARDIZED_PROJECT_SIZE,
)
from .helpers import verify_updated_project

from bedms import AttrStandardizer
Expand Down Expand Up @@ -93,7 +98,12 @@ async def get_namespace_projects_list(
return agent.annotation.get_by_rp_list(registry_paths=paths, admin=namespace_access)


@project.get("", summary="Fetch a PEP", response_model=ProjectRawRequest)
@project.get(
"",
summary="Fetch a PEP",
response_model=ProjectRawRequest,
response_model_by_alias=False,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, so I can learn... whats this for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pydantic has aliases, and by default, fastapi in openAPI docs is using them. I don't want to use aliases, but actual names, that's why I set it to False

)
async def get_a_pep(
proj: dict = Depends(get_project),
):
Expand All @@ -110,7 +120,7 @@ async def get_a_pep(
"""
try:
raw_project = ProjectRawModel(**proj)
return raw_project.model_dump(by_alias=False)
return raw_project
except Exception:
raise HTTPException(500, "Unexpected project error!")

Expand Down Expand Up @@ -255,6 +265,11 @@ async def get_pep_samples(
)

if isinstance(proj, dict):
if len(proj["_sample_dict"]) > 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)

if format == "json":
Expand All @@ -275,6 +290,11 @@ 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:
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)
return [sample.to_dict() for sample in proj.samples]

Expand Down Expand Up @@ -1166,33 +1186,46 @@ def delete_full_history(
response_model=StandardizerResponse,
)
async def get_standardized_cols(
pep: peppy.Project = Depends(get_project),
pep: dict = Depends(get_project),
schema: str = "",
):
"""
Standardize PEP metadata column headers using BEDmess.
Standardize PEP metadata column headers using BEDms.

:param namespace: pep: PEP string to be standardized
:param pep: PEP string to be standardized
:param schema: Schema for AttrStandardizer

:return dict: Standardized results
"""

if schema == "":
if schema == "" or schema not in ["ENCODE", "BEDBASE", "FAIRTRACKS"]:
raise HTTPException(
code=500,
detail="Schema is required! Available schemas are ENCODE and Fairtracks",
status_code=404,
detail="Schema not available! Available schemas are ENCODE, BEDBASE and FAIRTRACKS.",
)

if len(pep["_sample_dict"]) > MAX_STANDARDIZED_PROJECT_SIZE:
# raise HTTPException(
# status_code=400,
# detail=f"Project is too large. Cannot standardize. "
# f"Limit is {MAX_STANDARDIZED_PROJECT_SIZE} samples.",
# )
prj = peppy.Project.from_dict(
{
"_config": pep["_config"],
"_sample_dict": pep["_sample_dict"][:50],
}
)
return {}

prj = peppy.Project.from_dict(pep)
model = AttrStandardizer(schema)
else:
prj = peppy.Project.from_dict(pep)
model = AttrStandardizer(repo_id=BEDMS_REPO_URL, model_name=schema.lower())

try:
results = model.standardize(pep=prj)
except Exception:
except Exception as e:
_LOGGER.error(f"Error standardizing PEP. {e}")
raise HTTPException(
code=400,
status_code=400,
detail=f"Error standardizing PEP.",
)

Expand Down
11 changes: 11 additions & 0 deletions pephub/routers/eido/eido.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from ...dependencies import DEFAULT_TAG, get_db
from ...helpers import parse_user_file_upload, split_upload_files_on_init_file
from ...const import MAX_PROCESSED_PROJECT_SIZE

schemas_url = "https://schema.databio.org/list.json"
schemas_to_test = requests.get(schemas_url).json()
Expand Down Expand Up @@ -84,6 +85,16 @@ async def validate(
if pep_registry is not None:
namespace, name, tag = registry_path_converter(pep_registry)
tag = tag or DEFAULT_TAG

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."],
}

p = agent.project.get(namespace, name, tag, raw=False)
else:
init_file = parse_user_file_upload(pep_files)
Expand Down
2 changes: 1 addition & 1 deletion requirements/requirements-all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ fastembed
numpy<2.0.0
slowapi
cachetools>=4.2.4
bedms>=0.1.0
bedms>=0.2.0
30 changes: 14 additions & 16 deletions web/src/components/modals/validation-result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,29 +70,27 @@ export const ValidationResultModal = (props: Props) => {
)}
</>
) : (
<span className="d-flex align-items-center gap-1">
Select a Schema
</span>
<span className="d-flex align-items-center gap-1">Select a Schema</span>
)}
</h1>
</Modal.Header>
<Modal.Body>
{currentSchema && (
<>
{validationResult?.valid ? (
<p>Your PEP is valid against the schema.</p>
) : (
<Fragment>
<p>You PEP is invalid against the schema.</p>
<p>Validation result:</p>
<pre>
<code>{JSON.stringify(validationResult, null, 2)}</code>
</pre>
</Fragment>
)}
{validationResult?.valid ? (
<p>Your PEP is valid against the schema.</p>
) : (
<Fragment>
<p>Your PEP is invalid against the schema.</p>
<p>Validation result:</p>
<pre>
<code>{JSON.stringify(validationResult, null, 2)}</code>
</pre>
</Fragment>
)}
</>
)}

<form className="mb-1">
{currentSchema ? (
<label className="mt-1 fw-bold">Change schemas here:</label>
Expand Down Expand Up @@ -126,7 +124,7 @@ export const ValidationResultModal = (props: Props) => {
</Modal.Body>
<Modal.Footer>
<div className="d-flex align-items-center justify-content-between w-100">
{ currentSchema && (
{currentSchema && (
<div className="d-flex align-items-center">
<a href={`/schemas/${props.currentSchema}`}>
<button className="btn btn-sm btn-outline-dark">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ type ProjectValidationAndEditButtonsProps = {
filteredSamples: string[];
};

const MAX_SAMPLES_FOR_VALIDATION = 5000;

export const ProjectValidationAndEditButtons = (props: ProjectValidationAndEditButtonsProps) => {
const { isDirty, isUpdatingProject, reset, handleSubmit, filteredSamples } = props;
const { user } = useSession();

const { namespace, projectName, tag } = useProjectPage();

const { data: projectInfo } = useProjectAnnotation(namespace, projectName, tag);
const shouldValidate: boolean = (projectInfo?.number_of_samples || 0) > MAX_SAMPLES_FOR_VALIDATION;

const projectValidationQuery = useValidation({
pepRegistry: `${namespace}/${projectName}:${tag}`,
schema_registry: projectInfo?.pep_schema || 'pep/2.0.0',
Expand All @@ -48,6 +52,7 @@ export const ProjectValidationAndEditButtons = (props: ProjectValidationAndEditB
schemaRegistry={projectSchema}
isValidating={projectValidationQuery.isLoading}
validationResult={validationResult}
shouldValidate={shouldValidate}
/>
</div>
<div className="ps-1">
Expand Down
82 changes: 41 additions & 41 deletions web/src/components/project/validation/validation-result.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ import { useState } from 'react';
import { OverlayTrigger, Tooltip } from 'react-bootstrap';

import { useValidation } from '../../../hooks/queries/useValidation';
import { ValidationResultModal } from '../../modals/validation-result';
import { StatusIcon } from '../../badges/status-icons';

import { ValidationResultModal } from '../../modals/validation-result';

type Props = {
schemaRegistry: string | undefined;
isValidating: boolean;
validationResult: ReturnType<typeof useValidation>['data'];
shouldValidate: boolean;
};

export const ValidationResult = (props: Props) => {
const { validationResult, isValidating, schemaRegistry } = props;
const { validationResult, isValidating, schemaRegistry, shouldValidate } = props;

const [validationModalIsOpen, setValidationModalIsOpen] = useState(false);

let wrapperClassName = 'py-1 px-2 rounded-1 bg-opacity-10 validation-button';
if (isValidating) {
if (shouldValidate) {
wrapperClassName += ' border border-warning text-warning bg-warning';
} else if (isValidating) {
khoroshevskyi marked this conversation as resolved.
Show resolved Hide resolved
wrapperClassName += ' border border-warning text-warning bg-warning';
} else if (validationResult?.valid) {
wrapperClassName += ' border border-success text-success bg-success';
Expand All @@ -37,45 +39,43 @@ export const ValidationResult = (props: Props) => {
delay={{ show: 250, hide: 500 }}
trigger={['hover']}
>

{ schemaRegistry ? (
<button
disabled={isValidating}
onClick={() => {
setValidationModalIsOpen(true);
}}
className={wrapperClassName}
>
<div className="d-flex flex-row align-items-center gap-2 text-sm py-0">
{isValidating ? (
<span className="bg-warning text-warning rounded-pill validation-badge"></span>
) : validationResult?.valid ? (
<span className="bg-success text-success rounded-pill validation-badge"></span>
) : (
<span className="bg-danger text-danger rounded-pill validation-badge"></span>
)}
<span
className="text-truncate"
style={{
maxWidth: '200px',
}}
>
{schemaRegistry}
</span>
</div>
</button>
) : (
<button
className="d-flex align-items-center bg-warning bg-opacity-10 px-2 rounded-1 validation-button border border-warning text-sm"
onClick={() => {
{schemaRegistry ? (
<button
disabled={isValidating}
onClick={() => {
setValidationModalIsOpen(true);
}}
>
<StatusIcon className="me-1" variant="warning" />
<span className="text-warning">No Schema</span>
</button>
)}

className={wrapperClassName}
>
<div className="d-flex flex-row align-items-center gap-2 text-sm py-0">
{isValidating || shouldValidate ? (
<span className="bg-warning text-warning rounded-pill validation-badge"></span>
) : validationResult?.valid ? (
<span className="bg-success text-success rounded-pill validation-badge"></span>
) : (
<span className="bg-danger text-danger rounded-pill validation-badge"></span>
)}
<span
className="text-truncate"
style={{
maxWidth: '200px',
}}
>
{schemaRegistry}
</span>
</div>
</button>
) : (
<button
className="d-flex align-items-center bg-warning bg-opacity-10 px-2 rounded-1 validation-button border border-warning text-sm"
onClick={() => {
setValidationModalIsOpen(true);
}}
>
<StatusIcon className="me-1" variant="warning" />
<span className="text-warning">No Schema</span>
</button>
)}
</OverlayTrigger>
<ValidationResultModal
show={validationModalIsOpen}
Expand Down
3 changes: 2 additions & 1 deletion web/src/components/tables/sample-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ export const SampleTable = (props: Props) => {
'copy',
'cut',
]}
multiColumnSorting={true}
multiColumnSorting={false}
columnSorting={false}
filters={true}
rowHeaders={true}
beforeRenderer={addClassesToRows}
Expand Down
Loading