<@ULVA73B9P> I have a flow which uses a Pydantic `...
# ask-marvin
b
@Marvin I have a flow which uses a Pydantic
BaseModel
for it's parameters. This flow is called by another flow as a subflow, but I don't want all of the subflows parameters to be editable in the Prefect UI when configuring a run, as some are set programatically. How can I have some parameters in the BaseModel hidden in the UI?
m
thought for 250.2 seconds
Short answer: In Prefect 3.x there isn’t a supported way to “hide” individual fields from a Pydantic-based parameter model in the UI. The parameter form is generated from the model’s JSON schema; flags like
Field(exclude=True)
,
PrivateAttr
, or
SecretStr
don’t remove a field from the form (they affect serialization or masking, not visibility). See Form building. Recommended patterns to keep some values programmatic/non-editable: - Don’t expose them as flow parameters - Put only “public” inputs in the flow signature/model; derive/load the rest inside the flow (from blocks, env vars, or runtime context). - Example:
Copy code
from pydantic import BaseModel, Field
    from prefect import flow
    # from my_blocks import MyConfig  # a Prefect block you define

    class PublicParams(BaseModel):
        dataset: str
        date: str
        config_block: str = Field(default="etl-config/dev")  # only editable field

    @flow
    def subflow(params: PublicParams):
        cfg = MyConfig.load(params.config_block)  # holds api keys, endpoints, etc.
        # derive private/internal config here
        retries = 5
        api_key = cfg.api_key
        # ...rest of your logic using derived/private values...
- This keeps internal fields out of the UI entirely; users only edit
config_block
or other public inputs. - Don’t deploy the subflow - If you deploy only the parent (or a thin public wrapper flow) and keep the subflow undecorated-as-deployment, users won’t get a UI form for the subflow at all. The parent can set programmatic values and pass them down in code. - Use a wrapper flow for UI and keep a richer internal model - The wrapper flow takes a minimal “public” model and calls an internal implementation (task or plain function) with an “internal” model built programmatically. Only deploy the wrapper.
Copy code
from pydantic import BaseModel
    from prefect import flow

    class PublicParams(BaseModel):
        dataset: str
        date: str

    class InternalParams(BaseModel):
        dataset: str
        date: str
        api_key: str
        retries: int = 5

    def _impl(p: InternalParams):
        # actual work here
        ...

    @flow
    def subflow(params: PublicParams):
        # compute/load private values here
        api_key = MyConfig.load("etl-config/dev").api_key
        _impl(InternalParams(**params.model_dump(), api_key=api_key))
- Consider environment-driven config - If the values are environment-specific, load them from env vars or settings models instead of parameters. You can also pull context like flow run info via runtime if needed: Runtime context. Notes: - JSON Schema tricks like readOnly/writeOnly or
json_schema_extra
do not provide a supported “hidden” control in the Prefect UI today; only ordering is documented. -
Field(exclude=True)
affects
model_dump
but not the generated parameter form. If you can share: - Whether the subflow itself has a deployment you want to keep, and - Which fields you want to keep programmatic, I can suggest the cleanest refactor for your case. References: - Form building: Prefect 3.x form building - Runtime context: Prefect runtime context - Pydantic serialization notes: Pydantic model_dump()
b
@Marvin If I create a new model which inherits from the "internal" BaseModel, but overrides a few of the parameters to
param1: ClassVar[None] = None
, this successfully hides them in the UI. Is this approach acceptable?
m
thought for 70.0 seconds
Short answer: It works because Prefect builds the parameter form from the Pydantic JSON schema, and fields typed as ClassVar are excluded from the schema. So overriding inherited fields to ClassVar[...] in a subclass will hide them in the UI. Is it acceptable? Technically yes, but with caveats: - Those attributes are no longer model fields - They won’t be validated, accepted on initialization, or included in `model_dump()`; they won’t be persisted with the flow run’s parameters. - If you rely on them across retries/resumes or remote execution, you must re-derive/load them at runtime (e.g., from blocks or env vars). - Type ergonomics - Passing your “UI” subclass where code expects the original “internal” model will not work without converting and injecting the missing fields. - Potential confusion - ClassVar semantically indicates a class-level constant, not a “hidden instance field.” Future readers (and static analysis) may be confused by this pattern. - Not a security control - Hiding from the UI is not the same as securing the value; prefer secrets/blocks. Recommended pattern (clearer and more maintainable): - Define two models: a minimal PublicParams (what you want editable in the UI) and a full InternalParams. Convert inside the flow by deriving/loading the “private” values programmatically.
Copy code
from pydantic import BaseModel
  from prefect import flow

  class PublicParams(BaseModel):
      dataset: str
      date: str
      config_block: str = "etl-config/dev"

  class InternalParams(BaseModel):
      dataset: str
      date: str
      api_key: str
      retries: int = 5

  def to_internal(p: PublicParams) -> InternalParams:
      cfg = MyConfig.load(p.config_block)  # Prefect block or your own config
      return InternalParams(
          dataset=p.dataset,
          date=p.date,
          api_key=cfg.api_key,
          retries=5,
      )

  @flow
  def subflow(params: PublicParams):
      internal = to_internal(params)
      # use internal...
This keeps the UI clean and makes the data-flow explicit. References: - Prefect 3.x form building (UI uses your model’s JSON schema): Form building - Runtime context (if you need run/deployment info at runtime): Runtime context If you share which fields you’re hiding and how you compute them, I can tailor the conversion pattern for your case.