Skip to content
Merged
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 client-api/src/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type DatasetStorageDetails = components["schemas"]["DatasetStorageDetails
export type DatasetCollectionAttributes = components["schemas"]["DatasetCollectionAttributesResult"];
export type ConcreteObjectStoreModel = components["schemas"]["ConcreteObjectStoreModel"];
export type MessageException = components["schemas"]["MessageExceptionModel"];
export type DatasetHash = components["schemas"]["DatasetHash-Output"];
export type DatasetHash = components["schemas"]["DatasetHash"];
export type DatasetSource = components["schemas"]["DatasetSource"];
export type DatasetTransform = components["schemas"]["DatasetSourceTransform"];
export type StoreExportPayload = components["schemas"]["StoreExportPayload"];
Expand Down
2 changes: 1 addition & 1 deletion client/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export function canMutateHistory(history: AnyHistory): boolean {
return !history.purged && !history.archived;
}

export type DatasetHash = components["schemas"]["DatasetHash-Output"];
export type DatasetHash = components["schemas"]["DatasetHash"];

export type DatasetSource = components["schemas"]["DatasetSource"];
export type DatasetTransform = components["schemas"]["DatasetSourceTransform"];
Expand Down
30 changes: 15 additions & 15 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7659,7 +7659,7 @@ export interface components {
/** Ext */
ext: string;
/** Hashes */
hashes?: components["schemas"]["DatasetHash-Input"][] | null;
hashes?: components["schemas"]["FileHash"][] | null;
/**
* Identifier
* @description A unique identifier for this element within the collection.
Expand Down Expand Up @@ -10101,17 +10101,7 @@ export interface components {
*/
DatasetExtraFiles: components["schemas"]["ExtraFileEntry"][];
/** DatasetHash */
"DatasetHash-Input": {
/**
* Hash Function
* @enum {string}
*/
hash_function: "MD5" | "SHA-1" | "SHA-256" | "SHA-512";
/** Hash Value */
hash_value: string;
};
/** DatasetHash */
"DatasetHash-Output": {
DatasetHash: {
/**
* Extra Files Path
* @description The path to the extra files used to generate the hash.
Expand Down Expand Up @@ -11730,6 +11720,16 @@ export interface components {
*/
action_type: "fill_defaults";
};
/** FileHash */
FileHash: {
/**
* Hash Function
* @enum {string}
*/
hash_function: "MD5" | "SHA-1" | "SHA-256" | "SHA-512";
/** Hash Value */
hash_value: string;
};
/** FileLibraryFolderItem */
FileLibraryFolderItem: {
/** Can Manage */
Expand Down Expand Up @@ -11855,7 +11855,7 @@ export interface components {
/** Ext */
ext: string;
/** Hashes */
hashes?: components["schemas"]["DatasetHash-Input"][] | null;
hashes?: components["schemas"]["FileHash"][] | null;
/** Info */
info?: string | null;
/** Location */
Expand Down Expand Up @@ -12725,7 +12725,7 @@ export interface components {
* Hashes
* @description The list of hashes associated with this dataset.
*/
hashes?: components["schemas"]["DatasetHash-Output"][] | null;
hashes?: components["schemas"]["DatasetHash"][] | null;
/**
* HDA or LDDA
* @description Whether this dataset belongs to a history (HDA) or a library (LDDA).
Expand Down Expand Up @@ -12999,7 +12999,7 @@ export interface components {
* Hashes
* @description The list of hashes associated with this dataset.
*/
hashes: components["schemas"]["DatasetHash-Output"][];
hashes: components["schemas"]["DatasetHash"][];
/**
* HDA or LDDA
* @description Whether this dataset belongs to a history (HDA) or a library (LDDA).
Expand Down
6 changes: 4 additions & 2 deletions lib/galaxy/dependencies/pinned-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ aiosignal==1.4.0
alembic==1.16.5 ; python_full_version < '3.10'
alembic==1.17.2 ; python_full_version >= '3.10'
amqp==5.3.1
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.0
apispec==6.8.4
Expand Down Expand Up @@ -70,7 +71,7 @@ edam-ontology==1.25.3
email-validator==2.3.0
et-xmlfile==2.0.0
exceptiongroup==1.3.1
fastapi==0.118.3
fastapi==0.124.0
filelock==3.19.1 ; python_full_version < '3.10'
filelock==3.20.0 ; python_full_version >= '3.10'
fissix==24.4.24
Expand Down Expand Up @@ -213,7 +214,8 @@ sortedcontainers==2.4.0
spython==0.3.14
sqlalchemy==2.0.44
sqlparse==0.5.4
starlette==0.48.0
starlette==0.49.3 ; python_full_version < '3.10'
starlette==0.50.0 ; python_full_version >= '3.10'
starlette-context==0.4.0
supervisor==4.3.0
svgwrite==1.4.3
Expand Down
4 changes: 2 additions & 2 deletions lib/galaxy/tool_util_models/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ class DataRequestHdca(LegacyRequestModelAttributes):
id: StrictStr


class DatasetHash(StrictModel):
class FileHash(StrictModel):
hash_function: Literal["MD5", "SHA-1", "SHA-256", "SHA-512"]
hash_value: StrictStr

Expand All @@ -416,7 +416,7 @@ class BaseDataRequest(StrictModel):
created_from_basename: Optional[StrictStr] = None
info: Optional[StrictStr] = None
tags: Optional[List[str]] = None
hashes: Optional[List[DatasetHash]] = None
hashes: Optional[List[FileHash]] = None
space_to_tab: bool = False
to_posix_lines: bool = False

Expand Down
2 changes: 0 additions & 2 deletions lib/galaxy/util/config_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,6 @@ def _make_field_optional(field_info: FieldInfo):
"""Returns the field's definition to be used in a `create_model()` call to make the field optional."""
annotation = field_info.annotation
assert annotation is not None
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
annotation = make_model_with_all_fields_optional(annotation)
if field_info.is_required():
return Annotated[Union[annotation, None], field_info], None
else:
Expand Down
9 changes: 3 additions & 6 deletions lib/galaxy/webapps/galaxy/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
Annotated,
Any,
Optional,
TYPE_CHECKING,
Union,
)

Expand Down Expand Up @@ -92,9 +91,7 @@
)
from galaxy.webapps.galaxy.api.common import UserIdPathParam
from galaxy.webapps.galaxy.services.users import UsersService

if TYPE_CHECKING:
from galaxy.work.context import SessionRequestContext
from galaxy.work.context import SessionRequestContext

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -171,7 +168,7 @@ class FastAPIUsers:
)
def recalculate_disk_usage(
self,
trans: "SessionRequestContext" = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
):
"""This route will be removed in a future version.

Expand All @@ -193,7 +190,7 @@ def recalculate_disk_usage(
def recalculate_disk_usage_by_user_id(
self,
user_id: UserIdPathParam,
trans: "SessionRequestContext" = DependsOnTrans,
trans: SessionRequestContext = DependsOnTrans,
):
result = self.service.recalculate_disk_usage(trans, user_id)
return Response(status_code=status.HTTP_204_NO_CONTENT) if result is None else result
Expand Down
5 changes: 5 additions & 0 deletions lib/galaxy/webapps/openapi/_compat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .main import get_definitions as get_definitions

__all__ = [
"get_definitions",
]
62 changes: 62 additions & 0 deletions lib/galaxy/webapps/openapi/_compat/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import sys
from typing import (
Any,
Union,
)

from fastapi._compat import (
may_v1,
v2,
)
from fastapi._compat.model_field import ModelField
from fastapi._compat.shared import PYDANTIC_V2
from fastapi.types import ModelNameMap
from typing_extensions import Literal

from .v2 import (
GenerateJsonSchema as GenerateJsonSchema,
get_definitions as v2_get_definitions,
)


def get_definitions(
*,
fields: list[ModelField],
model_name_map: ModelNameMap,
separate_input_output_schemas: bool = True,
schema_generator: Union[GenerateJsonSchema, None] = None,
) -> tuple[
dict[tuple[ModelField, Literal["validation", "serialization"]], may_v1.JsonSchemaValue],
dict[str, dict[str, Any]],
]:
if sys.version_info < (3, 14):
v1_fields = [field for field in fields if isinstance(field, may_v1.ModelField)]
v1_field_maps, v1_definitions = may_v1.get_definitions(
fields=v1_fields,
model_name_map=model_name_map,
separate_input_output_schemas=separate_input_output_schemas,
)
if not PYDANTIC_V2:
return v1_field_maps, v1_definitions
else:
v2_fields = [field for field in fields if isinstance(field, v2.ModelField)]
v2_field_maps, v2_definitions = v2_get_definitions(
fields=v2_fields,
model_name_map=model_name_map,
separate_input_output_schemas=separate_input_output_schemas,
schema_generator=schema_generator,
)
all_definitions = {**v1_definitions, **v2_definitions}
all_field_maps = {**v1_field_maps, **v2_field_maps}
return all_field_maps, all_definitions

# Pydantic v1 is not supported since Python 3.14
else:
v2_fields = [field for field in fields if isinstance(field, v2.ModelField)]
v2_field_maps, v2_definitions = v2_get_definitions(
fields=v2_fields,
model_name_map=model_name_map,
separate_input_output_schemas=separate_input_output_schemas,
schema_generator=schema_generator,
)
return v2_field_maps, v2_definitions
76 changes: 76 additions & 0 deletions lib/galaxy/webapps/openapi/_compat/v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from collections.abc import Sequence
from typing import (
Any,
cast,
Union,
)

from fastapi._compat.v2 import (
_has_computed_fields,
_remap_definitions_and_field_mappings,
get_flat_models_from_fields,
ModelField,
)
from fastapi.openapi.constants import REF_TEMPLATE
from fastapi.types import ModelNameMap
from pydantic.fields import FieldInfo as FieldInfo
from pydantic.json_schema import (
GenerateJsonSchema as GenerateJsonSchema,
JsonSchemaValue as JsonSchemaValue,
)
from typing_extensions import Literal


def get_definitions(
*,
fields: Sequence[ModelField],
model_name_map: ModelNameMap,
separate_input_output_schemas: bool = True,
schema_generator: Union[GenerateJsonSchema, None] = None,
) -> tuple[
dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue],
dict[str, dict[str, Any]],
]:
schema_generator = schema_generator or GenerateJsonSchema(ref_template=REF_TEMPLATE)
validation_fields = [field for field in fields if field.mode == "validation"]
serialization_fields = [field for field in fields if field.mode == "serialization"]
flat_validation_models = get_flat_models_from_fields(validation_fields, known_models=set())
flat_serialization_models = get_flat_models_from_fields(serialization_fields, known_models=set())
flat_validation_model_fields = [
ModelField(
field_info=FieldInfo(annotation=model),
name=model.__name__,
mode="validation",
)
for model in flat_validation_models
]
flat_serialization_model_fields = [
ModelField(
field_info=FieldInfo(annotation=model),
name=model.__name__,
mode="serialization",
)
for model in flat_serialization_models
]
flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields
input_types = {f.type_ for f in fields}
unique_flat_model_fields = {f for f in flat_model_fields if f.type_ not in input_types}
inputs = [
(
field,
(field.mode if (separate_input_output_schemas or _has_computed_fields(field)) else "validation"),
field._type_adapter.core_schema,
)
for field in list(fields) + list(unique_flat_model_fields)
]
field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs)
for item_def in cast(dict[str, dict[str, Any]], definitions).values():
if "description" in item_def:
item_description = cast(str, item_def["description"]).split("\f")[0]
item_def["description"] = item_description
new_mapping, new_definitions = _remap_definitions_and_field_mappings(
model_name_map=model_name_map,
definitions=definitions, # type: ignore[arg-type]
field_mapping=field_mapping,
)
return new_mapping, new_definitions
14 changes: 7 additions & 7 deletions lib/galaxy/webapps/openapi/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copy of https://github.com/tiangolo/fastapi/blob/master/fastapi/openapi/utils.py with changes from https://github.com/tiangolo/fastapi/pull/10903
Copy of fastapi/openapi/utils.py from https://github.com/fastapi/fastapi/pull/13918
"""

from collections.abc import Sequence
Expand All @@ -12,10 +12,8 @@
from fastapi import routing
from fastapi._compat import (
get_compat_model_name_map,
get_definitions,
)
from fastapi.encoders import jsonable_encoder
from fastapi.openapi.constants import REF_TEMPLATE
from fastapi.openapi.models import OpenAPI
from fastapi.openapi.utils import (
get_fields_from_routes,
Expand All @@ -24,6 +22,8 @@
from pydantic.json_schema import GenerateJsonSchema
from starlette.routing import BaseRoute

from ._compat import get_definitions


def get_openapi(
*,
Expand All @@ -40,6 +40,7 @@ def get_openapi(
contact: Optional[dict[str, Union[str, Any]]] = None,
license_info: Optional[dict[str, Union[str, Any]]] = None,
separate_input_output_schemas: bool = True,
external_docs: Optional[dict[str, Any]] = None,
schema_generator: Optional[GenerateJsonSchema] = None,
) -> dict[str, Any]:
info: dict[str, Any] = {"title": title, "version": version}
Expand All @@ -62,19 +63,17 @@ def get_openapi(
operation_ids: set[str] = set()
all_fields = get_fields_from_routes(list(routes or []) + list(webhooks or []))
model_name_map = get_compat_model_name_map(all_fields)
schema_generator = schema_generator or GenerateJsonSchema(ref_template=REF_TEMPLATE)
field_mapping, definitions = get_definitions(
fields=all_fields,
schema_generator=schema_generator,
model_name_map=model_name_map,
separate_input_output_schemas=separate_input_output_schemas,
schema_generator=schema_generator,
)
for route in routes or []:
if isinstance(route, routing.APIRoute):
result = get_openapi_path(
route=route,
operation_ids=operation_ids,
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas,
Expand All @@ -92,7 +91,6 @@ def get_openapi(
result = get_openapi_path(
route=webhook,
operation_ids=operation_ids,
schema_generator=schema_generator,
model_name_map=model_name_map,
field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas,
Expand All @@ -114,4 +112,6 @@ def get_openapi(
output["webhooks"] = webhook_paths
if tags:
output["tags"] = tags
if external_docs:
output["externalDocs"] = external_docs
return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True)
Loading
Loading