Skip to content

dataset_instance

Pydantic models for a Dataverse dataset export (GET /api/datasets/:id response).

This is the companion to dataverse_schema.py: that file models the schema (what fields CAN exist and their rules), this file models actual metadata values attached to one dataset. The shapes are different — here each field is {typeName, multiple, typeClass, value}, and value itself is polymorphic depending on typeClass/multiple:

  • primitive / controlledVocabulary, multiple=False -> a plain string
  • primitive / controlledVocabulary, multiple=True -> a list of strings
  • compound, multiple=False -> a dict of nested fields
  • compound, multiple=True -> a list of dicts of nested fields

DatasetData

Bases: BaseModel

The 'data' payload of a dataset export response.

Source code in src/dv_schema_models/dataset_instance.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
class DatasetData(BaseModel):
    """The 'data' payload of a dataset export response."""

    model_config = ConfigDict(extra="allow")

    id: int
    identifier: str
    persistentUrl: str
    protocol: str
    authority: str
    separator: str
    publisher: str
    storageIdentifier: str
    isPartOf: IsPartOf | None = None
    datasetType: str | None = (
        None  # backward compatibility: some datasets /older Dataverse versions don't have this field
    )

    latestVersion: DatasetVersion | None = Field(
        None, validation_alias=AliasChoices("datasetVersion", "latestVersion")
    )  # Deaccessioned dataset does not have this field.

    @property
    def datasetVersion(self) -> DatasetVersion | None:
        """Supports both export and Native JSON endpoints.

        Returns
        -------
        DatasetVersion | None
            The latest version of the dataset, or None if not present.

        """
        return self.latestVersion

    def get_raw(self, key: str) -> object | None:
        """Get a raw top-level field not covered by the schema.

        Parameters
        ----------
        key
            The name of the top-level field to retrieve.

        Returns
        -------
        object | None
            The value of the specified field, or None if not found.

        """
        return self.model_extra.get(key) if self.model_extra else None

datasetVersion property

Supports both export and Native JSON endpoints.

Returns:

Type Description
DatasetVersion | None

The latest version of the dataset, or None if not present.

get_raw(key)

Get a raw top-level field not covered by the schema.

Parameters:

Name Type Description Default
key str

The name of the top-level field to retrieve.

required

Returns:

Type Description
object | None

The value of the specified field, or None if not found.

Source code in src/dv_schema_models/dataset_instance.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
def get_raw(self, key: str) -> object | None:
    """Get a raw top-level field not covered by the schema.

    Parameters
    ----------
    key
        The name of the top-level field to retrieve.

    Returns
    -------
    object | None
        The value of the specified field, or None if not found.

    """
    return self.model_extra.get(key) if self.model_extra else None

DatasetFieldValue

Bases: BaseModel

One metadata field value, as it appears inside metadataBlocks..fields.

Source code in src/dv_schema_models/dataset_instance.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class DatasetFieldValue(BaseModel):
    """One metadata field value, as it appears inside metadataBlocks.<block>.fields."""

    model_config = ConfigDict(extra="ignore")

    typeName: str
    multiple: bool
    typeClass: str  # 'primitive', 'compound', or 'controlledVocabulary'
    value: str | list[str] | dict[str, DatasetFieldValue] | list[dict[str, DatasetFieldValue]]

    def simple_value(self) -> Any:
        """Recursively strip away the typeName/multiple/typeClass wrapper, returning plain Python values.

        Compound fields become plain dicts (or lists of dicts); primitive and
        controlledVocabulary fields are already plain strings or lists of strings.
        """
        if self.typeClass == "compound":
            if isinstance(self.value, list):
                items = cast("list[dict[str, DatasetFieldValue]]", self.value)
                return [{k: v.simple_value() for k, v in item.items()} for item in items]
            if isinstance(self.value, dict):
                return {k: v.simple_value() for k, v in self.value.items()}
        return self.value

    def get_fields(self) -> list[DatasetFieldValue]:
        """Return a flat list of all DatasetFieldValue objects nested inside this one."""
        if self.typeClass != "compound":
            return []
        if isinstance(self.value, dict):
            return list(self.value.values())
        items = cast("list[dict[str, DatasetFieldValue]]", self.value)
        return [v for item in items for v in item.values()]

get_fields()

Return a flat list of all DatasetFieldValue objects nested inside this one.

Source code in src/dv_schema_models/dataset_instance.py
49
50
51
52
53
54
55
56
def get_fields(self) -> list[DatasetFieldValue]:
    """Return a flat list of all DatasetFieldValue objects nested inside this one."""
    if self.typeClass != "compound":
        return []
    if isinstance(self.value, dict):
        return list(self.value.values())
    items = cast("list[dict[str, DatasetFieldValue]]", self.value)
    return [v for item in items for v in item.values()]

simple_value()

Recursively strip away the typeName/multiple/typeClass wrapper, returning plain Python values.

Compound fields become plain dicts (or lists of dicts); primitive and controlledVocabulary fields are already plain strings or lists of strings.

Source code in src/dv_schema_models/dataset_instance.py
35
36
37
38
39
40
41
42
43
44
45
46
47
def simple_value(self) -> Any:
    """Recursively strip away the typeName/multiple/typeClass wrapper, returning plain Python values.

    Compound fields become plain dicts (or lists of dicts); primitive and
    controlledVocabulary fields are already plain strings or lists of strings.
    """
    if self.typeClass == "compound":
        if isinstance(self.value, list):
            items = cast("list[dict[str, DatasetFieldValue]]", self.value)
            return [{k: v.simple_value() for k, v in item.items()} for item in items]
        if isinstance(self.value, dict):
            return {k: v.simple_value() for k, v in self.value.items()}
    return self.value

DatasetJson

Bases: BaseModel

Top-level wrapper matching the raw JSON returned by GET /api/datasets/:id.

Source code in src/dv_schema_models/dataset_instance.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
class DatasetJson(BaseModel):
    """Top-level wrapper matching the raw JSON returned by GET /api/datasets/:id."""

    model_config = ConfigDict(extra="ignore")

    status: Literal["OK", "ERROR"]
    data: DatasetData

    def get_value(
        self, block_name: str, type_name: str
    ) -> str | list[str] | dict[str, Any] | list[dict[str, Any]] | None:
        """Get the value of a field by its block and type names.

        Parameters
        ----------
            block_name: The name of the metadata block (e.g. 'citation', 'geospatial').
            type_name: The typeName of the field within that block (e.g. 'title', 'author').

        Returns
        -------
            str | list[str] | dict[str, Any] | list[dict[str, Any]] | None: The value of the specified field, or None if not found.

        """  # ruff:ignore[doc-line-too-long]
        return (
            self.data.latestVersion.get_value(block_name, type_name)
            if self.data.latestVersion
            else None
        )

    def field_names(self, block_name: str) -> list[str]:
        """Check what fields are present in a given block.

        Parameters
        ----------
            block_name: The name of the metadata block (e.g. 'citation', 'geospatial').

        Returns
        -------
            list[str]: A list of typeNames for the fields present in the specified block.

        """
        return self.data.latestVersion.field_names(block_name) if self.data.latestVersion else []

field_names(block_name)

Check what fields are present in a given block.

Returns:

Type Description
list[str]: A list of typeNames for the fields present in the specified block.
Source code in src/dv_schema_models/dataset_instance.py
292
293
294
295
296
297
298
299
300
301
302
303
304
def field_names(self, block_name: str) -> list[str]:
    """Check what fields are present in a given block.

    Parameters
    ----------
        block_name: The name of the metadata block (e.g. 'citation', 'geospatial').

    Returns
    -------
        list[str]: A list of typeNames for the fields present in the specified block.

    """
    return self.data.latestVersion.field_names(block_name) if self.data.latestVersion else []

get_value(block_name, type_name)

Get the value of a field by its block and type names.

Returns:

Type Description
str | list[str] | dict[str, Any] | list[dict[str, Any]] | None: The value of the specified field, or None if not found.
Source code in src/dv_schema_models/dataset_instance.py
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
def get_value(
    self, block_name: str, type_name: str
) -> str | list[str] | dict[str, Any] | list[dict[str, Any]] | None:
    """Get the value of a field by its block and type names.

    Parameters
    ----------
        block_name: The name of the metadata block (e.g. 'citation', 'geospatial').
        type_name: The typeName of the field within that block (e.g. 'title', 'author').

    Returns
    -------
        str | list[str] | dict[str, Any] | list[dict[str, Any]] | None: The value of the specified field, or None if not found.

    """  # ruff:ignore[doc-line-too-long]
    return (
        self.data.latestVersion.get_value(block_name, type_name)
        if self.data.latestVersion
        else None
    )

DatasetVersion

Bases: BaseModel

One version of a dataset, holding the actual metadata block values and files.

Source code in src/dv_schema_models/dataset_instance.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
class DatasetVersion(BaseModel):
    """One version of a dataset, holding the actual metadata block values and files."""

    model_config = ConfigDict(extra="allow")

    id: int
    datasetId: int
    versionState: str
    datasetPersistentId: str
    datasetType: (
        str | None
    )  # backward compatibility: some datasets /older Dataverse versions don't have this field
    storageIdentifier: str
    versionNumber: int | None = None
    internalVersionNumber: int
    versionMinorNumber: int | None = None
    latestVersionPublishingState: str
    deaccessionLink: str | None = Field(None)
    UNF: str | None = Field(None)
    lastUpdateTime: str
    releaseTime: str | None = Field(None)
    createTime: str
    termsOfUse: str | None = Field(None)
    termsOfAccess: str | None = Field(None)
    dataAccessPlace: str | None = Field(None)
    fileAccessRequest: bool

    metadataBlocks: dict[str, MetadataBlockInstance]

    files: list[FileInstance] | None = None

    def get_value(self, block_name: str, type_name: str) -> Any:
        """Get a field's plain value by block name (e.g. 'citation') and field typeName (e.g. 'title')."""
        block = self.metadataBlocks.get(block_name)
        return block.get_value(type_name) if block else None

    def field_names(self, block_name: str) -> list[str]:
        """Get the typeNames of every field present in the given block (e.g. 'citation').

        Parameters
        ----------
        block_name
            The name of the metadata block (e.g. 'citation', 'geospatial').

        Returns
        -------
        list[str]
            A list of typeNames for the fields present in the specified block.

        """
        block = self.metadataBlocks.get(block_name)
        return block.field_names() if block else []

    def get_raw(self, key: str) -> object | None:
        """Get a raw top-level field not covered by the schema.

        Parameters
        ----------
        key
            The name of the top-level field to retrieve.

        Returns
        -------
        object | None
            The value of the specified field, or None if not found.


        """
        return self.model_extra.get(key) if self.model_extra else None

field_names(block_name)

Get the typeNames of every field present in the given block (e.g. 'citation').

Parameters:

Name Type Description Default
block_name str

The name of the metadata block (e.g. 'citation', 'geospatial').

required

Returns:

Type Description
list[str]

A list of typeNames for the fields present in the specified block.

Source code in src/dv_schema_models/dataset_instance.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def field_names(self, block_name: str) -> list[str]:
    """Get the typeNames of every field present in the given block (e.g. 'citation').

    Parameters
    ----------
    block_name
        The name of the metadata block (e.g. 'citation', 'geospatial').

    Returns
    -------
    list[str]
        A list of typeNames for the fields present in the specified block.

    """
    block = self.metadataBlocks.get(block_name)
    return block.field_names() if block else []

get_raw(key)

Get a raw top-level field not covered by the schema.

Parameters:

Name Type Description Default
key str

The name of the top-level field to retrieve.

required

Returns:

Type Description
object | None

The value of the specified field, or None if not found.

Source code in src/dv_schema_models/dataset_instance.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def get_raw(self, key: str) -> object | None:
    """Get a raw top-level field not covered by the schema.

    Parameters
    ----------
    key
        The name of the top-level field to retrieve.

    Returns
    -------
    object | None
        The value of the specified field, or None if not found.


    """
    return self.model_extra.get(key) if self.model_extra else None

get_value(block_name, type_name)

Get a field's plain value by block name (e.g. 'citation') and field typeName (e.g. 'title').

Source code in src/dv_schema_models/dataset_instance.py
123
124
125
126
def get_value(self, block_name: str, type_name: str) -> Any:
    """Get a field's plain value by block name (e.g. 'citation') and field typeName (e.g. 'title')."""
    block = self.metadataBlocks.get(block_name)
    return block.get_value(type_name) if block else None

IsPartOf

Bases: BaseModel

The 'isPartOf' payload of a dataset export response.

See: https://github.com/IQSS/dataverse/blob/dd1859dde249df8b0612ca3899b5f00fcc6d082e/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java#L533-L562 (v6.11)

Source code in src/dv_schema_models/dataset_instance.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class IsPartOf(BaseModel):
    """The 'isPartOf' payload of a dataset export response.

    See: https://github.com/IQSS/dataverse/blob/dd1859dde249df8b0612ca3899b5f00fcc6d082e/src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java#L533-L562 (v6.11)

    """  # ruff:ignore[doc-line-too-long]

    model_config = ConfigDict(extra="allow")

    type: str
    identifier: str
    isReleased: bool | None = None  # backward compatibility
    persistentIdentifier: str | None = None
    displayName: str
    isPartOf: IsPartOf | None = None  # recursive

    @staticmethod
    def get_field_list(node: IsPartOf | None, field_name: str, *, from_root: bool = False) -> list:
        """Return the values of a field from a node to the root.

        Parameters
        ----------
        node
            The node to start from. May be None, in which case an empty list is returned.

        field_name
            The name of the field to retrieve (e.g. 'identifier', 'displayName').

        from_root
            If True, return the values from the root to this node; if False, return from this node to the root.

        Returns
        -------
        list
            A list of the values of the specified field.

        """  # ruff:ignore[doc-line-too-long]
        values = []
        current = node

        while current is not None:
            values.append(getattr(current, field_name))
            current = current.isPartOf

        if not from_root:
            values.reverse()
        return values

get_field_list(node, field_name, *, from_root=False) staticmethod

Return the values of a field from a node to the root.

Parameters:

Name Type Description Default
node IsPartOf | None

The node to start from. May be None, in which case an empty list is returned.

required
field_name str

The name of the field to retrieve (e.g. 'identifier', 'displayName').

required
from_root bool

If True, return the values from the root to this node; if False, return from this node to the root.

False

Returns:

Type Description
list

A list of the values of the specified field.

Source code in src/dv_schema_models/dataset_instance.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
@staticmethod
def get_field_list(node: IsPartOf | None, field_name: str, *, from_root: bool = False) -> list:
    """Return the values of a field from a node to the root.

    Parameters
    ----------
    node
        The node to start from. May be None, in which case an empty list is returned.

    field_name
        The name of the field to retrieve (e.g. 'identifier', 'displayName').

    from_root
        If True, return the values from the root to this node; if False, return from this node to the root.

    Returns
    -------
    list
        A list of the values of the specified field.

    """  # ruff:ignore[doc-line-too-long]
    values = []
    current = node

    while current is not None:
        values.append(getattr(current, field_name))
        current = current.isPartOf

    if not from_root:
        values.reverse()
    return values

MetadataBlockInstance

Bases: BaseModel

One populated metadata block (e.g. citation) within a dataset version.

Source code in src/dv_schema_models/dataset_instance.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class MetadataBlockInstance(BaseModel):
    """One populated metadata block (e.g. citation) within a dataset version."""

    model_config = ConfigDict(extra="ignore")

    displayName: str
    name: str
    fields: list[DatasetFieldValue]

    def field_names(self) -> list[str]:
        """Return the typeName of every field present in this block instance."""
        return [f.typeName for f in self.fields]

    def get_field(self, type_name: str) -> DatasetFieldValue | None:
        """Find a field in this block by its typeName (e.g. 'title', 'author')."""
        return next((f for f in self.fields if f.typeName == type_name), None)

    def get_value(self, type_name: str) -> Any:
        """Get the plain (unwrapped) value of a field in this block, or None if absent."""
        field = self.get_field(type_name)
        return field.simple_value() if field else None

    def get_subfield_values(self, type_name: str, subfield: str) -> list[Any]:
        """Collect one subfield from a compound field, e.g. get_subfield_values('author', 'authorName') -> ['Author1', 'Author2']."""
        value = self.get_value(type_name)
        items = value if isinstance(value, list) else [value] if value else []
        return [item[subfield] for item in items if subfield in item]

field_names()

Return the typeName of every field present in this block instance.

Source code in src/dv_schema_models/dataset_instance.py
72
73
74
def field_names(self) -> list[str]:
    """Return the typeName of every field present in this block instance."""
    return [f.typeName for f in self.fields]

get_field(type_name)

Find a field in this block by its typeName (e.g. 'title', 'author').

Source code in src/dv_schema_models/dataset_instance.py
76
77
78
def get_field(self, type_name: str) -> DatasetFieldValue | None:
    """Find a field in this block by its typeName (e.g. 'title', 'author')."""
    return next((f for f in self.fields if f.typeName == type_name), None)

get_subfield_values(type_name, subfield)

Collect one subfield from a compound field, e.g. get_subfield_values('author', 'authorName') -> ['Author1', 'Author2'].

Source code in src/dv_schema_models/dataset_instance.py
85
86
87
88
89
def get_subfield_values(self, type_name: str, subfield: str) -> list[Any]:
    """Collect one subfield from a compound field, e.g. get_subfield_values('author', 'authorName') -> ['Author1', 'Author2']."""
    value = self.get_value(type_name)
    items = value if isinstance(value, list) else [value] if value else []
    return [item[subfield] for item in items if subfield in item]

get_value(type_name)

Get the plain (unwrapped) value of a field in this block, or None if absent.

Source code in src/dv_schema_models/dataset_instance.py
80
81
82
83
def get_value(self, type_name: str) -> Any:
    """Get the plain (unwrapped) value of a field in this block, or None if absent."""
    field = self.get_field(type_name)
    return field.simple_value() if field else None

load_dataset(metadata)

Parse a dataset export JSON payload (already loaded as a dict) into a DatasetExport.

Accepts either the full {status, data: {...}} export envelope, or a bare data-shaped payload (no envelope), by trying each model in turn.

Parameters:

Name Type Description Default
metadata dict

The JSON payload from the Dataverse API, already loaded as a dict.

required

Returns:

Type Description
DatasetJson

The parsed dataset.

Source code in src/dv_schema_models/dataset_instance.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def load_dataset(metadata: dict) -> DatasetJson:
    """Parse a dataset export JSON payload (already loaded as a dict) into a DatasetExport.

    Accepts either the full `{status, data: {...}}` export envelope, or a bare
    `data`-shaped payload (no envelope), by trying each model in turn.

    Parameters
    ----------
    metadata
        The JSON payload from the Dataverse API, already loaded as a dict.

    Returns
    -------
    DatasetJson
        The parsed dataset.

    """
    if "data" in metadata:
        return DatasetJson.model_validate(metadata)
    return DatasetJson(status="OK", data=DatasetData.model_validate(metadata))

safe_load_dataset(metadata)

Like load_dataset, but return the API's error message instead of raising when status is "ERROR".

Parameters:

Name Type Description Default
metadata dict

The JSON payload from the Dataverse API, already loaded as a dict.

required

Returns:

Type Description
DatasetJson | str | None

The parsed dataset, or the API's error message (if any) if the status is "ERROR".

Source code in src/dv_schema_models/dataset_instance.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def safe_load_dataset(metadata: dict) -> DatasetJson | str | None:
    """Like `load_dataset`, but return the API's error message instead of raising when status is "ERROR".

    Parameters
    ----------
    metadata
        The JSON payload from the Dataverse API, already loaded as a dict.

    Returns
    -------
    DatasetJson | str | None
        The parsed dataset, or the API's error message (if any) if the status is "ERROR".

    """  # ruff:ignore[doc-line-too-long]
    if metadata.get("status") == "OK":
        return load_dataset(metadata)
    return metadata.get("message")