# Copyright 2019 Kensho Technologies, LLC.
"""Module for Wikidata Snaks."""
from typing import Union
from qwikidata import typedefs
from qwikidata.datavalue import WikidataDatavalue, get_datavalue_from_snak_dict
[docs]class WikidataSnak:
"""A Wikidata snak.
Parameters
----------
snak_dict: dict
A dictionary representing a Wikidata snak.
See `the wikibase JSON data model docs`_ for a description
of the format.
Attributes
----------
snaktype: str
One of ["value", "somevalue", "novalue"]. "value" indicates that a known value exists,
"somevalue" indicates that an unknown value exists, and "novalue" indicates that no
value exists.
property_id: str
A Wikidata property id (e.g. "P551")
snak_datatype: str or `None`
The snak data type (SDT). Must be one of ["commonsMedia", "external-id", "geo-shape",
"globe-coordinate", "math", "monolingualtext", "quantity", "string", "tabular-data", "time",
"url", "wikibase-item", "wikibase-property", `None`]. Will be `None` if `snaktype` is not
"value".
value_datatype: str or `None`
The value data type (VDT). Must be one of ["globecoordinate", "monolingualtext",
"quantity", "string", "time", "wikibase-entityid", `None`]. Will be `None` if `snaktype`
is not "value".
datavalue: :py:class:`WikidataDataValue` or `None`
The datavalue object for this snak. Has `datatype` and `value` attributes. (this data type
is the same as VDT above).
.. note::
There are two related data types here, the snak data type (SDT) and the value data
type (VDT). The SDT is derived from the data type of the property referenced by
`property_id` and is stored in the `snak_datatype` attribute. The VDT defines
the structure of the `value` attribute of :py:class:`WikidataDataValue`.
The VDT does not allow for interpretation of the datavalue, only for processing of the raw
structure. As an example, a link to a web page may have SDT="url", but have VDT="string".
.. _the wikibase JSON data model docs: https://www.mediawiki.org/wiki/Wikibase/DataModel/JSON
"""
def __init__(self, snak_dict: typedefs.SnakDict) -> None:
self._validate_snak_dict(snak_dict)
self._snak_dict = snak_dict
def _validate_snak_dict(self, snak_dict: typedefs.SnakDict) -> None:
"""Raise excpetions if snak_dict is not valid."""
_REQUIRED_KEYS = ["snaktype", "property"]
for req_key in _REQUIRED_KEYS:
if req_key not in snak_dict:
raise ValueError(
"required snak_dict keys are {} but only found {}".format(
_REQUIRED_KEYS, list(snak_dict.keys())
)
)
self.snaktype = snak_dict["snaktype"]
self.property_id = snak_dict["property"]
self.snak_datatype = None # type: Union[str, None]
self.value_datatype = None # type: Union[str, None]
self.datavalue = None # type: Union[WikidataDatavalue, None]
if self.snaktype == "value":
_REQUIRED_KEYS = ["datavalue", "datatype"]
for req_key in _REQUIRED_KEYS:
if req_key not in snak_dict:
raise ValueError(
"required snak_dict keys are {} but only found {}".format(
_REQUIRED_KEYS, list(snak_dict.keys())
)
)
self.snak_datatype = snak_dict["datatype"]
self.value_datatype = str(snak_dict["datavalue"]["type"])
self.datavalue = get_datavalue_from_snak_dict(snak_dict)
elif self.snaktype == "somevalue" or self.snaktype == "novalue":
self.snak_datatype = None
self.value_datatype = None
self.datavalue = None
else:
raise ValueError(
'snaktype must be one of ["value", "somevalue", "novalue"] but got {}'.format(
self.snaktype
)
)
def __str__(self) -> str:
return (
"WikidataSnak(snaktype={}, property_id={}, "
"snak_datatype={}, value_datatype={}, "
"datavalue={})".format(
self.snaktype,
self.property_id,
self.snak_datatype,
self.value_datatype,
self.datavalue,
)
)
def __repr__(self) -> str:
return self.__str__()