cthoyt / zenodo-client

A tool for automated uploading and version management of scientific data to Zenodo
MIT License
25 stars 5 forks source link

Creator.__post_init__ does not run when expected #37

Closed elichad closed 3 months ago

elichad commented 3 months ago

The __post_init__ function on the Creator model does not appear to run when a Creator is initialised, nor afterward when publishing an upload. https://github.com/cthoyt/zenodo-client/blob/main/src/zenodo_client/struct.py#L52

>>> from zenodo_client import Creator
>>> Creator(name="Test Person")
Creator(name='Test Person', affiliation=None, orcid=None, gnd=None)

Switching to model_post_init, as suggested here https://github.com/pydantic/pydantic/issues/7255, it does run:

>>> from zenodo_client import Creator
>>> Creator(name="Test Person")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/eli/manchester/zenodo-client/.venv/lib/python3.11/site-packages/pydantic/main.py", line 176, in __init__
    self.__pydantic_validator__.validate_python(data, self_instance=self)
pydantic_core._pydantic_core.ValidationError: 1 validation error for Creator
  Value error, name should be in format Family name, given names [type=value_error, input_value={'name': 'Test Person'}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.7/v/value_error
cthoyt commented 3 months ago

That's a good catch! I think I wrote this code a long time before I understood model validation in pydantic, there are actually even more specific ways to do this. The following makes it possible to have pydantic 1/2 cross-compatible with the field validator

from pydantic import __version__ as pydantic_version

PYDANTIC_V1 = pydantic_version.startswith("1.")

if PYDANTIC_V1:
    from pydantic import validator as field_validator
else:
    from pydantic import field_validator

def get_field_validator_values(values, key: str):  # type:ignore
    """Get the value for the key from a field validator object, cross-compatible with Pydantic 1 and 2."""
    if PYDANTIC_V1:
        return values[key]
    else:
        return values.data[key]

then write something like:

   @field_validator("name")  # type:ignore
    def comma_in_name(cls, v: str, values: Mapping[str, Any]) -> str:  # noqa:N805
        """Check that a comma appears in the name."""
        prefix = get_field_validator_values(values, "name")
        if "," not in v:
            raise ValueError(f"missing comma in name")
        return v