1- from dataclasses import asdict
21from pathlib import Path
32
43import pytest
87from modflow_devtools .dfns .dfn2toml import convert , is_valid
98from modflow_devtools .dfns .fetch import fetch_dfns
109from modflow_devtools .dfns .schema .v1 import FieldV1
11- from modflow_devtools .dfns .schema .v2 import FieldV2
10+ from modflow_devtools .dfns .schema .v2 import (
11+ Array ,
12+ Double ,
13+ FieldBase ,
14+ FieldV2 ,
15+ Integer ,
16+ Keyword ,
17+ Record ,
18+ String ,
19+ Union ,
20+ )
1221from modflow_devtools .markers import requires_pkg
1322
1423PROJ_ROOT = Path (__file__ ).parents [1 ]
@@ -31,13 +40,11 @@ def pytest_generate_tests(metafunc):
3140 metafunc .parametrize ("dfn_name" , dfn_names , ids = dfn_names )
3241
3342 if "toml_name" in metafunc .fixturenames :
34- # Only convert if TOML files don't exist yet (avoid repeated conversions)
3543 dfn_paths = [p for p in DFN_DIR .glob ("*.dfn" ) if p .stem not in ["common" , "flopy" ]]
3644 if not TOML_DIR .exists () or not all (
3745 (TOML_DIR / f"{ dfn .stem } .toml" ).is_file () for dfn in dfn_paths
3846 ):
3947 convert (DFN_DIR , TOML_DIR )
40- # Verify all expected TOML files were created
4148 assert all ((TOML_DIR / f"{ dfn .stem } .toml" ).is_file () for dfn in dfn_paths )
4249 toml_names = [toml .stem for toml in TOML_DIR .glob ("*.toml" )]
4350 metafunc .parametrize ("toml_name" , toml_names , ids = toml_names )
@@ -108,11 +115,11 @@ def test_convert(function_tmpdir):
108115
109116 if gwf := models .get ("gwf-nam" , None ):
110117 pkgs = gwf .children or {}
111- pkgs = {k : v for k , v in pkgs .items () if k .startswith ("gwf-" ) and isinstance ( v , dict ) }
118+ pkgs = {k : v for k , v in pkgs .items () if k .startswith ("gwf-" )}
112119 assert len (pkgs ) > 0
113120 if dis := pkgs .get ("gwf-dis" , None ):
114121 assert dis .name == "gwf-dis"
115- assert dis .parent == "gwf"
122+ assert dis .parent == "gwf-nam "
116123 assert "options" in (dis .blocks or {})
117124 assert "dimensions" in (dis .blocks or {})
118125
@@ -166,7 +173,7 @@ def test_dfn_from_dict_roundtrip():
166173 multi = True ,
167174 blocks = {"options" : {}},
168175 )
169- d = asdict ( original )
176+ d = original . model_dump ( )
170177 reconstructed = Dfn .from_dict (d )
171178 assert reconstructed .name == original .name
172179 assert reconstructed .schema_version == original .schema_version
@@ -183,9 +190,9 @@ def test_fieldv1_from_dict_ignores_extra_keys():
183190 "extra_key" : "should be allowed" ,
184191 "another_extra" : 123 ,
185192 }
186- field = FieldV1 .from_dict (d )
187- assert field .name == "test_field"
188- assert field .type == "keyword"
193+ f = FieldV1 .from_dict (d )
194+ assert f .name == "test_field"
195+ assert f .type == "keyword"
189196
190197
191198def test_fieldv1_from_dict_strict_mode ():
@@ -199,6 +206,8 @@ def test_fieldv1_from_dict_strict_mode():
199206
200207
201208def test_fieldv1_from_dict_roundtrip ():
209+ from dataclasses import asdict
210+
202211 original = FieldV1 (
203212 name = "maxbound" ,
204213 type = "integer" ,
@@ -222,9 +231,10 @@ def test_fieldv2_from_dict_ignores_extra_keys():
222231 "extra_key" : "should be allowed" ,
223232 "another_extra" : 123 ,
224233 }
225- field = FieldV2 .from_dict (d )
226- assert field .name == "test_field"
227- assert field .type == "keyword"
234+ f = FieldBase .from_dict (d )
235+ assert f .name == "test_field"
236+ assert f .type == "keyword"
237+ assert isinstance (f , Keyword )
228238
229239
230240def test_fieldv2_from_dict_strict_mode ():
@@ -234,22 +244,20 @@ def test_fieldv2_from_dict_strict_mode():
234244 "extra_key" : "should cause error" ,
235245 }
236246 with pytest .raises (ValueError , match = "Unrecognized keys in field data" ):
237- FieldV2 .from_dict (d , strict = True )
247+ FieldBase .from_dict (d , strict = True )
238248
239249
240250def test_fieldv2_from_dict_roundtrip ():
241- original = FieldV2 (
251+ original = Integer (
242252 name = "nper" ,
243- type = "integer" ,
244- block = "dimensions" ,
245253 description = "number of stress periods" ,
246254 optional = False ,
247255 )
248- d = asdict (original )
249- reconstructed = FieldV2 .from_dict (d )
256+ d = original .model_dump ()
257+ reconstructed = FieldBase .from_dict (d )
258+ assert isinstance (reconstructed , Integer )
250259 assert reconstructed .name == original .name
251260 assert reconstructed .type == original .type
252- assert reconstructed .block == original .block
253261 assert reconstructed .description == original .description
254262 assert reconstructed .optional == original .optional
255263
@@ -276,12 +284,12 @@ def test_dfn_from_dict_with_v1_field_dicts():
276284 assert "options" in dfn .blocks
277285 assert "save_flows" in dfn .blocks ["options" ]
278286
279- field = dfn .blocks ["options" ]["save_flows" ]
280- assert isinstance (field , FieldV1 )
281- assert field .name == "save_flows"
282- assert field .type == "keyword"
283- assert field .tagged is True
284- assert field .in_record is False
287+ f = dfn .blocks ["options" ]["save_flows" ]
288+ assert isinstance (f , FieldV1 )
289+ assert f .name == "save_flows"
290+ assert f .type == "keyword"
291+ assert f .tagged is True
292+ assert f .in_record is False
285293
286294
287295def test_dfn_from_dict_with_v2_field_dicts ():
@@ -305,11 +313,11 @@ def test_dfn_from_dict_with_v2_field_dicts():
305313 assert "dimensions" in dfn .blocks
306314 assert "nper" in dfn .blocks ["dimensions" ]
307315
308- field = dfn .blocks ["dimensions" ]["nper" ]
309- assert isinstance (field , FieldV2 )
310- assert field .name == "nper"
311- assert field .type == "integer"
312- assert field .optional is False
316+ f = dfn .blocks ["dimensions" ]["nper" ]
317+ assert isinstance (f , Integer )
318+ assert f .name == "nper"
319+ assert f .type == "integer"
320+ assert f .optional is False
313321
314322
315323def test_dfn_from_dict_defaults_to_v2_fields ():
@@ -326,25 +334,26 @@ def test_dfn_from_dict_defaults_to_v2_fields():
326334 }
327335 dfn = Dfn .from_dict (d )
328336 assert dfn .blocks is not None
329- field = dfn .blocks ["options" ]["some_field" ]
330- assert isinstance (field , FieldV2 )
337+ f = dfn .blocks ["options" ]["some_field" ]
338+ assert isinstance (f , Keyword )
339+ assert isinstance (f , FieldBase )
331340 assert dfn .schema_version == Version ("2" )
332341
333342
334343def test_dfn_from_dict_with_already_deserialized_fields ():
335- field = FieldV2 (name = "test" , type = "keyword " )
344+ kw = Keyword (name = "test" )
336345 d = {
337346 "schema_version" : Version ("2" ),
338347 "name" : "test-dfn" ,
339348 "blocks" : {
340349 "options" : {
341- "test" : field ,
350+ "test" : kw ,
342351 },
343352 },
344353 }
345354 dfn = Dfn .from_dict (d )
346355 assert dfn .blocks is not None
347- assert dfn .blocks ["options" ]["test" ] is field
356+ assert dfn .blocks ["options" ]["test" ] is kw
348357
349358
350359@requires_pkg ("boltons" )
@@ -383,7 +392,7 @@ def test_validate_nonexistent_file(function_tmpdir):
383392
384393
385394def test_fieldv1_to_fieldv2_conversion ():
386- """Test that FieldV1 instances are properly converted to FieldV2 ."""
395+ """Test that FieldV1 instances are properly converted to typed v2 instances ."""
387396 from modflow_devtools .dfns import map
388397
389398 dfn_v1 = Dfn (
@@ -417,68 +426,57 @@ def test_fieldv1_to_fieldv2_conversion():
417426 assert "save_flows" in dfn_v2 .blocks ["options" ]
418427
419428 save_flows = dfn_v2 .blocks ["options" ]["save_flows" ]
420- assert isinstance (save_flows , FieldV2 )
429+ assert isinstance (save_flows , Keyword )
430+ assert isinstance (save_flows , FieldBase )
421431 assert save_flows .name == "save_flows"
422432 assert save_flows .type == "keyword"
423- assert save_flows .block == "options"
424433 assert save_flows .description == "save calculated flows"
425- assert hasattr (save_flows , "tagged" )
426434 assert not hasattr (save_flows , "in_record" )
427435 assert not hasattr (save_flows , "reader" )
428436
429437 some_float = dfn_v2 .blocks ["options" ]["some_float" ]
430- assert isinstance (some_float , FieldV2 )
438+ assert isinstance (some_float , Double )
431439 assert some_float .name == "some_float"
432440 assert some_float .type == "double"
433- assert some_float .block == "options"
434441 assert some_float .description == "a floating point value"
435442
436443
437444def test_fieldv1_to_fieldv2_conversion_with_children ():
438- """Test that FieldV1 with nested children are properly converted to FieldV2 ."""
445+ """Test that FieldV1 with nested children are properly converted to typed v2 instances ."""
439446 from modflow_devtools .dfns import map
440447
441- # Create nested fields for a record
442- child_field_v1 = FieldV1 (
443- name = "cellid" ,
444- type = "integer" ,
445- block = "period" ,
446- description = "cell identifier" ,
447- in_record = True ,
448- tagged = False ,
449- )
450-
451- parent_field_v1 = FieldV1 (
452- name = "stress_period_data" ,
453- type = "recarray cellid" ,
454- block = "period" ,
455- description = "stress period data" ,
456- in_record = False ,
457- )
458-
459448 dfn_v1 = Dfn (
460449 schema_version = Version ("1" ),
461450 name = "test-dfn" ,
462451 blocks = {
463452 "period" : {
464- "stress_period_data" : parent_field_v1 ,
465- "cellid" : child_field_v1 ,
453+ "stress_period_data" : FieldV1 (
454+ name = "stress_period_data" ,
455+ type = "recarray cellid" ,
456+ block = "period" ,
457+ description = "stress period data" ,
458+ in_record = False ,
459+ ),
460+ "cellid" : FieldV1 (
461+ name = "cellid" ,
462+ type = "integer" ,
463+ block = "period" ,
464+ description = "cell identifier" ,
465+ in_record = True ,
466+ tagged = False ,
467+ ),
466468 }
467469 },
468470 )
469471
470- # Convert to v2
471472 dfn_v2 = map (dfn_v1 , schema_version = "2" )
472-
473- # Check that all fields are FieldV2 instances
474473 assert dfn_v2 .blocks is not None
475- for block_name , block_fields in dfn_v2 .blocks .items ():
476- for field_name , field in block_fields .items ():
477- assert isinstance (field , FieldV2 )
478- # Check nested children too
479- if field .children :
480- for child_name , child_field in field .children .items ():
481- assert isinstance (child_field , FieldV2 )
474+ for block_fields in dfn_v2 .blocks .values ():
475+ for f in block_fields .values ():
476+ assert isinstance (f , FieldBase )
477+ if f .children :
478+ for child in f .children .values ():
479+ assert isinstance (child , FieldBase )
482480
483481
484482def test_period_block_conversion ():
@@ -517,13 +515,13 @@ def test_period_block_conversion():
517515 dfn_v2 = map (dfn_v1 , schema_version = "2" )
518516
519517 period_block = dfn_v2 .blocks ["period" ]
520- assert "cellid" not in period_block # cellid removed
518+ assert "cellid" not in period_block
521519 assert "q" in period_block
522- assert isinstance ( period_block ["q" ], FieldV2 )
523- # Shape should be transformed: maxbound removed, nper and nnodes added
524- assert "nper" in period_block [ "q" ] .shape
525- assert "nnodes " in period_block [ "q" ] .shape
526- assert "maxbound" not in period_block [ "q" ] .shape
520+ q = period_block ["q" ]
521+ assert isinstance ( q , Array )
522+ assert "nper" in q .shape
523+ assert "nodes " in q .shape
524+ assert "maxbound" not in q .shape
527525
528526
529527def test_record_type_conversion ():
@@ -560,17 +558,17 @@ def test_record_type_conversion():
560558 dfn_v2 = map (dfn_v1 , schema_version = "2" )
561559
562560 auxrecord = dfn_v2 .blocks ["options" ]["auxrecord" ]
563- assert isinstance (auxrecord , FieldV2 )
561+ assert isinstance (auxrecord , Record )
564562 assert auxrecord .type == "record"
565563 assert auxrecord .children is not None
566564 assert "auxiliary" in auxrecord .children
567565 assert "auxname" in auxrecord .children
568- assert isinstance (auxrecord .children ["auxiliary" ], FieldV2 )
569- assert isinstance (auxrecord .children ["auxname" ], FieldV2 )
566+ assert isinstance (auxrecord .children ["auxiliary" ], Keyword )
567+ assert isinstance (auxrecord .children ["auxname" ], String )
570568
571569
572570def test_keystring_type_conversion ():
573- """Test keystring type conversion."""
571+ """Test keystring (union) type conversion."""
574572 from modflow_devtools .dfns import map
575573
576574 dfn_v1 = Dfn (
@@ -610,7 +608,7 @@ def test_keystring_type_conversion():
610608 dfn_v2 = map (dfn_v1 , schema_version = "2" )
611609
612610 obs_rec = dfn_v2 .blocks ["options" ]["obs_filerecord" ]
613- assert isinstance (obs_rec , FieldV2 )
611+ assert isinstance (obs_rec , Record )
614612 assert obs_rec .type == "record"
615613 assert obs_rec .children is not None
616- assert all (isinstance (child , FieldV2 ) for child in obs_rec .children .values ())
614+ assert all (isinstance (child , FieldBase ) for child in obs_rec .children .values ())
0 commit comments