Modified JSON API v1.0 for OPTIMADE API
A type that constrains strings to valid OPTIMADE identifiers (e.g., property names, ID strings).
Bases: Meta
Specific meta field for base relationship resource
Source code in optimade/models/optimade_json.py421
422
423
424
425
426
427
428
429 | class BaseRelationshipMeta(jsonapi.Meta):
"""Specific meta field for base relationship resource"""
description: Annotated[
str,
StrictField(
description="OPTIONAL human-readable description of the relationship."
),
]
|
Bases: BaseResource
Minimum requirements to represent a relationship resource
Source code in optimade/models/optimade_json.py432
433
434
435
436
437
438
439
440 | class BaseRelationshipResource(jsonapi.BaseResource):
"""Minimum requirements to represent a relationship resource"""
meta: Annotated[
BaseRelationshipMeta | None,
StrictField(
description="Relationship meta field. MUST contain 'description' if supplied.",
),
] = None
|
Bases: Enum
Optimade Data types
See the section "Data types" in the OPTIMADE API specification for more information.
Source code in optimade/models/optimade_json.py 39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
90
91
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 | class DataType(Enum):
"""Optimade Data types
See the section "Data types" in the OPTIMADE API specification for more information.
"""
STRING = "string"
INTEGER = "integer"
FLOAT = "float"
BOOLEAN = "boolean"
TIMESTAMP = "timestamp"
LIST = "list"
DICTIONARY = "dictionary"
UNKNOWN = "unknown"
@classmethod
def get_values(cls) -> list[str]:
"""Get OPTIMADE data types (enum values) as a (sorted) list"""
return sorted(_.value for _ in cls)
@classmethod
def from_python_type(cls, python_type: type | str | object) -> Optional["DataType"]:
"""Get OPTIMADE data type from a Python type"""
mapping = {
"bool": cls.BOOLEAN,
"int": cls.INTEGER,
"float": cls.FLOAT,
"complex": None,
"generator": cls.LIST,
"list": cls.LIST,
"tuple": cls.LIST,
"range": cls.LIST,
"hash": cls.INTEGER,
"str": cls.STRING,
"bytes": cls.STRING,
"bytearray": None,
"memoryview": None,
"set": cls.LIST,
"frozenset": cls.LIST,
"dict": cls.DICTIONARY,
"dict_keys": cls.LIST,
"dict_values": cls.LIST,
"dict_items": cls.LIST,
"Nonetype": cls.UNKNOWN,
"None": cls.UNKNOWN,
"datetime": cls.TIMESTAMP,
"date": cls.TIMESTAMP,
"time": cls.TIMESTAMP,
"datetime.datetime": cls.TIMESTAMP,
"datetime.date": cls.TIMESTAMP,
"datetime.time": cls.TIMESTAMP,
}
if isinstance(python_type, type):
python_type = python_type.__name__
elif isinstance(python_type, object):
if str(python_type) in mapping:
python_type = str(python_type)
else:
python_type = type(python_type).__name__
return mapping.get(python_type, None)
@classmethod
def from_json_type(cls, json_type: str) -> Optional["DataType"]:
"""Get OPTIMADE data type from a named JSON type"""
mapping = {
"string": cls.STRING,
"integer": cls.INTEGER,
"number": cls.FLOAT, # actually includes both integer and float
"object": cls.DICTIONARY,
"array": cls.LIST,
"boolean": cls.BOOLEAN,
"null": cls.UNKNOWN,
# OpenAPI "format"s:
"double": cls.FLOAT,
"float": cls.FLOAT,
"int32": cls.INTEGER,
"int64": cls.INTEGER,
"date": cls.TIMESTAMP,
"date-time": cls.TIMESTAMP,
"password": cls.STRING,
"byte": cls.STRING,
"binary": cls.STRING,
# Non-OpenAPI "format"s, but may still be used by pydantic/FastAPI
"email": cls.STRING,
"uuid": cls.STRING,
"uri": cls.STRING,
"hostname": cls.STRING,
"ipv4": cls.STRING,
"ipv6": cls.STRING,
}
return mapping.get(json_type, None)
|
Get OPTIMADE data type from a named JSON type
Source code in optimade/models/optimade_json.py102
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 | @classmethod
def from_json_type(cls, json_type: str) -> Optional["DataType"]:
"""Get OPTIMADE data type from a named JSON type"""
mapping = {
"string": cls.STRING,
"integer": cls.INTEGER,
"number": cls.FLOAT, # actually includes both integer and float
"object": cls.DICTIONARY,
"array": cls.LIST,
"boolean": cls.BOOLEAN,
"null": cls.UNKNOWN,
# OpenAPI "format"s:
"double": cls.FLOAT,
"float": cls.FLOAT,
"int32": cls.INTEGER,
"int64": cls.INTEGER,
"date": cls.TIMESTAMP,
"date-time": cls.TIMESTAMP,
"password": cls.STRING,
"byte": cls.STRING,
"binary": cls.STRING,
# Non-OpenAPI "format"s, but may still be used by pydantic/FastAPI
"email": cls.STRING,
"uuid": cls.STRING,
"uri": cls.STRING,
"hostname": cls.STRING,
"ipv4": cls.STRING,
"ipv6": cls.STRING,
}
return mapping.get(json_type, None)
|
Get OPTIMADE data type from a Python type
Source code in optimade/models/optimade_json.py 59
60
61
62
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
90
91
92
93
94
95
96
97
98
99
100 | @classmethod
def from_python_type(cls, python_type: type | str | object) -> Optional["DataType"]:
"""Get OPTIMADE data type from a Python type"""
mapping = {
"bool": cls.BOOLEAN,
"int": cls.INTEGER,
"float": cls.FLOAT,
"complex": None,
"generator": cls.LIST,
"list": cls.LIST,
"tuple": cls.LIST,
"range": cls.LIST,
"hash": cls.INTEGER,
"str": cls.STRING,
"bytes": cls.STRING,
"bytearray": None,
"memoryview": None,
"set": cls.LIST,
"frozenset": cls.LIST,
"dict": cls.DICTIONARY,
"dict_keys": cls.LIST,
"dict_values": cls.LIST,
"dict_items": cls.LIST,
"Nonetype": cls.UNKNOWN,
"None": cls.UNKNOWN,
"datetime": cls.TIMESTAMP,
"date": cls.TIMESTAMP,
"time": cls.TIMESTAMP,
"datetime.datetime": cls.TIMESTAMP,
"datetime.date": cls.TIMESTAMP,
"datetime.time": cls.TIMESTAMP,
}
if isinstance(python_type, type):
python_type = python_type.__name__
elif isinstance(python_type, object):
if str(python_type) in mapping:
python_type = str(python_type)
else:
python_type = type(python_type).__name__
return mapping.get(python_type, None)
|
Get OPTIMADE data types (enum values) as a (sorted) list
Source code in optimade/models/optimade_json.py54
55
56
57 | @classmethod
def get_values(cls) -> list[str]:
"""Get OPTIMADE data types (enum values) as a (sorted) list"""
return sorted(_.value for _ in cls)
|
Bases: BaseModel
Information on the server implementation
Source code in optimade/models/optimade_json.py246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284 | class Implementation(BaseModel):
"""Information on the server implementation"""
name: Annotated[
str | None, StrictField(description="name of the implementation")
] = None
version: Annotated[
str | None,
StrictField(description="version string of the current implementation"),
] = None
homepage: Annotated[
jsonapi.JsonLinkType | None,
StrictField(
description="A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the homepage of the implementation.",
),
] = None
source_url: Annotated[
jsonapi.JsonLinkType | None,
StrictField(
description="A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the implementation source, either downloadable archive or version control system.",
),
] = None
maintainer: Annotated[
ImplementationMaintainer | None,
StrictField(
description="A dictionary providing details about the maintainer of the implementation.",
),
] = None
issue_tracker: Annotated[
jsonapi.JsonLinkType | None,
StrictField(
description="A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the implementation's issue tracker.",
),
] = None
|
Bases: BaseModel
Details about the maintainer of the implementation
Source code in optimade/models/optimade_json.py238
239
240
241
242
243 | class ImplementationMaintainer(BaseModel):
"""Details about the maintainer of the implementation"""
email: Annotated[
EmailStr, StrictField(description="the maintainer's email address")
]
|
Bases: Error
detail MUST be present
Source code in optimade/models/optimade_json.py135
136
137
138
139
140
141
142
143 | class OptimadeError(jsonapi.Error):
"""detail MUST be present"""
detail: Annotated[
str,
StrictField(
description="A human-readable explanation specific to this occurrence of the problem.",
),
]
|
191
192 | def __hash__(self):
return hash(self.model_dump_json())
|
Bases: BaseModel
Information on the database provider of the implementation.
Source code in optimade/models/optimade_json.py209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235 | class Provider(BaseModel):
"""Information on the database provider of the implementation."""
name: Annotated[
str, StrictField(description="a short name for the database provider")
]
description: Annotated[
str, StrictField(description="a longer description of the database provider")
]
prefix: Annotated[
str,
StrictField(
pattern=r"^[a-z]([a-z]|[0-9]|_)*$",
description="database-provider-specific prefix as found in section Database-Provider-Specific Namespace Prefixes.",
),
]
homepage: Annotated[
jsonapi.JsonLinkType | None,
StrictField(
description="a [JSON API links object](http://jsonapi.org/format/1.0#document-links) "
"pointing to homepage of the database provider, either "
"directly as a string, or as a link object.",
),
] = None
|
Bases: Relationship
Similar to normal JSON API relationship, but with addition of OPTIONAL meta field for a resource.
Source code in optimade/models/optimade_json.py443
444
445
446
447
448
449 | class Relationship(jsonapi.Relationship):
"""Similar to normal JSON API relationship, but with addition of OPTIONAL meta field for a resource."""
data: Annotated[
BaseRelationshipResource | list[BaseRelationshipResource] | None,
StrictField(description="Resource linkage", uniqueItems=True),
] = None
|
279
280
281
282
283
284
285 | @model_validator(mode="after")
def at_least_one_relationship_key_must_be_set(self) -> "Relationship":
if self.links is None and self.data is None and self.meta is None:
raise ValueError(
"Either 'links', 'data', or 'meta' MUST be specified for Relationship"
)
return self
|
Bases: Meta
A JSON API meta member that contains JSON API meta objects of non-standard meta-information.
OPTIONAL additional information global to the query that is not specified in this document, MUST start with a database-provider-specific prefix.
Source code in optimade/models/optimade_json.py287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394 | class ResponseMeta(jsonapi.Meta):
"""
A [JSON API meta member](https://jsonapi.org/format/1.0#document-meta)
that contains JSON API meta objects of non-standard
meta-information.
OPTIONAL additional information global to the query that is not
specified in this document, MUST start with a
database-provider-specific prefix.
"""
query: Annotated[
ResponseMetaQuery,
StrictField(description="Information on the Query that was requested"),
]
api_version: Annotated[
SemanticVersion,
StrictField(
description="""Presently used full version of the OPTIMADE API.
The version number string MUST NOT be prefixed by, e.g., "v".
Examples: `1.0.0`, `1.0.0-rc.2`.""",
),
]
more_data_available: Annotated[
bool,
StrictField(
description="`false` if the response contains all data for the request (e.g., a request issued to a single entry endpoint, or a `filter` query at the last page of a paginated response) and `true` if the response is incomplete in the sense that multiple objects match the request, and not all of them have been included in the response (e.g., a query with multiple pages that is not at the last page).",
),
]
# start of "SHOULD" fields for meta response
optimade_schema: Annotated[
jsonapi.JsonLinkType | None,
StrictField(
alias="schema",
description="""A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) that points to a schema for the response.
If it is a string, or a dictionary containing no `meta` field, the provided URL MUST point at an [OpenAPI](https://swagger.io/specification/) schema.
It is possible that future versions of this specification allows for alternative schema types.
Hence, if the `meta` field of the JSON API links object is provided and contains a field `schema_type` that is not equal to the string `OpenAPI` the client MUST not handle failures to parse the schema or to validate the response against the schema as errors.""",
),
] = None
time_stamp: Annotated[
datetime | None,
StrictField(
description="A timestamp containing the date and time at which the query was executed.",
),
] = None
data_returned: Annotated[
int | None,
StrictField(
description="An integer containing the total number of data resource objects returned for the current `filter` query, independent of pagination.",
ge=0,
),
] = None
provider: Annotated[
Provider | None,
StrictField(
description="information on the database provider of the implementation."
),
] = None
# start of "MAY" fields for meta response
data_available: Annotated[
int | None,
StrictField(
description="An integer containing the total number of data resource objects available in the database for the endpoint.",
),
] = None
last_id: Annotated[
str | None,
StrictField(description="a string containing the last ID returned"),
] = None
response_message: Annotated[
str | None, StrictField(description="response string from the server")
] = None
request_delay: Annotated[
NonNegativeFloat | None,
StrictField(
description="""A non-negative float giving time in seconds that the client is suggested to wait before issuing a subsequent request.
Implementation note: the functionality of this field overlaps to some degree with features provided by the HTTP error `429 Too Many Requests` and the `Retry-After` HTTP header.
Implementations are suggested to provide consistent handling of request overload through both mechanisms."""
),
] = None
implementation: Annotated[
Implementation | None,
StrictField(description="a dictionary describing the server implementation"),
] = None
warnings: Annotated[
list[Warnings] | None,
StrictField(
description="""A list of warning resource objects representing non-critical errors or warnings.
A warning resource object is defined similarly to a [JSON API error object](http://jsonapi.org/format/1.0/#error-objects), but MUST also include the field `type`, which MUST have the value `"warning"`.
The field `detail` MUST be present and SHOULD contain a non-critical message, e.g., reporting unrecognized search attributes or deprecated features.
The field `status`, representing a HTTP response status code, MUST NOT be present for a warning resource object.
This is an exclusive field for error resource objects.""",
uniqueItems=True,
),
] = None
|
Bases: BaseModel
Information on the query that was requested.
Source code in optimade/models/optimade_json.py195
196
197
198
199
200
201
202
203
204
205
206 | class ResponseMetaQuery(BaseModel):
"""Information on the query that was requested."""
representation: Annotated[
str,
StrictField(
description="""A string with the part of the URL following the versioned or unversioned base URL that serves the API.
Query parameters that have not been used in processing the request MAY be omitted.
In particular, if no query parameters have been involved in processing the request, the query part of the URL MAY be excluded.
Example: `/structures?filter=nelements=2`""",
),
]
|
Bases: Response
errors are not allowed
Source code in optimade/models/optimade_json.py397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418 | class Success(jsonapi.Response):
"""errors are not allowed"""
meta: Annotated[
ResponseMeta,
StrictField(description="A meta object containing non-standard information"),
]
@model_validator(mode="after")
def either_data_meta_or_errors_must_be_set(self) -> "Success":
"""Overwriting the existing validation function, since 'errors' MUST NOT be set."""
required_fields = ("data", "meta")
if not any(field in self.model_fields_set for field in required_fields):
raise ValueError(
f"At least one of {required_fields} MUST be specified in the top-level response."
)
# errors MUST be skipped
if self.errors or "errors" in self.model_fields_set:
raise ValueError("'errors' MUST be skipped for a successful response.")
return self
|
The specification mandates that datetimes must be encoded following RFC3339, which does not support fractional seconds, thus they must be stripped in the response. This can cause issues when the underlying database contains fields that do include microseconds, as filters may return unexpected results.
Overwriting the existing validation function, since 'errors' MUST NOT be set.
Source code in optimade/models/optimade_json.py405
406
407
408
409
410
411
412
413
414
415
416
417
418 | @model_validator(mode="after")
def either_data_meta_or_errors_must_be_set(self) -> "Success":
"""Overwriting the existing validation function, since 'errors' MUST NOT be set."""
required_fields = ("data", "meta")
if not any(field in self.model_fields_set for field in required_fields):
raise ValueError(
f"At least one of {required_fields} MUST be specified in the top-level response."
)
# errors MUST be skipped
if self.errors or "errors" in self.model_fields_set:
raise ValueError("'errors' MUST be skipped for a successful response.")
return self
|
Bases: OptimadeError
OPTIMADE-specific warning class based on OPTIMADE-specific JSON API Error.
From the specification:
A warning resource object is defined similarly to a JSON API error object, but MUST also include the field type, which MUST have the value "warning". The field detail MUST be present and SHOULD contain a non-critical message, e.g., reporting unrecognized search attributes or deprecated features.
Note: Must be named "Warnings", since "Warning" is a built-in Python class.
Source code in optimade/models/optimade_json.py166
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 | class Warnings(OptimadeError):
"""OPTIMADE-specific warning class based on OPTIMADE-specific JSON API Error.
From the specification:
A warning resource object is defined similarly to a JSON API error object, but MUST also include the field type, which MUST have the value "warning".
The field detail MUST be present and SHOULD contain a non-critical message, e.g., reporting unrecognized search attributes or deprecated features.
Note: Must be named "Warnings", since "Warning" is a built-in Python class.
"""
model_config = ConfigDict(json_schema_extra=warnings_json_schema_extra)
type: Annotated[
Literal["warning"],
StrictField(
description='Warnings must be of type "warning"',
pattern="^warning$",
),
] = "warning"
@model_validator(mode="after")
def status_must_not_be_specified(self) -> "Warnings":
if self.status or "status" in self.model_fields_set:
raise ValueError("status MUST NOT be specified for warnings")
return self
|
191
192 | def __hash__(self):
return hash(self.model_dump_json())
|
188
189
190
191
192 | @model_validator(mode="after")
def status_must_not_be_specified(self) -> "Warnings":
if self.status or "status" in self.model_fields_set:
raise ValueError("status MUST NOT be specified for warnings")
return self
|
Update OpenAPI JSON schema model for Warning.
Since type is the last model field defined, it will simply be appended.
Source code in optimade/models/optimade_json.py146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163 | def warnings_json_schema_extra(schema: dict[str, Any], model: type["Warnings"]) -> None:
"""Update OpenAPI JSON schema model for `Warning`.
* Ensure `type` is in the list required properties and in the correct place.
* Remove `status` property.
This property is not allowed for `Warning`, nor is it a part of the OPTIMADE
definition of the `Warning` object.
Note:
Since `type` is the _last_ model field defined, it will simply be appended.
"""
if "required" in schema:
if "type" not in schema["required"]:
schema["required"].append("type")
else:
schema["required"] = ["type"]
schema.get("properties", {}).pop("status", None)
|