Steve Friedman
11/11/2025, 7:52 PMMarvin
11/11/2025, 7:52 PMMarvin
11/11/2025, 7:55 PMfrom datetime import date
from enum import Enum
from typing import Union, Annotated
from pydantic import BaseModel, Field
from typing_extensions import Literal
from prefect import flow
class MyEnum(str, Enum):
A = "A"
B = "B"
class DateChoice(BaseModel):
kind: Literal["date"]
value: date
class EnumChoice(BaseModel):
kind: Literal["enum"]
value: MyEnum
Param = Annotated[Union[DateChoice, EnumChoice], Field(discriminator="kind")]
@flow
def my_flow(x: Param):
print(x)
Workaround B: One model with two optional fields + validation
- Show both inputs and validate that exactly one was provided.
from datetime import date
from enum import Enum
from pydantic import BaseModel, model_validator
from prefect import flow
class MyEnum(str, Enum):
A = "A"
B = "B"
class Input(BaseModel):
enum_value: MyEnum | None = None
run_date: date | None = None
@model_validator(mode="after")
def _one_of(self):
if (self.enum_value is None) == (self.run_date is None):
raise ValueError("Provide exactly one of enum_value or run_date")
return self
@flow
def my_flow(x: Input):
print(x)
Workaround C: Two separate parameters with runtime validation
- Keep run_date: date | None and enum_value: MyEnum | None as separate params and assert that exactly one is set at the start of the flow.
A couple of additional tips:
- Setting a default value for the parameter (e.g., defaulting to the enum variant) can “anchor” the initially selected tab in some cases, though it may not fix the tab-flipping you’re seeing.
- If you’re self-hosting, consider upgrading the UI to the latest version in case this has already been addressed.
I wasn’t able to find an existing GitHub issue for this specific behavior in the UI. If you can share a minimal reproducer (your enum, flow signature, and how you deploy), I’m happy to file a bug in the UI repo on your behalf, or you can open one directly here:
- Prefect UI repo: PrefectHQ/prefect-ui
- Prefect Core repo (for reference): PrefectHQ/prefect
If you prefer, drop a minimal code snippet here and your environment details, and I’ll package it up for the team.Steve Friedman
11/11/2025, 8:03 PMclass MyEnum(Enum):
VALUE_ONE = "VALUE_ONE"
...
MyUnionType = Union[date, MyEnum]
And a flow looks like
@flow
def my_flow(input: MyUnionType = MyEnum.VALUE_ONE):
...Marvin
11/11/2025, 8:03 PMMarvin
11/11/2025, 8:04 PMMyUnionType = Union[MyEnum, date]
# or with | syntax: MyUnionType = MyEnum | date
You already default to MyEnum.VALUE_ONE, so this change may help the UI default/rendering heuristics.
More reliable workarounds
If the above doesn’t stick, these patterns are much more stable in the UI:
1) Discriminated union of models (recommended)
- Wrap each variant in a small model and add a discriminator; the UI tends to handle this well.
from datetime import date
from enum import Enum
from typing import Union, Annotated
from typing_extensions import Literal
from pydantic import BaseModel, Field
from prefect import flow
class MyEnum(str, Enum):
VALUE_ONE = "VALUE_ONE"
# ...
class DateChoice(BaseModel):
kind: Literal["date"]
value: date
class EnumChoice(BaseModel):
kind: Literal["enum"]
value: MyEnum
MyUnionType = Annotated[Union[DateChoice, EnumChoice], Field(discriminator="kind")]
@flow
def my_flow(input: MyUnionType = EnumChoice(kind="enum", value=MyEnum.VALUE_ONE)):
...
2) Single model with two optional fields + validation
- Show both inputs and enforce that exactly one is provided.
from datetime import date
from enum import Enum
from pydantic import BaseModel, model_validator
from prefect import flow
class MyEnum(str, Enum):
VALUE_ONE = "VALUE_ONE"
# ...
class Input(BaseModel):
enum_value: MyEnum | None = MyEnum.VALUE_ONE
run_date: date | None = None
@model_validator(mode="after")
def _one_of(self):
if (self.enum_value is None) == (self.run_date is None):
raise ValueError("Provide exactly one of enum_value or run_date")
return self
@flow
def my_flow(input: Input = Input()):
...
Upgrade suggestion
- If you can, try upgrading the UI to the latest available version; there have been periodic fixes to form rendering. Since you’re self-hosted, updating just the UI container/image may be sufficient depending on your deployment.
- Prefect UI repo: PrefectHQ/prefect-ui
- Form building docs (helpful for shaping parameters the UI handles best): Form building in Prefect 3
If reordering the union doesn’t help and you’d prefer not to change the type, I can open a UI issue with your minimal example. If you’re up for it, please also share:
- Browser and version
- How you deploy the UI (image tag/Helm chart version)
- A short GIF or screenshots showing the selection flipping
I’m happy to file the bug on your behalf with that info.