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

Added pop column #103

Merged
merged 4 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 2 additions & 3 deletions pepdbagent/db_utils.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import datetime
import logging
from typing import Any, Optional, List
from typing import Optional, List

from sqlalchemy import (
BigInteger,
FetchedValue,
PrimaryKeyConstraint,
Result,
Select,
String,
event,
select,
TIMESTAMP,
ForeignKey,
ForeignKeyConstraint,
UniqueConstraint,
)
from sqlalchemy.dialects.postgresql import JSON
Expand Down Expand Up @@ -92,6 +90,7 @@ class Projects(Base):
onupdate=deliver_update_date, default=deliver_update_date
)
pep_schema: Mapped[Optional[str]]
pop: Mapped[Optional[bool]] = mapped_column(default=False)
samples_mapping: Mapped[List["Samples"]] = relationship(
back_populates="sample_mapping", cascade="all, delete-orphan"
)
Expand Down
63 changes: 30 additions & 33 deletions pepdbagent/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# file with pydantic models
import datetime
from typing import List, Optional, Union

import peppy
from pydantic import BaseModel, Extra, Field, validator
from pydantic import BaseModel, Extra, Field, validator, ConfigDict, field_validator


class AnnotationModel(BaseModel):
Expand All @@ -21,12 +18,14 @@ class AnnotationModel(BaseModel):
submission_date: Optional[str]
digest: Optional[str]
pep_schema: Optional[str]
pop: Optional[bool] = False

class Config:
allow_population_by_field_name = True
validate_assignment = True
model_config = ConfigDict(
validate_assignment=True,
populate_by_name=True,
)

@validator("is_private")
@field_validator("is_private")
def is_private_should_be_bool(cls, v):
if not isinstance(v, bool):
return False
Expand Down Expand Up @@ -71,20 +70,20 @@ class UpdateItems(BaseModel):
Model used for updating individual items in db
"""

name: Optional[str]
description: Optional[str]
tag: Optional[str]
is_private: Optional[bool]
pep_schema: Optional[str]
digest: Optional[str]
config: Optional[dict]
samples: Optional[List[dict]]
subsamples: Optional[List[List[dict]]]
description: Optional[str]

class Config:
arbitrary_types_allowed = True
extra = Extra.forbid
name: Optional[str] = None
description: Optional[str] = None
tag: Optional[str] = None
is_private: Optional[bool] = None
pep_schema: Optional[str] = None
digest: Optional[str] = None
config: Optional[dict] = None
samples: Optional[List[dict]] = None
subsamples: Optional[List[List[dict]]] = None

model_config = ConfigDict(
arbitrary_types_allowed=True,
extra="forbid",
)

@property
def number_of_samples(self) -> Union[int, None]:
Expand All @@ -99,37 +98,35 @@ class UpdateModel(BaseModel):
Model used for updating individual items and creating sql string in the code
"""

config: Optional[dict]
config: Optional[dict] = None
name: Optional[str] = None
tag: Optional[str] = None
private: Optional[bool] = Field(alias="is_private")
digest: Optional[str]
number_of_samples: Optional[int]
pep_schema: Optional[str]
private: Optional[bool] = Field(alias="is_private", default=None)
digest: Optional[str] = None
number_of_samples: Optional[int] = None
pep_schema: Optional[str] = None
description: Optional[str] = ""
# last_update_date: Optional[datetime.datetime] = datetime.datetime.now(datetime.timezone.utc)

@validator("tag", "name")
@field_validator("tag", "name")
def value_must_not_be_empty(cls, v):
if "" == v:
return None
return v

@validator("tag", "name")
@field_validator("tag", "name")
def value_must_be_lowercase(cls, v):
if v:
return v.lower()
return v

@validator("tag", "name")
@field_validator("tag", "name")
def value_should_not_contain_question(cls, v):
if "?" in v:
return ValueError("Question mark (?) is prohibited in name and tag.")
return v

class Config:
extra = Extra.forbid
allow_population_by_field_name = True
model_config = ConfigDict(populate_by_name=True, extra="forbid")


class NamespaceInfo(BaseModel):
Expand Down
9 changes: 6 additions & 3 deletions pepdbagent/modules/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
from datetime import datetime
from typing import List, Literal, Optional, Union

from sqlalchemy import Engine, and_, func, or_, select
from sqlalchemy.exc import IntegrityError
from sqlalchemy import and_, func, or_, select
from sqlalchemy.sql.selectable import Select

from pepdbagent.const import (
Expand Down Expand Up @@ -189,6 +188,7 @@ def _get_single_annotation(
Projects.last_update_date,
Projects.digest,
Projects.pep_schema,
Projects.pop,
).where(
and_(
Projects.name == name,
Expand All @@ -214,6 +214,7 @@ def _get_single_annotation(
last_update_date=str(query_result.last_update_date),
digest=query_result.digest,
pep_schema=query_result.pep_schema,
pop=query_result.pop,
)
_LOGGER.info(f"Annotation of the project '{namespace}/{name}:{tag}' has been found!")
return annot
Expand Down Expand Up @@ -307,6 +308,7 @@ def _get_projects(
Projects.last_update_date,
Projects.digest,
Projects.pep_schema,
Projects.pop,
).select_from(Projects)

statement = self._add_condition(
Expand Down Expand Up @@ -337,6 +339,7 @@ def _get_projects(
last_update_date=str(result.last_update_date),
digest=result.digest,
pep_schema=result.pep_schema,
pop=result.pop,
)
)
return results_list
Expand Down Expand Up @@ -445,7 +448,7 @@ def _add_date_filter_if_provided(
return statement
else:
if filter_by:
_LOGGER.warning(f"filter_start_date was not provided, skipping filter...")
_LOGGER.warning("filter_start_date was not provided, skipping filter...")
return statement

def get_project_number_in_namespace(
Expand Down
51 changes: 33 additions & 18 deletions pepdbagent/modules/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@
from typing import Union, List, NoReturn

import peppy
from sqlalchemy import Engine, and_, delete, insert, or_, select, update
from sqlalchemy import and_, delete, select
from sqlalchemy.exc import IntegrityError, NoResultFound
from sqlalchemy.orm import Session
from sqlalchemy import Select

from peppy.const import SAMPLE_RAW_DICT_KEY, SUBSAMPLE_RAW_LIST_KEY, CONFIG_KEY

from pepdbagent.const import *
from pepdbagent.const import (
DEFAULT_TAG,
DESCRIPTION_KEY,
NAME_KEY,
PKG_NAME,
)

from pepdbagent.db_utils import Projects, Samples, Subsamples, BaseEngine
from pepdbagent.exceptions import ProjectNotFoundError, ProjectUniqueNameError
from pepdbagent.models import UpdateItems, UpdateModel
Expand Down Expand Up @@ -200,6 +206,7 @@ def create(
tag: str = DEFAULT_TAG,
description: str = None,
is_private: bool = False,
pop: bool = False,
pep_schema: str = None,
overwrite: bool = False,
update_only: bool = False,
Expand All @@ -215,6 +222,7 @@ def create(
:param tag: tag (or version) of the project.
:param is_private: boolean value if the project should be visible just for user that creates it.
:param pep_schema: assign PEP to a specific schema. [DefaultL: None]
khoroshevskyi marked this conversation as resolved.
Show resolved Hide resolved
:param pop: if project is a pep of peps (POP) [Default: False]
:param overwrite: if project exists overwrite the project, otherwise upload it.
[Default: False - project won't be overwritten if it exists in db]
:param update_only: if project exists overwrite it, otherwise do nothing. [Default: False]
Expand All @@ -232,7 +240,7 @@ def create(
elif proj_dict[CONFIG_KEY][NAME_KEY]:
proj_name = proj_dict[CONFIG_KEY][NAME_KEY].lower()
else:
raise ValueError(f"Name of the project wasn't provided. Project will not be uploaded.")
raise ValueError("Name of the project wasn't provided. Project will not be uploaded.")

proj_dict[CONFIG_KEY][NAME_KEY] = proj_name

Expand All @@ -251,6 +259,7 @@ def create(
private=is_private,
pep_schema=pep_schema,
description=description,
pop=pop,
)
return None
else:
Expand All @@ -268,6 +277,7 @@ def create(
last_update_date=datetime.datetime.now(datetime.timezone.utc),
pep_schema=pep_schema,
description=description,
pop=pop,
)

self._add_samples_to_project(new_prj, proj_dict[SAMPLE_RAW_DICT_KEY])
Expand Down Expand Up @@ -299,9 +309,9 @@ def create(

else:
raise ProjectUniqueNameError(
f"Namespace, name and tag already exists. Project won't be "
f"uploaded. Solution: Set overwrite value as True"
f" (project will be overwritten), or change tag!"
"Namespace, name and tag already exists. Project won't be "
"uploaded. Solution: Set overwrite value as True"
" (project will be overwritten), or change tag!"
)

def _overwrite(
Expand All @@ -315,6 +325,7 @@ def _overwrite(
private: bool = False,
pep_schema: str = None,
description: str = "",
pop: bool = False,
Copy link
Member

Choose a reason for hiding this comment

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

I'm trying to think of any consequences that might occur from us defaulting to False here, but I can't immediately think of any...

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it should cause any problems. pop is like private, but you can't change it. If you set it, it should be pop. So it's only metadata for pephub, nothing else. All the configurations will be then done in pephub (I am against this approach, but decision was made)

) -> None:
"""
Update existing project by providing all necessary information.
Expand All @@ -328,6 +339,7 @@ def _overwrite(
:param private: boolean value if the project should be visible just for user that creates it.
:param pep_schema: assign PEP to a specific schema. [DefaultL: None]
:param description: project description
:param pop: if project is a pep of peps, simply POP [Default: False]
:return: None
"""
proj_name = proj_name.lower()
Expand Down Expand Up @@ -414,6 +426,7 @@ def update(
update_dict["subsamples"] = project_dict[SUBSAMPLE_RAW_LIST_KEY]

update_values = UpdateItems(**update_dict)
# update_values = UpdateItems(tag="ff")
khoroshevskyi marked this conversation as resolved.
Show resolved Hide resolved

update_values = self.__create_update_dict(update_values)

Expand Down Expand Up @@ -476,64 +489,64 @@ def __create_update_dict(update_values: UpdateItems) -> dict:
updating values
:return: unified update dict
"""
update_final = UpdateModel()
update_final = UpdateModel.model_construct()

if update_values.name is not None:
if update_values.config is not None:
update_values.config[NAME_KEY] = update_values.name
update_final = UpdateModel(
name=update_values.name,
**update_final.dict(exclude_unset=True),
**update_final.model_dump(exclude_unset=True),
)

if update_values.description is not None:
if update_values.config is not None:
update_values.config[DESCRIPTION_KEY] = update_values.description
update_final = UpdateModel(
description=update_values.description,
**update_final.dict(exclude_unset=True),
**update_final.model_dump(exclude_unset=True),
)
if update_values.config is not None:
update_final = UpdateModel(
config=update_values.config, **update_final.dict(exclude_unset=True)
config=update_values.config, **update_final.model_dump(exclude_unset=True)
)
name = update_values.config.get(NAME_KEY)
description = update_values.config.get(DESCRIPTION_KEY)
if name:
update_final = UpdateModel(
name=name,
**update_final.dict(exclude_unset=True, exclude={NAME_KEY}),
**update_final.model_dump(exclude_unset=True, exclude={NAME_KEY}),
)
if description:
update_final = UpdateModel(
description=description,
**update_final.dict(exclude_unset=True, exclude={DESCRIPTION_KEY}),
**update_final.model_dump(exclude_unset=True, exclude={DESCRIPTION_KEY}),
)

if update_values.tag is not None:
update_final = UpdateModel(
tag=update_values.tag, **update_final.dict(exclude_unset=True)
tag=update_values.tag, **update_final.model_dump(exclude_unset=True)
)

if update_values.is_private is not None:
update_final = UpdateModel(
is_private=update_values.is_private,
**update_final.dict(exclude_unset=True),
**update_final.model_dump(exclude_unset=True),
)

if update_values.pep_schema is not None:
update_final = UpdateModel(
pep_schema=update_values.pep_schema,
**update_final.dict(exclude_unset=True),
**update_final.model_dump(exclude_unset=True),
)

if update_values.number_of_samples is not None:
update_final = UpdateModel(
number_of_samples=update_values.number_of_samples,
**update_final.dict(exclude_unset=True),
**update_final.model_dump(exclude_unset=True),
)

return update_final.dict(exclude_unset=True, exclude_none=True)
return update_final.model_dump(exclude_unset=True, exclude_none=True)

def exists(
self,
Expand Down Expand Up @@ -565,7 +578,7 @@ def exists(
return False

@staticmethod
def _add_samples_to_project(projects_sa: Projects, samples: List[dict]) -> NoReturn:
def _add_samples_to_project(projects_sa: Projects, samples: List[dict]) -> None:
"""
Add samples to the project sa object. (With commit this samples will be added to the 'samples table')
:param projects_sa: Projects sa object, in open session
Expand All @@ -575,6 +588,8 @@ def _add_samples_to_project(projects_sa: Projects, samples: List[dict]) -> NoRet
for row_number, sample in enumerate(samples):
projects_sa.samples_mapping.append(Samples(sample=sample, row_number=row_number))

return None

@staticmethod
def _add_subsamples_to_project(
projects_sa: Projects, subsamples: List[List[dict]]
Expand Down
2 changes: 1 addition & 1 deletion pepdbagent/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import ubiquerg
from peppy.const import SAMPLE_RAW_DICT_KEY

from .exceptions import IncorrectDateFormat, RegistryPathError
from .exceptions import RegistryPathError


def is_valid_registry_path(rpath: str) -> bool:
Expand Down
Loading
Loading