Skip to content

[25.0] Run landing request state through validator#21087

Merged
ahmedhamidawan merged 2 commits intogalaxyproject:release_25.0from
mvdbeek:validate_request
Oct 17, 2025
Merged

[25.0] Run landing request state through validator#21087
ahmedhamidawan merged 2 commits intogalaxyproject:release_25.0from
mvdbeek:validate_request

Conversation

@mvdbeek
Copy link
Copy Markdown
Member

@mvdbeek mvdbeek commented Oct 16, 2025

Should resolve the inconsistency in #21084

How to test the changes?

(Select all options that apply)

  • I've included appropriate automated tests.
  • This is a refactoring of components with existing test coverage.
  • Instructions for manual testing are as follows:
    1. [add testing steps and prerequisites here if you didn't write automated tests covering all your changes]

License

  • I agree to license these and all my past contributions to the core galaxy codebase under the MIT license.

@ahmedhamidawan
Copy link
Copy Markdown
Member

ahmedhamidawan commented Oct 16, 2025

Trying to test this locally, and this seems to produce an error for me:

INFO:     127.0.0.1:60184 - "POST /api/workflow_landings HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1815, in _execute_context
    context = constructor(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 1525, in _init_compiled
    d_param = {
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 1527, in <dictcomp>
    flattened_processors[key](compiled_params[key])
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/sql/type_api.py", line 2103, in process
    fixed_process_param(value, dialect)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/model/custom_types.py", line 98, in process_bind_param
    value = json_encoder.encode(value).encode()
  File "/usr/lib/python3.10/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.10/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/model/custom_types.py", line 40, in default
    return json.JSONEncoder.default(self, obj)
  File "/usr/lib/python3.10/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type FileRequestUri is not JSON serializable

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
    return await self.app(scope, receive, send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1082, in __call__
    await super().__call__(scope, receive, send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/applications.py", line 113, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__
    raise exc
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette_context/middleware/raw_middleware.py", line 94, in __call__
    await self.app(scope, receive, send_wrapper)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/middleware/base.py", line 182, in __call__
    with recv_stream, send_stream, collapse_excgroups():
  File "/usr/lib/python3.10/contextlib.py", line 153, in __exit__
    self.gen.throw(typ, value, traceback)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/_utils.py", line 85, in collapse_excgroups
    raise exc
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/middleware/base.py", line 184, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/webapps/galaxy/fast_app.py", line 108, in add_x_frame_options
    response = await call_next(request)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/middleware/base.py", line 159, in call_next
    raise app_exc
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/middleware/base.py", line 144, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/routing.py", line 716, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/routing.py", line 736, in app
    await route.handle(scope, receive, send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/routing.py", line 290, in handle
    await self.app(scope, receive, send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/routing.py", line 78, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/routing.py", line 75, in app
    response = await f(request)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/webapps/galaxy/api/__init__.py", line 553, in custom_route_handler
    response: Response = await original_route_handler(request)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 308, in app
    raw_response = await run_endpoint_function(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/fastapi/routing.py", line 221, in run_endpoint_function
    return await run_in_threadpool(dependant.call, **values)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/starlette/concurrency.py", line 38, in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/anyio/to_thread.py", line 56, in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 2485, in run_sync_in_worker_thread
    return await future
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 976, in run
    result = context.run(func, *args)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/webapps/galaxy/api/workflows.py", line 1171, in create_landing
    return self.landing_manager.create_workflow_landing_request(workflow_landing_request)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/managers/landing.py", line 79, in create_workflow_landing_request
    self._save(model)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/managers/landing.py", line 213, in _save
    sa_session.commit()
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/scoping.py", line 599, in commit
    return self._proxied.commit()
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2032, in commit
    trans.commit(_to_root=True)
  File "<string>", line 2, in commit
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
    ret_value = fn(self, *arg, **kw)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1313, in commit
    self._prepare_impl()
  File "<string>", line 2, in _prepare_impl
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/state_changes.py", line 137, in _go
    ret_value = fn(self, *arg, **kw)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1288, in _prepare_impl
    self.session.flush()
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4345, in flush
    self._flush(objects)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4480, in _flush
    with util.safe_reraise():
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 224, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4441, in _flush
    flush_context.execute()
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute
    rec.execute(self)
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute
    util.preloaded.orm_persistence.save_obj(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/persistence.py", line 93, in save_obj
    _emit_insert_statements(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/orm/persistence.py", line 1233, in _emit_insert_statements
    result = connection.execute(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1419, in execute
    return meth(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 526, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1641, in _execute_clauseelement
    ret = self._execute_context(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1821, in _execute_context
    self._handle_dbapi_exception(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 2355, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1815, in _execute_context
    context = constructor(
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 1525, in _init_compiled
    d_param = {
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 1527, in <dictcomp>
    flattened_processors[key](compiled_params[key])
  File "/home/ahmedhamidawan/repos/galaxy2/.venv/lib/python3.10/site-packages/sqlalchemy/sql/type_api.py", line 2103, in process
    fixed_process_param(value, dialect)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/model/custom_types.py", line 98, in process_bind_param
    value = json_encoder.encode(value).encode()
  File "/usr/lib/python3.10/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.10/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/home/ahmedhamidawan/repos/galaxy2/lib/galaxy/model/custom_types.py", line 40, in default
    return json.JSONEncoder.default(self, obj)
  File "/usr/lib/python3.10/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
sqlalchemy.exc.StatementError: (builtins.TypeError) Object of type FileRequestUri is not JSON serializable
[SQL: INSERT INTO workflow_landing_request (user_id, workflow_id, stored_workflow_id, create_time, update_time, uuid, request_state, client_secret, workflow_source, workflow_source_type, public) VALUES (%(user_id)s, %(workflow_id)s, %(stored_workflow_id)s, %(create_time)s, %(update_time)s, %(uuid)s, %(request_state)s, %(client_secret)s, %(workflow_source)s, %(workflow_source_type)s, %(public)s) RETURNING workflow_landing_request.id]
[parameters: [{'stored_workflow_id': 497, 'public': False, 'request_state': {'The Input Dataset': FileRequestUri(url='https://raw.githubusercontent.com/galaxyprojec ... (220 characters truncated) ... 19b-62f8-4d9d-a280-b12ffff0628b'), 'client_secret': None, 'user_id': None, 'workflow_source_type': None, 'workflow_source': None, 'workflow_id': None}]]

This happens for any request_state i try, but i was specifically trying something like:

simple_workflow:
  # Encoded ID for a workflow to target - if workflow_target_type is stored_workflow this
  # will always be the latest version of that workflow and stored workflow ID should be provided.
  # If instead workflow_target_type is workflow - this is a particular version of a workflow and
  # the workflow ID should be provided.
  workflow_id: e96c6ce43a3d239a
  workflow_target_type: stored_workflow
  # request state provides prepopulated parameters for this workflow or stored workflow when the
  # user navigates to the landing URL.
  request_state:
    The Input Dataset:
      class: File
      location: https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed
      ext: txt
simple_workflow_1:
  # Encoded ID for a workflow to target - if workflow_target_type is stored_workflow this
  # will always be the latest version of that workflow and stored workflow ID should be provided.
  # If instead workflow_target_type is workflow - this is a particular version of a workflow and
  # the workflow ID should be provided.
  workflow_id: e96c6ce43a3d239a
  workflow_target_type: stored_workflow
  # request state provides prepopulated parameters for this workflow or stored workflow when the
  # user navigates to the landing URL.
  request_state:
    The Input Dataset:
      class: File
      location: https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed
      filetype: txt

Both fail.

I also confirmed that specifically removing your change here was fixing it, and a landing was being generated

Comment thread lib/galaxy/managers/landing.py Outdated
@ahmedhamidawan
Copy link
Copy Markdown
Member

ahmedhamidawan commented Oct 16, 2025

However, though this does produce a nice detailed response, the new keys will still not match what the client expects in client/src/components/Form/Elements/FormData/types.ts

The request state I tried here was:

request_state:
    The Input Dataset:
      class: File
      location: https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/1.bed
      filetype: txt
Before After
image image

Though yes, we are nicely ensuring it is always ext and not filetype, however, notice how Class has become Class_, and location has become url.

@ahmedhamidawan ahmedhamidawan added this to the 25.1 milestone Oct 16, 2025
@mvdbeek
Copy link
Copy Markdown
Member Author

mvdbeek commented Oct 17, 2025

That should be fixed by persisting via by_alias=True. I've added a test case that makes sure url is turned into location, but if you can run a sanity check that'd be appreciated.

@mvdbeek mvdbeek marked this pull request as ready for review October 17, 2025 14:20
@ahmedhamidawan
Copy link
Copy Markdown
Member

Thank you! I will test this out again locally and let you know!

Copy link
Copy Markdown
Member

@ahmedhamidawan ahmedhamidawan left a comment

Choose a reason for hiding this comment

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

Bingo!

image

Works perfectly, tried all sorts of structures/keys!

@ahmedhamidawan ahmedhamidawan merged commit 493be31 into galaxyproject:release_25.0 Oct 17, 2025
49 of 52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants