from typing import Any, Optional, Union
from wikibaseintegrator.datatypes.basedatatype import BaseDataType
from wikibaseintegrator.wbi_config import config
from wikibaseintegrator.wbi_helpers import format_amount
[docs]
class Quantity(BaseDataType):
"""
Implements the Wikibase data type for quantities
"""
DTYPE = 'quantity'
sparql_query = '''
SELECT * WHERE {{
?item_id <{wb_url}/prop/{pid}> ?s .
?s <{wb_url}/prop/statement/{pid}> '{value}'^^xsd:decimal .
}}
'''
[docs]
def __init__(self, amount: Optional[Union[str, int, float]] = None, upper_bound: Optional[Union[str, int, float]] = None, lower_bound: Optional[Union[str, int, float]] = None, unit: Union[str, int] = '1',
wikibase_url: Optional[str] = None, **kwargs: Any):
"""
Constructor, calls the superclass BaseDataType
:param amount: The amount value
:param upper_bound: Upper bound of the value if it exists, e.g. for standard deviations
:param lower_bound: Lower bound of the value if it exists, e.g. for standard deviations
:param unit: The unit item URL or the QID a certain amount has been measured in (https://www.wikidata.org/wiki/Wikidata:Units).
The default is dimensionless, represented by a '1'
:param wikibase_url: The default wikibase URL, used when the unit is only an ID like 'Q2'. Use wbi_config['WIKIBASE_URL'] by default.
"""
super().__init__(**kwargs)
self.set_value(amount=amount, upper_bound=upper_bound, lower_bound=lower_bound, unit=unit, wikibase_url=wikibase_url)
[docs]
def set_value(self, amount: Optional[Union[str, int, float]] = None, upper_bound: Optional[Union[str, int, float]] = None, lower_bound: Optional[Union[str, int, float]] = None, unit: Union[str, int] = '1',
wikibase_url: Optional[str] = None):
wikibase_url = wikibase_url or str(config['WIKIBASE_URL'])
unit = str(unit or '1')
if unit.startswith('Q'):
unit = wikibase_url + '/entity/' + unit
if amount is not None:
amount = format_amount(amount)
unit = str(unit)
if upper_bound:
upper_bound = format_amount(upper_bound)
if lower_bound:
lower_bound = format_amount(lower_bound)
# Integrity checks for value and bounds
try:
for i in [amount, upper_bound, lower_bound]:
if i:
float(i)
except ValueError as error:
raise ValueError("Value, bounds and units must parse as integers or float") from error
if (lower_bound and upper_bound) and (float(lower_bound) > float(upper_bound) or float(lower_bound) > float(amount)):
raise ValueError("Lower bound too large")
if upper_bound and float(upper_bound) < float(amount):
raise ValueError("Upper bound too small")
self.mainsnak.datavalue = {
'value': {
'amount': amount,
'unit': unit,
'upperBound': upper_bound,
'lowerBound': lower_bound
},
'type': 'quantity'
}
# remove bounds from json if they are undefined
if not upper_bound:
del self.mainsnak.datavalue['value']['upperBound']
if not lower_bound:
del self.mainsnak.datavalue['value']['lowerBound']
[docs]
def get_sparql_value(self) -> str:
return '"' + format_amount(self.mainsnak.datavalue['value']['amount']) + '"^^xsd:decimal'
[docs]
def parse_sparql_value(self, value, type='literal', unit='1') -> bool:
self.set_value(amount=value, unit=unit)
return True