Skip to content

literal

Literal rdf values.

Literal (str)

A literal RDF value.

Parameters:

Name Type Description Default
value Union[datetime, bytes, bytearray, bool, int, float, str]

The literal value. See the datatypes class attribute for valid supported data types. A localised string is provided as a string with lang set to a language code.

required
lang Optional[str]

A standard language code, like "en", "no", etc. Implies that the value is a localised string.

required
datatype Any

Explicit specification of the type of value. Should not be combined with lang.

required
Source code in tripper/literal.py
class Literal(str):
    """A literal RDF value.

    Arguments:
        value (Union[datetime, bytes, bytearray, bool, int, float, str]):
            The literal value. See the `datatypes` class attribute for valid
            supported data types.  A localised string is provided as a string
            with `lang` set to a language code.
        lang (Optional[str]): A standard language code, like "en", "no", etc.
            Implies that the `value` is a localised string.
        datatype (Any): Explicit specification of the type of `value`. Should
            not be combined with `lang`.
    """

    lang: "Union[str, None]"
    datatype: "Any"

    # Note that the order of datatypes matters - it is used by
    # utils.parse_literal() when inferring the datatype of a literal.
    datatypes = {
        datetime: (XSD.dateTime,),
        bytes: (XSD.hexBinary,),
        bytearray: (XSD.hexBinary,),
        bool: (XSD.boolean,),
        int: (
            XSD.integer,
            XSD.int,
            XSD.short,
            XSD.long,
            XSD.nonNegativeInteger,
            XSD.nonPositiveInteger,
            XSD.negativeInteger,
            XSD.unsignedInt,
            XSD.unsignedShort,
            XSD.unsignedLong,
            XSD.byte,
            XSD.unsignedByte,
        ),
        float: (
            XSD.double,
            XSD.decimal,
            XSD.dateTimeStamp,
            XSD.real,
            XSD.rational,
        ),
        str: (
            XSD.string,
            RDF.HTML,
            RDF.PlainLiteral,
            RDF.XMLLiteral,
            RDFS.Literal,
            XSD.anyURI,
            XSD.language,
            XSD.Name,
            XSD.NMName,
            XSD.normalizedString,
            XSD.token,
            XSD.NMTOKEN,
        ),
    }

    def __new__(
        cls,
        value: "Union[datetime, bytes, bytearray, bool, int, float, str]",
        lang: "Optional[str]" = None,
        datatype: "Optional[Any]" = None,
    ):
        # pylint: disable=too-many-branches
        string = super().__new__(cls, value)
        string.lang = None
        string.datatype = None

        # Get lang
        if lang:
            if datatype:
                raise TypeError(
                    "A literal can only have one of `lang` or `datatype`."
                )
            string.lang = str(lang)

        # Get datatype
        elif datatype in cls.datatypes:
            string.datatype = cls.datatypes[datatype][0]
        elif datatype:
            # Create canonical representation of value for
            # given datatype
            val = None
            for typ, names in cls.datatypes.items():
                for name in names:
                    if name == datatype:
                        try:
                            val = typ(value)
                            break
                        except:  # pylint: disable=bare-except
                            pass  # nosec
                    if val:
                        break
            if val is not None:
                # Re-initialize the value anew, similarly to what is done in
                # the first line of this method.
                string = super().__new__(cls, val)
                string.lang = None

            string.datatype = datatype

        # Infer datatype from value
        elif isinstance(value, Literal):
            string.lang = value.lang
            string.datatype = value.datatype
        elif isinstance(value, str):
            string.datatype = None
        elif isinstance(value, bool):
            string.datatype = XSD.boolean
        elif isinstance(value, int):
            string.datatype = XSD.integer
        elif isinstance(value, float):
            string.datatype = XSD.double
        elif isinstance(value, (bytes, bytearray)):
            # Re-initialize the value anew, similarly to what is done in
            # the first line of this method.
            string = super().__new__(cls, value.hex())
            string.lang = None
            string.datatype = XSD.hexBinary
        elif isinstance(value, datetime):
            string.datatype = XSD.dateTime
            # TODO:
            #   - XSD.base64Binary
            #   - XSD.byte, XSD.unsignedByte

        # Some consistency checking
        if (
            string.datatype == XSD.nonPositiveInteger
            and int(value) > 0  # type: ignore[arg-type]
        ):
            raise TypeError(f"not a xsd:nonPositiveInteger: '{string}'")
        if (
            string.datatype == XSD.nonNegativeInteger
            and int(value) < 0  # type: ignore[arg-type]
        ):
            raise TypeError(f"not a xsd:nonNegativeInteger: '{string}'")
        if (
            string.datatype
            in (
                XSD.unsignedInt,
                XSD.unsignedShort,
                XSD.unsignedLong,
                XSD.unsignedByte,
            )
            and int(value) < 0  # type: ignore[arg-type]
        ):
            raise TypeError(f"not an unsigned integer: '{string}'")

        # Check if datatype is known
        if string.datatype and not any(
            string.datatype in types for types in cls.datatypes.values()
        ):
            warnings.warn(
                f"unknown datatype: {string.datatype} - assuming xsd:string"
            )

        return string

    def __hash__(self):
        return hash((str(self), self.lang, self.datatype))

    def __eq__(self, other):
        if not isinstance(other, Literal):
            if isinstance(other, str) and self.lang:
                return str(self) == other
            other = Literal(other)
        return (
            str(self) == str(other)
            and self.lang == other.lang
            and self.datatype == other.datatype
        )

    def __ne__(self, other):
        return not self.__eq__(other)

    def __repr__(self) -> str:
        lang = f", lang='{self.lang}'" if self.lang else ""
        datatype = f", datatype='{self.datatype}'" if self.datatype else ""
        return f"Literal('{self}'{lang}{datatype})"

    value = property(
        fget=lambda self: self.to_python(),
        doc="Appropriate python datatype derived from this RDF literal.",
    )

    def to_python(self):
        """Returns an appropriate python datatype derived from this RDF
        literal."""
        value = str(self)

        if self.datatype == XSD.boolean:
            value = False if str(self) == "False" else bool(self)
        elif self.datatype in self.datatypes[int]:
            value = int(self)
        elif self.datatype in self.datatypes[float]:
            value = float(self)
        elif self.datatype == XSD.dateTime:
            value = datetime.fromisoformat(self)

        return value

    def n3(self) -> str:  # pylint: disable=invalid-name
        """Returns a representation in n3 format."""
        if self.lang:
            return f'"{self}"@{self.lang}'
        if self.datatype:
            return f'"{self}"^^<{self.datatype}>'
        return f'"{self}"'

value property readonly

Appropriate python datatype derived from this RDF literal.

__new__(cls, value, lang=None, datatype=None) special staticmethod

Create and return a new object. See help(type) for accurate signature.

Source code in tripper/literal.py
def __new__(
    cls,
    value: "Union[datetime, bytes, bytearray, bool, int, float, str]",
    lang: "Optional[str]" = None,
    datatype: "Optional[Any]" = None,
):
    # pylint: disable=too-many-branches
    string = super().__new__(cls, value)
    string.lang = None
    string.datatype = None

    # Get lang
    if lang:
        if datatype:
            raise TypeError(
                "A literal can only have one of `lang` or `datatype`."
            )
        string.lang = str(lang)

    # Get datatype
    elif datatype in cls.datatypes:
        string.datatype = cls.datatypes[datatype][0]
    elif datatype:
        # Create canonical representation of value for
        # given datatype
        val = None
        for typ, names in cls.datatypes.items():
            for name in names:
                if name == datatype:
                    try:
                        val = typ(value)
                        break
                    except:  # pylint: disable=bare-except
                        pass  # nosec
                if val:
                    break
        if val is not None:
            # Re-initialize the value anew, similarly to what is done in
            # the first line of this method.
            string = super().__new__(cls, val)
            string.lang = None

        string.datatype = datatype

    # Infer datatype from value
    elif isinstance(value, Literal):
        string.lang = value.lang
        string.datatype = value.datatype
    elif isinstance(value, str):
        string.datatype = None
    elif isinstance(value, bool):
        string.datatype = XSD.boolean
    elif isinstance(value, int):
        string.datatype = XSD.integer
    elif isinstance(value, float):
        string.datatype = XSD.double
    elif isinstance(value, (bytes, bytearray)):
        # Re-initialize the value anew, similarly to what is done in
        # the first line of this method.
        string = super().__new__(cls, value.hex())
        string.lang = None
        string.datatype = XSD.hexBinary
    elif isinstance(value, datetime):
        string.datatype = XSD.dateTime
        # TODO:
        #   - XSD.base64Binary
        #   - XSD.byte, XSD.unsignedByte

    # Some consistency checking
    if (
        string.datatype == XSD.nonPositiveInteger
        and int(value) > 0  # type: ignore[arg-type]
    ):
        raise TypeError(f"not a xsd:nonPositiveInteger: '{string}'")
    if (
        string.datatype == XSD.nonNegativeInteger
        and int(value) < 0  # type: ignore[arg-type]
    ):
        raise TypeError(f"not a xsd:nonNegativeInteger: '{string}'")
    if (
        string.datatype
        in (
            XSD.unsignedInt,
            XSD.unsignedShort,
            XSD.unsignedLong,
            XSD.unsignedByte,
        )
        and int(value) < 0  # type: ignore[arg-type]
    ):
        raise TypeError(f"not an unsigned integer: '{string}'")

    # Check if datatype is known
    if string.datatype and not any(
        string.datatype in types for types in cls.datatypes.values()
    ):
        warnings.warn(
            f"unknown datatype: {string.datatype} - assuming xsd:string"
        )

    return string

n3(self)

Returns a representation in n3 format.

Source code in tripper/literal.py
def n3(self) -> str:  # pylint: disable=invalid-name
    """Returns a representation in n3 format."""
    if self.lang:
        return f'"{self}"@{self.lang}'
    if self.datatype:
        return f'"{self}"^^<{self.datatype}>'
    return f'"{self}"'

to_python(self)

Returns an appropriate python datatype derived from this RDF literal.

Source code in tripper/literal.py
def to_python(self):
    """Returns an appropriate python datatype derived from this RDF
    literal."""
    value = str(self)

    if self.datatype == XSD.boolean:
        value = False if str(self) == "False" else bool(self)
    elif self.datatype in self.datatypes[int]:
        value = int(self)
    elif self.datatype in self.datatypes[float]:
        value = float(self)
    elif self.datatype == XSD.dateTime:
        value = datetime.fromisoformat(self)

    return value