Refactor quotas API

- Use DecodedDatabaseIdField
- Build response models with `construct` to avoid validation before serialization
This commit is contained in:
davelopez
2022-08-24 12:00:27 +02:00
committed by mvdbeek
parent ba69a677d0
commit 1e52cc293c
5 changed files with 44 additions and 49 deletions
+1 -2
View File
@@ -23,7 +23,6 @@ from galaxy.quota._schema import (
DefaultQuotaValues,
QuotaOperation,
)
from galaxy.schema.fields import EncodedDatabaseIdField
from galaxy.structured_app import StructuredApp
log = logging.getLogger(__name__)
@@ -257,5 +256,5 @@ class QuotaManager:
message += ", ".join(names)
return message
def get_quota(self, trans, id: EncodedDatabaseIdField, deleted: Optional[bool] = None) -> model.Quota:
def get_quota(self, trans, id: int, deleted: Optional[bool] = None) -> model.Quota:
return base.get_object(trans, id, "Quota", check_ownership=False, check_accessible=False, deleted=deleted)
+12 -14
View File
@@ -4,17 +4,15 @@ from typing import (
Optional,
)
from pydantic import (
BaseModel,
Field,
)
from pydantic import Field
from galaxy.schema.fields import (
EncodedDatabaseIdField,
DecodedDatabaseIdField,
ModelClassField,
)
from galaxy.schema.schema import (
GroupModel,
Model,
UserModel,
)
@@ -67,7 +65,7 @@ QuotaOperationField = Field(
)
class DefaultQuota(BaseModel): # TODO: should this replace lib.galaxy.model.DefaultQuotaAssociation at some point?
class DefaultQuota(Model): # TODO: should this replace lib.galaxy.model.DefaultQuotaAssociation at some point?
model_class: str = ModelClassField(DEFAULT_QUOTA_ASSOCIATION_MODEL_CLASS_NAME)
type: DefaultQuotaTypes = Field(
...,
@@ -80,7 +78,7 @@ class DefaultQuota(BaseModel): # TODO: should this replace lib.galaxy.model.Def
)
class UserQuota(BaseModel):
class UserQuota(Model):
model_class: str = ModelClassField(USER_QUOTA_ASSOCIATION_MODEL_CLASS_NAME)
user: UserModel = Field(
...,
@@ -89,7 +87,7 @@ class UserQuota(BaseModel):
)
class GroupQuota(BaseModel):
class GroupQuota(Model):
model_class: str = ModelClassField(GROUP_QUOTA_ASSOCIATION_MODEL_CLASS_NAME)
group: GroupModel = Field(
...,
@@ -98,11 +96,11 @@ class GroupQuota(BaseModel):
)
class QuotaBase(BaseModel):
class QuotaBase(Model):
"""Base model containing common fields for Quotas."""
model_class: str = ModelClassField(QUOTA_MODEL_CLASS_NAME)
id: EncodedDatabaseIdField = Field(
id: DecodedDatabaseIdField = Field(
...,
title="ID",
description="The `encoded identifier` of the quota.",
@@ -121,7 +119,7 @@ class QuotaSummary(QuotaBase):
)
class QuotaSummaryList(BaseModel):
class QuotaSummaryList(Model):
__root__: List[QuotaSummary] = Field(
default=[],
title="List with summary information of Quotas.",
@@ -166,7 +164,7 @@ class CreateQuotaResult(QuotaSummary):
)
class CreateQuotaParams(BaseModel):
class CreateQuotaParams(Model):
name: str = QuotaNameField
description: str = QuotaDescriptionField
amount: str = Field(
@@ -196,7 +194,7 @@ class CreateQuotaParams(BaseModel):
)
class UpdateQuotaParams(BaseModel):
class UpdateQuotaParams(Model):
name: Optional[str] = Field(
default=None,
title="Name",
@@ -243,7 +241,7 @@ class UpdateQuotaParams(BaseModel):
)
class DeleteQuotaPayload(BaseModel):
class DeleteQuotaPayload(Model):
purge: bool = Field(
False,
title="Purge",
+3 -3
View File
@@ -196,7 +196,7 @@ class Model(BaseModel):
class UserModel(Model):
"""User in a transaction context."""
id: EncodedDatabaseIdField = Field(title="ID", description="User ID")
id: DecodedDatabaseIdField = Field(title="ID", description="User ID")
username: str = Field(title="Username", description="User username")
email: str = Field(title="Email", description="User email")
active: bool = Field(title="Active", description="User is active")
@@ -205,11 +205,11 @@ class UserModel(Model):
model_class: str = ModelClassField(USER_MODEL_CLASS_NAME)
class GroupModel(BaseModel):
class GroupModel(Model):
"""User group model"""
model_class: str = ModelClassField(GROUP_MODEL_CLASS_NAME)
id: EncodedDatabaseIdField = Field(
id: DecodedDatabaseIdField = Field(
..., # Required
title="ID",
description="Encoded group ID",
+11 -9
View File
@@ -15,13 +15,13 @@ from galaxy.quota._schema import (
QuotaSummaryList,
UpdateQuotaParams,
)
from galaxy.schema.fields import EncodedDatabaseIdField
from galaxy.webapps.galaxy.services.quotas import QuotasService
from . import (
from galaxy.schema.fields import DecodedDatabaseIdField
from galaxy.webapps.galaxy.api import (
depends,
DependsOnTrans,
Router,
)
from galaxy.webapps.galaxy.services.quotas import QuotasService
log = logging.getLogger(__name__)
@@ -29,7 +29,7 @@ log = logging.getLogger(__name__)
router = Router(tags=["quotas"])
QuotaIdPathParam: EncodedDatabaseIdField = Path(
QuotaIdPathParam: DecodedDatabaseIdField = Path(
..., title="Quota ID", description="The encoded identifier of the Quota." # Required
)
@@ -64,24 +64,26 @@ class FastAPIQuota:
@router.get(
"/api/quotas/{id}",
name="quota",
summary="Displays details on a particular active quota.",
require_admin=True,
)
def show(
self, trans: ProvidesUserContext = DependsOnTrans, id: EncodedDatabaseIdField = QuotaIdPathParam
self, trans: ProvidesUserContext = DependsOnTrans, id: DecodedDatabaseIdField = QuotaIdPathParam
) -> QuotaDetails:
"""Displays details on a particular active quota."""
return self.service.show(trans, id)
@router.get(
"/api/quotas/deleted/{id}",
name="deleted_quota",
summary="Displays details on a particular quota that has been deleted.",
require_admin=True,
)
def show_deleted(
self,
trans: ProvidesUserContext = DependsOnTrans,
id: EncodedDatabaseIdField = QuotaIdPathParam,
id: DecodedDatabaseIdField = QuotaIdPathParam,
) -> QuotaDetails:
"""Displays details on a particular quota that has been deleted."""
return self.service.show(trans, id, deleted=True)
@@ -107,7 +109,7 @@ class FastAPIQuota:
def update(
self,
payload: UpdateQuotaParams,
id: EncodedDatabaseIdField = QuotaIdPathParam,
id: DecodedDatabaseIdField = QuotaIdPathParam,
trans: ProvidesUserContext = DependsOnTrans,
) -> str:
"""Updates an existing quota."""
@@ -120,7 +122,7 @@ class FastAPIQuota:
)
def delete(
self,
id: EncodedDatabaseIdField = QuotaIdPathParam,
id: DecodedDatabaseIdField = QuotaIdPathParam,
trans: ProvidesUserContext = DependsOnTrans,
payload: DeleteQuotaPayload = Body(None), # Optional
) -> str:
@@ -134,7 +136,7 @@ class FastAPIQuota:
)
def undelete(
self,
id: EncodedDatabaseIdField = QuotaIdPathParam,
id: DecodedDatabaseIdField = QuotaIdPathParam,
trans: ProvidesUserContext = DependsOnTrans,
) -> str:
"""Restores a previously deleted quota."""
+17 -21
View File
@@ -21,7 +21,7 @@ from galaxy.quota._schema import (
QuotaSummaryList,
UpdateQuotaParams,
)
from galaxy.schema.fields import EncodedDatabaseIdField
from galaxy.schema.fields import DecodedDatabaseIdField
from galaxy.security.idencoding import IdEncodingHelper
from galaxy.web import url_for
from galaxy.webapps.galaxy.services.base import ServiceBase
@@ -37,7 +37,7 @@ class QuotasService(ServiceBase):
self.quota_manager = quota_manager
def index(self, trans: ProvidesUserContext, deleted: bool = False) -> QuotaSummaryList:
"""Displays a collection (list) of quotas."""
"""Displays a list of quotas."""
rval = []
query = trans.sa_session.query(model.Quota)
if deleted:
@@ -47,29 +47,31 @@ class QuotasService(ServiceBase):
route = "quota"
query = query.filter(model.Quota.deleted == false())
for quota in query:
item = quota.to_dict(value_mapper={"id": trans.security.encode_id})
encoded_id = trans.security.encode_id(quota.id)
item["url"] = self._url_for(route, id=encoded_id)
item = quota.to_dict(value_mapper={"id": DecodedDatabaseIdField.encode})
encoded_id = DecodedDatabaseIdField.encode(quota.id)
item["url"] = url_for(route, id=encoded_id)
rval.append(item)
return QuotaSummaryList.parse_obj(rval)
return QuotaSummaryList.construct(__root__=rval)
def show(self, trans: ProvidesUserContext, id: EncodedDatabaseIdField, deleted: bool = False) -> QuotaDetails:
def show(self, trans: ProvidesUserContext, id: DecodedDatabaseIdField, deleted: bool = False) -> QuotaDetails:
"""Displays information about a quota."""
quota = self.quota_manager.get_quota(trans, id, deleted=deleted)
rval = quota.to_dict(view="element", value_mapper={"id": trans.security.encode_id, "total_disk_usage": float})
return QuotaDetails.parse_obj(rval)
rval = quota.to_dict(
view="element", value_mapper={"id": DecodedDatabaseIdField.encode, "total_disk_usage": float}
)
return QuotaDetails.construct(**rval)
def create(self, trans: ProvidesUserContext, params: CreateQuotaParams) -> CreateQuotaResult:
"""Creates a new quota."""
payload = params.dict()
self.validate_in_users_and_groups(trans, payload)
quota, message = self.quota_manager.create_quota(payload)
item = quota.to_dict(value_mapper={"id": trans.security.encode_id})
item["url"] = self._url_for("quota", id=trans.security.encode_id(quota.id))
item = quota.to_dict(value_mapper={"id": DecodedDatabaseIdField.encode})
item["url"] = url_for("quota", id=DecodedDatabaseIdField.encode(quota.id))
item["message"] = message
return CreateQuotaResult.parse_obj(item)
return CreateQuotaResult.construct(**item)
def update(self, trans: ProvidesUserContext, id: EncodedDatabaseIdField, params: UpdateQuotaParams) -> str:
def update(self, trans: ProvidesUserContext, id: DecodedDatabaseIdField, params: UpdateQuotaParams) -> str:
"""Modifies a quota."""
payload = params.dict()
self.validate_in_users_and_groups(trans, payload)
@@ -96,7 +98,7 @@ class QuotasService(ServiceBase):
return "; ".join(messages)
def delete(
self, trans: ProvidesUserContext, id: EncodedDatabaseIdField, payload: Optional[DeleteQuotaPayload] = None
self, trans: ProvidesUserContext, id: DecodedDatabaseIdField, payload: Optional[DeleteQuotaPayload] = None
) -> str:
"""Marks a quota as deleted."""
quota = self.quota_manager.get_quota(
@@ -107,7 +109,7 @@ class QuotasService(ServiceBase):
message += self.quota_manager.purge_quota(quota)
return message
def undelete(self, trans: ProvidesUserContext, id: EncodedDatabaseIdField) -> str:
def undelete(self, trans: ProvidesUserContext, id: DecodedDatabaseIdField) -> str:
"""Restores a previously deleted quota."""
quota = self.quota_manager.get_quota(trans, id, deleted=True)
return self.quota_manager.undelete_quota(quota)
@@ -146,9 +148,3 @@ class QuotasService(ServiceBase):
raise Exception(msg)
payload["in_users"] = list(map(str, new_in_users))
payload["in_groups"] = list(map(str, new_in_groups))
def _url_for(self, *args, **kargs):
try:
return url_for(*args, **kargs)
except AttributeError:
return "*deprecated attribute not filled in by FastAPI server*"