coercion for extra columns passed as arrays

This commit is contained in:
sneakers-the-rat 2024-08-07 20:23:18 -07:00
parent cebb21993d
commit 92d28baedd
Signed by untrusted user who does not match committer: jonny
GPG key ID: 6DCB96EF1E4D232D
14 changed files with 463 additions and 66 deletions

View file

@ -227,6 +227,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -263,11 +289,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)
@ -361,7 +391,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -412,7 +442,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -424,7 +454,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):

View file

@ -136,7 +136,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -187,7 +187,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -199,7 +199,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -405,6 +405,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -441,11 +467,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -136,7 +136,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -187,7 +187,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -199,7 +199,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -405,6 +405,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -441,11 +467,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -136,7 +136,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -187,7 +187,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -199,7 +199,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -405,6 +405,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -441,11 +467,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -137,7 +137,7 @@ class VectorIndexMixin(BaseModel):
if self.target is None: if self.target is None:
return self.value[item] return self.value[item]
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self._getitem_helper(item) return self._getitem_helper(item)
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -188,7 +188,7 @@ class DynamicTableRegionMixin(BaseModel):
this being a subclass of ``VectorData`` this being a subclass of ``VectorData``
""" """
if self._index: if self._index:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
# index returns an array of indices, # index returns an array of indices,
# and indexing table with an array returns a list of rows # and indexing table with an array returns a list of rows
return self.table[self._index[item]] return self.table[self._index[item]]
@ -200,7 +200,7 @@ class DynamicTableRegionMixin(BaseModel):
else: else:
raise ValueError(f"Dont know how to index with {item}, need an int or a slice") raise ValueError(f"Dont know how to index with {item}, need an int or a slice")
else: else:
if isinstance(item, int): if isinstance(item, (int, np.integer)):
return self.table[self.value[item]] return self.table[self.value[item]]
elif isinstance(item, (slice, Iterable)): elif isinstance(item, (slice, Iterable)):
if isinstance(item, slice): if isinstance(item, slice):
@ -406,6 +406,32 @@ class DynamicTableMixin(BaseModel):
model["colnames"].extend(colnames) model["colnames"].extend(colnames)
return model return model
@model_validator(mode="after")
def cast_extra_columns(self):
"""
If extra columns are passed as just lists or arrays, cast to VectorData
before we resolve targets for VectorData and VectorIndex pairs.
See :meth:`.cast_specified_columns` for handling columns in the class specification
"""
# if columns are not in the specification, cast to a generic VectorData
for key, val in self.__pydantic_extra__.items():
if not isinstance(val, (VectorData, VectorIndex)):
try:
if key.endswith("_index"):
self.__pydantic_extra__[key] = VectorIndex(
name=key, description="", value=val
)
else:
self.__pydantic_extra__[key] = VectorData(
name=key, description="", value=val
)
except ValidationError as e:
raise ValidationError(
f"field {key} cannot be cast to VectorData from {val}"
) from e
return self
@model_validator(mode="after") @model_validator(mode="after")
def resolve_targets(self) -> "DynamicTableMixin": def resolve_targets(self) -> "DynamicTableMixin":
""" """
@ -442,11 +468,15 @@ class DynamicTableMixin(BaseModel):
@field_validator("*", mode="wrap") @field_validator("*", mode="wrap")
@classmethod @classmethod
def cast_columns( def cast_specified_columns(
cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo cls, val: Any, handler: ValidatorFunctionWrapHandler, info: ValidationInfo
) -> Any: ) -> Any:
""" """
If columns are supplied as arrays, try casting them to the type before validating If columns *in* the model specification are supplied as arrays,
try casting them to the type before validating.
Columns that are not in the spec are handled separately in
:meth:`.cast_extra_columns`
""" """
try: try:
return handler(val) return handler(val)

View file

@ -211,7 +211,7 @@ def test_dynamictable_region_ragged():
name="table", name="table",
description="a table what else would it be", description="a table what else would it be",
id=np.arange(len(spike_idx)), id=np.arange(len(spike_idx)),
timeseries=spike_times, timeseries=spike_times_flat,
timeseries_index=spike_idx, timeseries_index=spike_idx,
) )
region = DynamicTableRegion( region = DynamicTableRegion(
@ -242,3 +242,10 @@ def test_dynamictable_append_column():
def test_dynamictable_append_row(): def test_dynamictable_append_row():
pass pass
def test_dynamictable_extra_coercion():
"""
Extra fields should be coerced to VectorData and have their
indexing relationships handled when passed as plain arrays.
"""