@@ -255,23 +255,27 @@ def DynamicNestedDictGenerator(n: int):
255255from aiida_workgraph .socket_spec import Leaf
256256
257257
258+ class InputsModel (BaseModel ):
259+ x : int
260+ y : int
261+
262+
258263class OutputsModel (BaseModel ):
259264 sum : int
260265 product : int
261266
262267
263268@task
264- def add_multiply_pydantic_in_out (x , y ) -> OutputsModel :
265- return { ' sum' : x + y , ' product' : x * y }
269+ def add_multiply_pydantic_in_out (data : InputsModel ) -> OutputsModel :
270+ return OutputsModel ( sum = data . x + data . y , product = data . x * data . y )
266271
267272
268273@task .graph
269- def AddMultiplyPydantic ():
270- # IMPORTANT: pass a plain dict, not OutputsModel(x=3, y=4)
271- add_multiply_pydantic_in_out (x = 3 , y = 4 )
274+ def AddMultiplyPydantic (data : InputsModel ) -> OutputsModel :
275+ return add_multiply_pydantic_in_out (data = data )
272276
273277
274- wg = AddMultiplyPydantic .build ()
278+ wg = AddMultiplyPydantic .build (data = InputsModel ( x = 3 , y = 4 ) )
275279wg .run ()
276280wg .generate_provenance_graph ()
277281
@@ -292,7 +296,9 @@ class DynamicOut(BaseModel):
292296@task
293297def make_dynamic_with_model (n : int ) -> DynamicOut :
294298 # fixed field + dynamic keys with int values
295- return {'header' : 100 , ** {f'k{ i } ' : i * i for i in range (n )}}
299+ payload = {'header' : 100 }
300+ payload .update ({f'k{ i } ' : i * i for i in range (n )})
301+ return DynamicOut (** payload )
296302
297303
298304@task .graph
@@ -309,6 +315,12 @@ def GraphDynamicOut(n: int):
309315# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
310316#
311317# Sometimes you want to **validate** with a Pydantic model but store it as a **single node** instead of expanding fields.
318+ # For leaf models, WorkGraph treats the value as a blob. If a serializer is registered for the class, it is used;
319+ # otherwise ``JsonableData`` stores the model as a JSON-friendly dict.
320+ #
321+ # Note: Pydantic annotations define the *schema* for WorkGraph sockets. Outputs are stored as typed
322+ # AiiDA nodes per field, so runtime results are dicts of nodes (not Pydantic instances). Use those
323+ # nodes for linking/provenance; rebuild a Pydantic model only for convenience.
312324# There are two ways:
313325#
314326# 1) Mark the model: ``model_config = {"leaf": True}``
@@ -325,7 +337,7 @@ class BlobModel(BaseModel):
325337@task
326338def consume_blob (m : BlobModel ) -> dict :
327339 # 'm' is validated by Pydantic but stored/treated as one leaf node
328- return {'sum' : m [ 'a' ] + m [ 'b' ] }
340+ return {'sum' : m . a + m . b }
329341
330342
331343# Per-use override without modifying the model:
@@ -336,13 +348,13 @@ class AnotherModel(BaseModel):
336348
337349@task
338350def consume_blob_per_use (m : Leaf [AnotherModel ]) -> dict :
339- return {'sum' : m [ 'a' ] + m [ 'b' ] }
351+ return {'sum' : m . a + m . b }
340352
341353
342354@task .graph
343355def BlobExamples ():
344- consume_blob (m = { 'a' : 1 , 'b' : 2 } )
345- consume_blob_per_use (m = { 'a' : 3 , 'b' : 4 } )
356+ consume_blob (m = BlobModel ( a = 1 , b = 2 ) )
357+ consume_blob_per_use (m = AnotherModel ( a = 3 , b = 4 ) )
346358
347359
348360wg = BlobExamples .build ()
@@ -402,12 +414,14 @@ def AddMultiplyDataclass():
402414# %%
403415# .. important::
404416#
405- # Models/ dataclasses are annotation-only
406- # Even when you annotate with BaseModel or @dataclass, do not pass instances of these types to tasks/graphs. Always pass plain dictionaries :
417+ # Structured models (Pydantic or dataclasses) are supported as *runtime* values.
418+ # You may pass instances to tasks/graphs and return them from tasks :
407419#
408- # - This lets WorkGraph expand inputs/outputs into individual sockets, so it can wire provenance edges precisely (e.g., data.x --> task.data.x).
409- # - It allows graph inputs to be collected from task outputs as a dict of AiiDA ORM nodes, preserving AiiDA links between nodes.
410- # - Validation still happens via the WorkGraph spec (derived from your annotations)--you’re just not constructing runtime model/dataclass objects.
420+ # - Instances are expanded to plain dicts when assigned to namespace sockets, so WorkGraph can
421+ # wire provenance edges precisely (e.g., data.x --> task.data.x).
422+ # - Graph inputs can still be collected from task outputs as a dict of AiiDA ORM nodes,
423+ # preserving AiiDA links between nodes.
424+ # - Validation still happens via the WorkGraph spec (derived from your annotations).
411425#
412426# Data linkage
413427# ------------
0 commit comments