1- from collections .abc import Iterable , MutableMapping
1+ from collections .abc import Iterable , Mapping
22from datetime import datetime
33from pathlib import Path
44from typing import Any
77import sparse
88import xarray as xr
99import xattree
10- from attrs import define
1110from cattrs import Converter
1211from modflow_devtools .dfn .schema .block import block_sort_key
1312from numpy .typing import NDArray
1413from xattree import get_xatspec
1514
1615from flopy4 .adapters import get_nn
16+ from flopy4 .mf6 .binding import Binding
1717from flopy4 .mf6 .component import Component
1818from flopy4 .mf6 .config import SPARSE_THRESHOLD
1919from flopy4 .mf6 .constants import FILL_DNODATA
2020from flopy4 .mf6 .context import Context
21- from flopy4 .mf6 .exchange import Exchange
22- from flopy4 .mf6 .model import Model
23- from flopy4 .mf6 .package import Package
24- from flopy4 .mf6 .solution import Solution
2521from flopy4 .mf6 .spec import fields_dict
2622
2723
28- @define
29- class _Binding :
30- """
31- An MF6 component binding: a record representation of the
32- component for writing to a parent component's name file.
33- """
34-
35- type : str
36- fname : str
37- terms : tuple [str , ...] | None = None
38-
39- def to_tuple (self ):
40- if self .terms and any (self .terms ):
41- return (self .type , self .fname , * self .terms )
42- else :
43- return (self .type , self .fname )
44-
45- @classmethod
46- def from_component (cls , component : Component ) -> "_Binding" :
47- def _get_binding_type (component : Component ) -> str :
48- cls_name = component .__class__ .__name__
49- if isinstance (component , Exchange ):
50- return f"{ '-' .join ([cls_name [:2 ], cls_name [3 :]]).upper ()} 6"
51- elif isinstance (component , Solution ):
52- return f"{ component .slntype } 6"
53- else :
54- return f"{ cls_name .upper ()} 6"
55-
56- def _get_binding_terms (component : Component ) -> tuple [str , ...] | None :
57- if isinstance (component , Exchange ):
58- return (component .exgmnamea , component .exgmnameb ) # type: ignore
59- elif isinstance (component , Solution ):
60- return tuple (component .models )
61- elif isinstance (component , (Model , Package )):
62- return (component .name ,) # type: ignore
63- return None
64-
65- return cls (
66- type = _get_binding_type (component ),
67- fname = component .filename or component .default_filename (),
68- terms = _get_binding_terms (component ),
69- )
70-
71-
7224def _attach_field_metadata (
7325 dataset : xr .Dataset , component_type : type , field_names : list [str ]
7426) -> None :
@@ -88,43 +40,43 @@ def _path_to_tuple(field_name: str, path_value: Path) -> tuple:
8840 return (field_name .upper (), "FILEOUT" , str (path_value ))
8941
9042
91- def unstructure_component (value : Component ) -> dict [str , Any ]:
92- blockspec = dict (sorted (value .dfn .blocks .items (), key = block_sort_key )) # type: ignore
93- blocks : dict [str , dict [str , Any ]] = {}
43+ def get_binding_blocks (value : Component ) -> dict [str , dict [str , list [tuple [str , ...]]]]:
44+ if not isinstance (value , Context ):
45+ return {}
46+
47+ blocks = {} # type: ignore
9448 xatspec = xattree .get_xatspec (type (value ))
9549
96- # Handle child component bindings before converting to dict
97- if isinstance (value , Context ):
98- for field_name , child_spec in xatspec .children .items ():
99- if hasattr (child_spec , "metadata" ) and "block" in child_spec .metadata : # type: ignore
100- block_name = child_spec .metadata ["block" ] # type: ignore
101- field_value = getattr (value , field_name , None )
102-
103- if block_name not in blocks :
104- blocks [block_name ] = {}
105-
106- if isinstance (field_value , Component ):
107- components = [_Binding .from_component (field_value ).to_tuple ()]
108- elif isinstance (field_value , MutableMapping ):
109- components = [
110- _Binding .from_component (comp ).to_tuple ()
111- for comp in field_value .values ()
112- if comp is not None
113- ]
114- elif isinstance (field_value , Iterable ):
115- components = [
116- _Binding .from_component (comp ).to_tuple ()
117- for comp in field_value
118- if comp is not None
119- ]
120- else :
121- continue
50+ for child_name , child_spec in xatspec .children .items ():
51+ if (child := getattr (value , child_name , None )) is None :
52+ continue
53+ if (block_name := child_spec .metadata ["block" ]) not in blocks : # type: ignore
54+ blocks [block_name ] = {}
55+ match child :
56+ case Component ():
57+ blocks [block_name ][child_name ] = [Binding .from_component (child ).to_tuple ()]
58+ case Mapping ():
59+ blocks [block_name ][child_name ] = [
60+ Binding .from_component (c ).to_tuple () for c in child .values () if c is not None
61+ ]
62+ case Iterable ():
63+ blocks [block_name ][child_name ] = [
64+ Binding .from_component (c ).to_tuple () for c in child if c is not None
65+ ]
66+ case _:
67+ raise ValueError (f"Unexpected child type: { type (child )} " )
68+
69+ return blocks
12270
123- if components :
124- blocks [block_name ][field_name ] = components
12571
72+ def unstructure_component (value : Component ) -> dict [str , Any ]:
73+ blockspec = dict (sorted (value .dfn .blocks .items (), key = block_sort_key )) # type: ignore
74+ blocks : dict [str , dict [str , Any ]] = {}
75+ xatspec = xattree .get_xatspec (type (value ))
12676 data = xattree .asdict (value )
12777
78+ blocks .update (binding_blocks := get_binding_blocks (value ))
79+
12880 for block_name , block in blockspec .items ():
12981 if block_name not in blocks :
13082 blocks [block_name ] = {}
0 commit comments