mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Merge pull request #22463 from mvdbeek/warnings/pydantic-v2-mechanical
Warnings/pydantic v2 cleanup
This commit is contained in:
@@ -8459,8 +8459,8 @@ export interface components {
|
||||
/** CollectionElementCollectionRequestUri */
|
||||
CollectionElementCollectionRequestUri: {
|
||||
/**
|
||||
* Class
|
||||
* @constant
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
class: "Collection";
|
||||
/** Collection Type */
|
||||
@@ -8479,8 +8479,8 @@ export interface components {
|
||||
/** CollectionElementDataRequestUri */
|
||||
CollectionElementDataRequestUri: {
|
||||
/**
|
||||
* Class
|
||||
* @constant
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
class: "File";
|
||||
/** Created From Basename */
|
||||
@@ -20712,7 +20712,7 @@ export interface components {
|
||||
/** Dry Run */
|
||||
dry_run: boolean;
|
||||
/** Workflow */
|
||||
workflow: string;
|
||||
workflow: unknown;
|
||||
};
|
||||
/** RegexJobMessage */
|
||||
RegexJobMessage: {
|
||||
|
||||
@@ -203,7 +203,7 @@ text-unidecode==1.3
|
||||
tinydb==4.8.2
|
||||
toml==0.10.2
|
||||
tomli==2.4.1 ; python_full_version <= '3.11'
|
||||
total-perspective-vortex==3.2.0
|
||||
total-perspective-vortex==3.2.1
|
||||
trio==0.33.0
|
||||
trio-websocket==0.12.2
|
||||
tuspy==1.1.0
|
||||
|
||||
@@ -95,7 +95,7 @@ google-auth==2.49.2
|
||||
google-cloud-batch==0.21.0
|
||||
google-genai==1.72.0
|
||||
googleapis-common-protos==1.74.0
|
||||
gravity==1.2.0
|
||||
gravity==1.2.1
|
||||
greenlet==3.4.0 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'
|
||||
griffelib==2.0.2
|
||||
groq==1.1.2
|
||||
@@ -206,7 +206,7 @@ pydantic-evals==1.80.0
|
||||
pydantic-graph==1.80.0
|
||||
pydantic-handlebars==0.1.0
|
||||
pydantic-settings==2.13.1
|
||||
pydantic-tes==0.3.0
|
||||
pydantic-tes==0.4.0
|
||||
pydicom==3.0.2
|
||||
pydot==4.0.1
|
||||
pyeventsystem==0.1.0
|
||||
|
||||
@@ -155,7 +155,7 @@ text-unidecode==1.3
|
||||
tinydb==4.8.2
|
||||
toml==0.10.2
|
||||
tomli==2.4.1 ; python_full_version <= '3.11'
|
||||
total-perspective-vortex==3.2.0
|
||||
total-perspective-vortex==3.2.1
|
||||
trio==0.33.0
|
||||
trio-websocket==0.12.2
|
||||
tuspy==1.1.0
|
||||
|
||||
@@ -33,7 +33,10 @@ from galaxy.model import (
|
||||
DatasetPermissions,
|
||||
HistoryDatasetAssociation,
|
||||
)
|
||||
from galaxy.model.db.role import get_private_role_user_emails_dict
|
||||
from galaxy.model.db.role import (
|
||||
get_private_role_user_emails_dict,
|
||||
role_name_id_pairs,
|
||||
)
|
||||
from galaxy.schema.tasks import (
|
||||
ComputeDatasetHashTaskRequest,
|
||||
PurgeDatasetsTaskRequest,
|
||||
@@ -472,23 +475,14 @@ class DatasetAssociationManager(
|
||||
)
|
||||
all_role_ids = {r.id for r in access_roles | manage_roles | modify_roles}
|
||||
private_role_emails = get_private_role_user_emails_dict(self.session(), role_ids=all_role_ids)
|
||||
encode_id = self.app.security.encode_id
|
||||
|
||||
def make_tuples(roles: set):
|
||||
tuples = []
|
||||
for role in roles:
|
||||
# use role name for non-private roles, and user.email from private rules
|
||||
displayed_name = private_role_emails.get(role.id, role.name)
|
||||
role_tuple = (displayed_name, self.app.security.encode_id(role.id))
|
||||
tuples.append(role_tuple)
|
||||
return tuples
|
||||
|
||||
access_dataset_role_list = make_tuples(access_roles)
|
||||
manage_dataset_role_list = make_tuples(manage_roles)
|
||||
|
||||
rval = dict(access_dataset_roles=access_dataset_role_list, manage_dataset_roles=manage_dataset_role_list)
|
||||
rval = dict(
|
||||
access_dataset_roles=role_name_id_pairs(access_roles, private_role_emails, encode_id),
|
||||
manage_dataset_roles=role_name_id_pairs(manage_roles, private_role_emails, encode_id),
|
||||
)
|
||||
if library_dataset is not None:
|
||||
modify_item_role_list = make_tuples(modify_roles)
|
||||
rval["modify_item_roles"] = modify_item_role_list
|
||||
rval["modify_item_roles"] = role_name_id_pairs(modify_roles, private_role_emails, encode_id)
|
||||
return rval
|
||||
|
||||
def ensure_dataset_on_disk(self, trans, dataset: U):
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from pydantic.tools import parse_obj_as
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from galaxy.datatypes._schema import (
|
||||
DatatypeConverterList,
|
||||
@@ -87,7 +87,7 @@ def view_converters(datatypes_registry: Registry) -> DatatypeConverterList:
|
||||
"tool_id": targets[target_type].id,
|
||||
}
|
||||
)
|
||||
return parse_obj_as(DatatypeConverterList, converters)
|
||||
return TypeAdapter(DatatypeConverterList).validate_python(converters)
|
||||
|
||||
|
||||
def _get_edam_details(datatypes_registry: Registry, edam_ids: dict[str, str]) -> dict[str, dict]:
|
||||
@@ -158,7 +158,7 @@ def view_visualization_mappings(
|
||||
}
|
||||
)
|
||||
|
||||
return parse_obj_as(DatatypeVisualizationMappingsList, mappings)
|
||||
return TypeAdapter(DatatypeVisualizationMappingsList).validate_python(mappings)
|
||||
|
||||
|
||||
def get_preferred_visualization(datatypes_registry: Registry, datatype_extension: str) -> Optional[dict[str, str]]:
|
||||
|
||||
@@ -47,7 +47,10 @@ from galaxy.model import (
|
||||
LibraryFolder,
|
||||
LibraryFolderPermissions,
|
||||
)
|
||||
from galaxy.model.db.role import get_private_role_user_emails_dict
|
||||
from galaxy.model.db.role import (
|
||||
get_private_role_user_emails_dict,
|
||||
role_name_id_pairs,
|
||||
)
|
||||
from galaxy.model.scoped_session import galaxy_scoped_session
|
||||
from galaxy.schema.schema import LibraryFolderContentsIndexQueryPayload
|
||||
from galaxy.security import RBACAgent
|
||||
@@ -324,20 +327,11 @@ class FolderManager:
|
||||
)
|
||||
all_role_ids = {r.id for r in modify_roles | manage_roles | add_roles}
|
||||
private_role_emails = get_private_role_user_emails_dict(trans.sa_session, role_ids=all_role_ids)
|
||||
|
||||
def make_tuples(roles: set):
|
||||
tuples = []
|
||||
for role in roles:
|
||||
# use role name for non-private roles, and user.email from private rules
|
||||
displayed_name = private_role_emails.get(role.id, role.name)
|
||||
role_tuple = (displayed_name, trans.security.encode_id(role.id))
|
||||
tuples.append(role_tuple)
|
||||
return tuples
|
||||
|
||||
encode_id = trans.security.encode_id
|
||||
return dict(
|
||||
modify_folder_role_list=make_tuples(modify_roles),
|
||||
manage_folder_role_list=make_tuples(manage_roles),
|
||||
add_library_item_role_list=make_tuples(add_roles),
|
||||
modify_folder_role_list=role_name_id_pairs(modify_roles, private_role_emails, encode_id),
|
||||
manage_folder_role_list=role_name_id_pairs(manage_roles, private_role_emails, encode_id),
|
||||
add_library_item_role_list=role_name_id_pairs(add_roles, private_role_emails, encode_id),
|
||||
)
|
||||
|
||||
def can_add_item(self, trans, folder):
|
||||
|
||||
@@ -15,6 +15,7 @@ from typing import (
|
||||
TYPE_CHECKING,
|
||||
Union,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import (
|
||||
asc,
|
||||
@@ -718,6 +719,11 @@ class HistoryExportManager:
|
||||
if "target_uri" in payload_raw:
|
||||
payload = WriteStoreToPayload.model_construct(**payload_raw)
|
||||
else:
|
||||
# UUID field bypasses validation under model_construct, so coerce the
|
||||
# stored string to UUID so Pydantic's serializer emits it cleanly.
|
||||
request_id = payload_raw.get("short_term_storage_request_id")
|
||||
if isinstance(request_id, str):
|
||||
payload_raw["short_term_storage_request_id"] = UUID(request_id)
|
||||
payload = ShortTermStoreExportPayload.model_construct(**payload_raw)
|
||||
request_data = ExportObjectRequestMetadata.model_construct(
|
||||
object_id=request_data_raw.get("object_id"),
|
||||
|
||||
@@ -27,7 +27,10 @@ from galaxy.model.db.library import (
|
||||
get_library_ids,
|
||||
get_library_permissions_by_role,
|
||||
)
|
||||
from galaxy.model.db.role import get_private_role_user_emails_dict
|
||||
from galaxy.model.db.role import (
|
||||
get_private_role_user_emails_dict,
|
||||
role_name_id_pairs,
|
||||
)
|
||||
from galaxy.util import (
|
||||
pretty_print_time_interval,
|
||||
unicodify,
|
||||
@@ -278,21 +281,12 @@ class LibraryManager:
|
||||
add_roles = self.get_add_roles(trans, library)
|
||||
all_role_ids = {r.id for r in access_roles | modify_roles | manage_roles | add_roles}
|
||||
private_role_emails = get_private_role_user_emails_dict(trans.sa_session, role_ids=all_role_ids)
|
||||
|
||||
def make_tuples(roles: set):
|
||||
tuples = []
|
||||
for role in roles:
|
||||
# use role name for non-private roles, and user.email from private rules
|
||||
displayed_name = private_role_emails.get(role.id, role.name)
|
||||
role_tuple = (displayed_name, trans.security.encode_id(role.id))
|
||||
tuples.append(role_tuple)
|
||||
return tuples
|
||||
|
||||
encode_id = trans.security.encode_id
|
||||
return dict(
|
||||
access_library_role_list=make_tuples(access_roles),
|
||||
modify_library_role_list=make_tuples(modify_roles),
|
||||
manage_library_role_list=make_tuples(manage_roles),
|
||||
add_library_item_role_list=make_tuples(add_roles),
|
||||
access_library_role_list=role_name_id_pairs(access_roles, private_role_emails, encode_id),
|
||||
modify_library_role_list=role_name_id_pairs(modify_roles, private_role_emails, encode_id),
|
||||
manage_library_role_list=role_name_id_pairs(manage_roles, private_role_emails, encode_id),
|
||||
add_library_item_role_list=role_name_id_pairs(add_roles, private_role_emails, encode_id),
|
||||
)
|
||||
|
||||
def get_access_roles(self, trans, library: Library) -> set[Role]:
|
||||
|
||||
@@ -395,7 +395,7 @@ class NotificationManager:
|
||||
if request.expiration_time is not None:
|
||||
stmt = stmt.values(expiration_time=request.expiration_time)
|
||||
if request.content is not None:
|
||||
stmt = stmt.values(content=request.content.json())
|
||||
stmt = stmt.values(content=request.content.model_dump_json())
|
||||
result = cast(CursorResult, self.sa_session.execute(stmt))
|
||||
updated_row_count = result.rowcount
|
||||
self.sa_session.commit()
|
||||
|
||||
@@ -53,7 +53,7 @@ class QuotaManager:
|
||||
return cast(DatabaseQuotaAgent, self.app.quota_agent)
|
||||
|
||||
def create_quota(self, payload: dict, decode_id=None) -> tuple[model.Quota, str]:
|
||||
params = CreateQuotaParams.parse_obj(payload)
|
||||
params = CreateQuotaParams.model_validate(payload)
|
||||
create_amount = self._parse_amount(params.amount)
|
||||
stmt = select(Quota).where(Quota.name == params.name).limit(1)
|
||||
if self.sa_session.scalars(stmt).first():
|
||||
|
||||
@@ -2394,7 +2394,7 @@ class WorkflowContentsManager(UsesAnnotations):
|
||||
return sa_session.execute(stmnt.order_by(model.StoredWorkflow.id.desc()).limit(1)).scalar()
|
||||
|
||||
|
||||
def safe_wraps(v: Any, nxt: SerializerFunctionWrapHandler) -> str:
|
||||
def safe_wraps(v: Any, nxt: SerializerFunctionWrapHandler) -> Any:
|
||||
try:
|
||||
return nxt(v)
|
||||
except Exception:
|
||||
|
||||
@@ -10230,7 +10230,7 @@ class WorkflowInvocation(Base, UsesCreateAndUpdateTime, Dictifiable, Serializabl
|
||||
|
||||
def add_message(self, message: "InvocationMessageUnion"):
|
||||
|
||||
message_dict = message.dict(
|
||||
message_dict = message.model_dump(
|
||||
exclude_unset=True,
|
||||
exclude={"history_id"}, # history_id comes in through workflow_invocation and isn't persisted in database
|
||||
)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
from collections.abc import (
|
||||
Callable,
|
||||
Iterable,
|
||||
)
|
||||
|
||||
from sqlalchemy import (
|
||||
and_,
|
||||
false,
|
||||
@@ -99,3 +104,18 @@ def get_private_role_user_emails_dict(session, role_ids: set[int] | None = None)
|
||||
stmt = stmt.where(UserRoleAssociation.role_id.in_(role_ids))
|
||||
roleid_email_tuples = session.execute(stmt).all()
|
||||
return dict(roleid_email_tuples)
|
||||
|
||||
|
||||
def role_name_id_pairs(
|
||||
roles: Iterable[Role],
|
||||
private_role_emails: dict[int, str],
|
||||
encode_id: Callable[[int], str],
|
||||
) -> list[list[str]]:
|
||||
"""Build [displayed_name, encoded_id] pairs for a set of roles.
|
||||
|
||||
Private roles render as the owning user's email (looked up via
|
||||
``private_role_emails``); other roles render as their role name.
|
||||
The return shape matches RoleNameIdTuple in the API schema, which
|
||||
is declared as list[str] (a 2-element array on the wire).
|
||||
"""
|
||||
return [[private_role_emails.get(role.id, role.name), encode_id(role.id)] for role in roles]
|
||||
|
||||
@@ -348,7 +348,7 @@ class NotificationCreatedResponse(Model):
|
||||
class NotificationUpdateRequest(Model):
|
||||
def has_changes(self) -> bool:
|
||||
"""Whether the notification update request contains at least one change."""
|
||||
return any(getattr(self, field) is not None for field in self.__fields__.keys())
|
||||
return any(getattr(self, field) is not None for field in type(self).model_fields)
|
||||
|
||||
|
||||
class UserNotificationUpdateRequest(NotificationUpdateRequest):
|
||||
|
||||
@@ -22,11 +22,13 @@ from pydantic import (
|
||||
BaseModel,
|
||||
BeforeValidator,
|
||||
ConfigDict,
|
||||
Discriminator,
|
||||
Field,
|
||||
HttpUrl,
|
||||
Json,
|
||||
model_validator,
|
||||
RootModel,
|
||||
Tag,
|
||||
UUID4,
|
||||
)
|
||||
from pydantic_core import core_schema
|
||||
@@ -1979,11 +1981,25 @@ class ExportObjectType(str, Enum):
|
||||
INVOCATION = "invocation"
|
||||
|
||||
|
||||
def _store_export_payload_discriminator(value: Any) -> str:
|
||||
# Route untagged persisted/constructed payloads to the right union branch
|
||||
# without requiring a tag field on the payload classes themselves.
|
||||
if isinstance(value, dict):
|
||||
return "short_term" if "short_term_storage_request_id" in value else "write"
|
||||
return "short_term" if isinstance(value, ShortTermStoreExportPayload) else "write"
|
||||
|
||||
|
||||
class ExportObjectRequestMetadata(Model):
|
||||
object_id: EncodedDatabaseIdField
|
||||
object_type: ExportObjectType
|
||||
user_id: Optional[EncodedDatabaseIdField] = None
|
||||
payload: Union[WriteStoreToPayload, ShortTermStoreExportPayload]
|
||||
payload: Annotated[
|
||||
Union[
|
||||
Annotated[WriteStoreToPayload, Tag("write")],
|
||||
Annotated[ShortTermStoreExportPayload, Tag("short_term")],
|
||||
],
|
||||
Discriminator(_store_export_payload_discriminator),
|
||||
]
|
||||
|
||||
|
||||
class ExportObjectResultMetadata(Model):
|
||||
|
||||
@@ -871,7 +871,7 @@ class TabularToolDataField(Dictifiable):
|
||||
rval = super().to_dict(view, value_mapper)
|
||||
rval["name"] = self.data["value"]
|
||||
rval["fields"] = self.data
|
||||
rval["base_dir"] = (self.get_base_dir(),)
|
||||
rval["base_dir"] = [self.get_base_dir()]
|
||||
rval["files"] = self.get_filesize_map(True)
|
||||
rval["fingerprint"] = self.get_fingerprint()
|
||||
return rval
|
||||
|
||||
@@ -472,7 +472,7 @@ class ToolOutputCollectionStructure:
|
||||
return collection_prototype
|
||||
|
||||
def to_dict(self):
|
||||
return self.to_model().dict()
|
||||
return self.to_model().model_dump()
|
||||
|
||||
def to_model(self) -> ToolOutputCollectionStructureModel:
|
||||
discover_datasets = []
|
||||
|
||||
@@ -145,7 +145,7 @@ def _description_from_tool_source(
|
||||
request_schema: Optional[Dict[str, Any]] = None
|
||||
if request_and_schema:
|
||||
request = request_and_schema.request.input_state
|
||||
request_schema = request_and_schema.request_schema.dict()
|
||||
request_schema = request_and_schema.request_schema.model_dump()
|
||||
|
||||
value_state_representation = raw_test_dict.get("value_state_representation", "test_case_xml")
|
||||
tool_id, tool_version = _tool_id_and_version(tool_source, tool_guid)
|
||||
|
||||
@@ -609,12 +609,7 @@ class CollectionElementCollectionRequestUri(StrictModel):
|
||||
validation_alias=AliasChoices("identifier", "name"),
|
||||
)
|
||||
collection_type: StrictStr
|
||||
elements: List[
|
||||
Annotated[
|
||||
Union["CollectionElementCollectionRequestUri", CollectionElementDataRequestUri],
|
||||
Field(discriminator="class_"),
|
||||
]
|
||||
]
|
||||
elements: List["CollectionRequestUriElement"]
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -628,14 +623,39 @@ class CollectionElementCollectionRequestUri(StrictModel):
|
||||
return data
|
||||
|
||||
|
||||
def _collection_element_discriminator(value: Any) -> Optional[str]:
|
||||
if isinstance(value, dict):
|
||||
return value.get("class") or value.get("class_")
|
||||
return getattr(value, "class_", None)
|
||||
|
||||
|
||||
# A callable Discriminator avoids the PydanticJsonSchemaWarning emitted for
|
||||
# the recursive Field(discriminator="class_") on this self-referential union;
|
||||
# json_schema_extra restores the OpenAPI discriminator metadata.
|
||||
CollectionRequestUriElement = Annotated[
|
||||
Union[
|
||||
Annotated[CollectionElementCollectionRequestUri, Tag("Collection")],
|
||||
Annotated[CollectionElementDataRequestUri, Tag("File")],
|
||||
],
|
||||
Discriminator(_collection_element_discriminator),
|
||||
Field(
|
||||
json_schema_extra={
|
||||
"discriminator": {
|
||||
"propertyName": "class",
|
||||
"mapping": {
|
||||
"Collection": "#/components/schemas/CollectionElementCollectionRequestUri",
|
||||
"File": "#/components/schemas/CollectionElementDataRequestUri",
|
||||
},
|
||||
}
|
||||
}
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class DataRequestCollectionUri(StrictModel):
|
||||
class_: Literal["Collection"] = Field(..., alias="class")
|
||||
collection_type: str
|
||||
elements: List[
|
||||
Annotated[
|
||||
Union[CollectionElementCollectionRequestUri, CollectionElementDataRequestUri], Field(discriminator="class_")
|
||||
]
|
||||
]
|
||||
elements: List[CollectionRequestUriElement]
|
||||
deferred: StrictBool = False
|
||||
name: Optional[StrictStr] = None
|
||||
src: None = Field(None, exclude=True)
|
||||
@@ -658,6 +678,7 @@ DataRequestLdda.model_rebuild()
|
||||
DataRequestDce.model_rebuild()
|
||||
DataRequestUri.model_rebuild()
|
||||
DataRequestHdca.model_rebuild()
|
||||
CollectionElementCollectionRequestUri.model_rebuild()
|
||||
DataRequestCollectionUri.model_rebuild()
|
||||
|
||||
DataOrCollectionRequestAdapter: TypeAdapter[DataOrCollectionRequest] = TypeAdapter(DataOrCollectionRequest)
|
||||
|
||||
@@ -12,6 +12,10 @@ from typing import (
|
||||
Union,
|
||||
)
|
||||
|
||||
from pydantic import (
|
||||
ConfigDict,
|
||||
with_config,
|
||||
)
|
||||
from typing_extensions import (
|
||||
Literal,
|
||||
NotRequired,
|
||||
@@ -31,6 +35,7 @@ SampleSheetColumnValueT = Union[int, float, bool, str, NoneType]
|
||||
|
||||
|
||||
# type ignore because mypy can't handle closed TypedDicts yet
|
||||
@with_config(ConfigDict(extra="forbid"))
|
||||
class SampleSheetColumnDefinition(TypedDict, closed=True): # type: ignore[call-arg]
|
||||
name: str
|
||||
description: NotRequired[Optional[str]]
|
||||
|
||||
@@ -6,7 +6,9 @@ from typing import (
|
||||
)
|
||||
|
||||
from pydantic import (
|
||||
ConfigDict,
|
||||
Field,
|
||||
with_config,
|
||||
)
|
||||
from typing_extensions import (
|
||||
Annotated,
|
||||
@@ -173,6 +175,7 @@ FieldType = Union[CwlType, List[CwlType]]
|
||||
|
||||
|
||||
# type ignore because mypy can't handle closed TypedDicts yet
|
||||
@with_config(ConfigDict(extra="forbid"))
|
||||
class FieldDict(TypedDict, closed=True): # type: ignore[call-arg]
|
||||
name: str
|
||||
type: FieldType
|
||||
|
||||
@@ -2052,12 +2052,12 @@ def src_id_to_item(
|
||||
for item in adapting:
|
||||
element = TransientCollectionAdapterDatasetInstanceElement(
|
||||
item.name,
|
||||
cast(HistoryDatasetAssociation, src_id_to_item(sa_session, item.dict(), security)),
|
||||
cast(HistoryDatasetAssociation, src_id_to_item(sa_session, item.model_dump(), security)),
|
||||
)
|
||||
elements.append(element)
|
||||
return recover_adapter(elements, adapter_model)
|
||||
else:
|
||||
value = adapting.dict()
|
||||
value = adapting.model_dump()
|
||||
src_to_class = {
|
||||
"hda": HistoryDatasetAssociation,
|
||||
"ldda": LibraryDatasetDatasetAssociation,
|
||||
@@ -2994,7 +2994,7 @@ def history_item_to_json(value, app, use_security):
|
||||
collection_adapter: Optional[CollectionAdapter] = None
|
||||
if isinstance(value, CollectionAdapter):
|
||||
collection_adapter = value
|
||||
return collection_adapter.to_adapter_model().dict()
|
||||
return collection_adapter.to_adapter_model().model_dump()
|
||||
if isinstance(value, MutableMapping) and "src" in value and "id" in value:
|
||||
return value
|
||||
elif isinstance(value, DatasetCollectionElement):
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
import os
|
||||
|
||||
import yaml
|
||||
from pydantic import parse_obj_as
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from galaxy.exceptions import ObjectNotFound
|
||||
from galaxy.navigation.data import load_root_component
|
||||
@@ -106,7 +106,7 @@ class ToursRegistryImpl:
|
||||
"requirements": self.tours[k].get("requirements"),
|
||||
}
|
||||
tours.append(tourdata)
|
||||
return parse_obj_as(TourList, tours)
|
||||
return TypeAdapter(TourList).validate_python(tours)
|
||||
|
||||
def tour_contents(self, tour_id):
|
||||
"""Return tour contents."""
|
||||
|
||||
@@ -138,7 +138,15 @@ class FastAPIConfiguration:
|
||||
def _user_to_model(user):
|
||||
if user:
|
||||
return UserModel.model_construct(
|
||||
**user.to_dict(view="element", value_mapper={"id": Security.security.encode_id})
|
||||
**user.to_dict(
|
||||
view="element",
|
||||
value_mapper={
|
||||
"id": Security.security.encode_id,
|
||||
# Dictifiable otherwise stringifies datetimes via isoformat(); keep the
|
||||
# datetime object so UserModel.last_password_change serializes correctly.
|
||||
"last_password_change": lambda v: v,
|
||||
},
|
||||
)
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import (
|
||||
Optional,
|
||||
Union,
|
||||
)
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import Query
|
||||
|
||||
@@ -20,6 +21,8 @@ from galaxy.schema.schema import (
|
||||
ExportObjectResultMetadata,
|
||||
ExportTaskListResponse,
|
||||
ObjectExportTaskResponse,
|
||||
ShortTermStoreExportPayload,
|
||||
WriteStoreToPayload,
|
||||
)
|
||||
from galaxy.webapps.galaxy.api import (
|
||||
depends,
|
||||
@@ -105,11 +108,26 @@ class FastAPIExports:
|
||||
request_data_raw = metadata.get("request_data", {})
|
||||
result_data_raw = metadata.get("result_data")
|
||||
|
||||
payload_raw = request_data_raw.get("payload") or {}
|
||||
# Pick the right payload flavour by presence of target_uri
|
||||
# (WriteStoreToPayload has it, ShortTermStoreExportPayload does not).
|
||||
if "target_uri" in payload_raw:
|
||||
payload: Union[WriteStoreToPayload, ShortTermStoreExportPayload] = WriteStoreToPayload.model_construct(
|
||||
**payload_raw
|
||||
)
|
||||
else:
|
||||
# UUID field bypasses validation under model_construct, so coerce the
|
||||
# stored string to UUID so Pydantic's serializer emits it cleanly.
|
||||
request_id = payload_raw.get("short_term_storage_request_id")
|
||||
if isinstance(request_id, str):
|
||||
payload_raw["short_term_storage_request_id"] = UUID(request_id)
|
||||
payload = ShortTermStoreExportPayload.model_construct(**payload_raw)
|
||||
|
||||
request_data = ExportObjectRequestMetadata.model_construct(
|
||||
object_id=request_data_raw.get("object_id"),
|
||||
object_type=request_data_raw.get("object_type"),
|
||||
user_id=request_data_raw.get("user_id"),
|
||||
payload=request_data_raw.get("payload"),
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
result_data = None
|
||||
|
||||
@@ -22,6 +22,10 @@ from fastapi import (
|
||||
Response,
|
||||
status,
|
||||
)
|
||||
from pydantic import (
|
||||
Discriminator,
|
||||
Tag,
|
||||
)
|
||||
from pydantic.fields import Field
|
||||
from pydantic.main import BaseModel
|
||||
|
||||
@@ -186,6 +190,19 @@ IndexExportsAcceptHeader = Annotated[
|
||||
]
|
||||
|
||||
|
||||
def _index_exports_response_discriminator(value: Any) -> str:
|
||||
return "tasks" if isinstance(value, ExportTaskListResponse) else "jobs"
|
||||
|
||||
|
||||
IndexExportsResponse = Annotated[
|
||||
Union[
|
||||
Annotated[JobExportHistoryArchiveListResponse, Tag("jobs")],
|
||||
Annotated[ExportTaskListResponse, Tag("tasks")],
|
||||
],
|
||||
Discriminator(_index_exports_response_discriminator),
|
||||
]
|
||||
|
||||
|
||||
@router.cbv
|
||||
class FastAPIHistories:
|
||||
service: HistoriesService = depends(HistoriesService)
|
||||
@@ -551,7 +568,7 @@ class FastAPIHistories:
|
||||
limit: Optional[int] = LimitQueryParam,
|
||||
offset: Optional[int] = OffsetQueryParam,
|
||||
accept: IndexExportsAcceptHeader = "application/json",
|
||||
) -> Union[JobExportHistoryArchiveListResponse, ExportTaskListResponse]:
|
||||
) -> IndexExportsResponse:
|
||||
"""
|
||||
By default the legacy job-based history exports (jeha) are returned.
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ class DatasetCollectionsService(ServiceBase, UsesLibraryMixinItems):
|
||||
:returns: element view of new dataset collection
|
||||
"""
|
||||
# TODO: Error handling...
|
||||
create_params = api_payload_to_create_params(payload.dict(exclude_unset=True, by_alias=True))
|
||||
create_params = api_payload_to_create_params(payload.model_dump(exclude_unset=True, by_alias=True))
|
||||
if payload.instance_type == "history":
|
||||
if payload.history_id is None:
|
||||
raise exceptions.RequestParameterInvalidException("Parameter history_id is required.")
|
||||
|
||||
@@ -395,7 +395,7 @@ class HistoriesService(ServiceBase, ConsumesModelStores, ServesExportStores):
|
||||
duration=short_term_storage_target.duration,
|
||||
user=trans.async_request_user,
|
||||
export_association_id=export_association.id,
|
||||
**payload.dict(),
|
||||
**payload.model_dump(),
|
||||
)
|
||||
result = prepare_history_download.delay(request=request, task_user_id=getattr(trans.user, "id", None))
|
||||
task_summary = async_task_summary(result)
|
||||
@@ -412,7 +412,7 @@ class HistoriesService(ServiceBase, ConsumesModelStores, ServesExportStores):
|
||||
user=trans.async_request_user,
|
||||
history_id=history.id,
|
||||
export_association_id=export_association.id,
|
||||
**payload.dict(),
|
||||
**payload.model_dump(),
|
||||
)
|
||||
result = write_history_to.delay(request=request, task_user_id=getattr(trans.user, "id", None))
|
||||
task_summary = async_task_summary(result)
|
||||
|
||||
@@ -31,6 +31,7 @@ from galaxy.schema.schema import (
|
||||
ShareWithStatus,
|
||||
SharingOptions,
|
||||
SharingStatus,
|
||||
UserEmail,
|
||||
UserIdentifier,
|
||||
)
|
||||
from galaxy.webapps.galaxy.services.notifications import NotificationService
|
||||
@@ -108,7 +109,10 @@ class ShareableService:
|
||||
users, errors = self._get_users(trans, payload.user_ids)
|
||||
extra, users_to_notify = self._share_with_options(trans, item, users, errors, payload.share_option)
|
||||
base_status = self._get_sharing_status(trans, item)
|
||||
status = self.share_with_status_cls.model_construct(**base_status.model_dump(), extra=extra)
|
||||
# Use dict() for a shallow field copy so nested UserEmail instances in
|
||||
# users_shared_with survive; model_dump() would deep-serialize them to
|
||||
# dicts that model_construct won't re-validate back to UserEmail.
|
||||
status = self.share_with_status_cls.model_construct(**dict(base_status), extra=extra)
|
||||
status.errors.extend(errors)
|
||||
galaxy_url = str(trans.url_builder("/", qualified=True)).rstrip("/") if trans.url_builder else None
|
||||
self._send_notification_to_users(users_to_notify, item, status, galaxy_url)
|
||||
@@ -138,7 +142,7 @@ class ShareableService:
|
||||
status = self.serializer.serialize_to_view(
|
||||
item, user=trans.user, trans=trans, default_view="sharing", encode_id=False
|
||||
)
|
||||
status["users_shared_with"] = [{"id": a.user.id, "email": a.user.email} for a in item.users_shared_with]
|
||||
status["users_shared_with"] = [UserEmail(id=a.user.id, email=a.user.email) for a in item.users_shared_with]
|
||||
return SharingStatus(**status)
|
||||
|
||||
def _get_users(self, trans, emails_or_ids: list[UserIdentifier]) -> tuple[set[User], set[str]]:
|
||||
|
||||
@@ -59,4 +59,4 @@ class ModelCache:
|
||||
if not os.path.exists(parent_directory):
|
||||
os.makedirs(parent_directory)
|
||||
with open(cache_target, "w") as f:
|
||||
json.dump(model_object.dict(), f)
|
||||
json.dump(model_object.model_dump(), f)
|
||||
|
||||
@@ -559,7 +559,7 @@ class StandaloneToolShedInstallationClient(ToolShedInstallationClient):
|
||||
installed_repository.id,
|
||||
)
|
||||
response = CheckForUpdatesResponse(message=message, status=status)
|
||||
response_dict = response.dict()
|
||||
response_dict = response.model_dump()
|
||||
if verify_no_updates:
|
||||
assert "message" in response_dict
|
||||
message = response_dict["message"]
|
||||
|
||||
@@ -4,6 +4,7 @@ asyncio_mode = auto
|
||||
log_level = DEBUG
|
||||
consider_namespace_packages = true
|
||||
filterwarnings =
|
||||
error::pydantic.warnings.PydanticDeprecatedSince20
|
||||
ignore::DeprecationWarning:pkg_resources
|
||||
ignore::DeprecationWarning:refgenconf
|
||||
ignore::UserWarning:refgenconf
|
||||
|
||||
Reference in New Issue
Block a user