77from types import NoneType
88from typing import Any , get_args , get_origin
99
10- from pydantic import BaseModel , ConfigDict , Field , model_validator
10+ from pydantic import BaseModel , ConfigDict , Field , Json , model_validator
1111from pydantic_core import PydanticUndefined
1212
1313logger = logging .getLogger (__name__ )
1414
15+ # JSON types representing the expected structure after parsing
16+ type JsonList = list [Any ]
17+
18+ # Cache for schema-related warnings to avoid per-row logging
19+ _SCHEMA_WARNING_CACHE : set [tuple [str , str , str ]] = set ()
20+
1521
1622def spec_field (
1723 * ,
@@ -190,11 +196,14 @@ def _report_validation_issues(
190196 )
191197
192198 if missing_optional :
193- logger .warning (
194- 'Table "%s" is missing optional columns: %s. Using default values.' ,
195- cls .__name__ ,
196- ", " .join (missing_optional ),
197- )
199+ cache_key = (cls .__name__ , "missing_optional" , "," .join (sorted (missing_optional )))
200+ if cache_key not in _SCHEMA_WARNING_CACHE :
201+ logger .warning (
202+ 'Table "%s" is missing optional columns: %s. Using default values.' ,
203+ cls .__name__ ,
204+ ", " .join (missing_optional ),
205+ )
206+ _SCHEMA_WARNING_CACHE .add (cache_key )
198207
199208 @model_validator (mode = "before" )
200209 @classmethod
@@ -208,11 +217,15 @@ def validate_row(cls, data: Any) -> Any:
208217 # 1. Check for extra columns
209218 extra_columns = sorted (set (row_mapping .keys ()) - set (cls .model_fields .keys ()))
210219 if extra_columns :
211- logger .warning (
212- 'Table "%s": Input contains extra columns not defined in model: %s. Accepting due to extra="allow".' ,
213- cls .__name__ ,
214- ", " .join (extra_columns ),
215- )
220+ cache_key = (cls .__name__ , "extra_columns" , "," .join (extra_columns ))
221+ if cache_key not in _SCHEMA_WARNING_CACHE :
222+ logger .warning (
223+ 'Table "%s": Input contains extra columns not defined in model: %s. '
224+ 'Accepting due to extra="allow".' ,
225+ cls .__name__ ,
226+ ", " .join (extra_columns ),
227+ )
228+ _SCHEMA_WARNING_CACHE .add (cache_key )
216229
217230 # 2. Process field values (NULLs, Coercion, Overrides)
218231 coerced_fields : list [str ] = []
@@ -274,7 +287,7 @@ class AssayRow(BaseRow):
274287
275288 identifier : str = spec_field ()
276289 investigation_ref : str = spec_field ()
277- study_ref : str | None = spec_field (default = None )
290+ study_ref : Json [ JsonList ] | None = spec_field (default = None )
278291 title : str | None = spec_field (default = None )
279292 description_text : str | None = spec_field (default = None )
280293 measurement_type_term : str | None = spec_field (default = None )
@@ -314,5 +327,5 @@ class ContactRow(BaseRow):
314327 fax : str | None = spec_field (default = None )
315328 postal_address : str | None = spec_field (default = None )
316329 affiliation : str | None = spec_field (default = None )
317- roles : str | None = spec_field (default = None ) # JSON string
330+ roles : Json [ JsonList ] | None = spec_field (default = None )
318331 target_ref : str | None = spec_field (default = None )
0 commit comments