Skip to content

More precise typing of Literals #3108

Open
@multimeric

Description

@multimeric

I want to be able to write functions with precise RDF typing, that e.g. accept a number literal but not other types of Literal. Currently Literal is just a single class that isn't generic, so there's no way to enforce this.

One way I could see this working is by making a subclass corresponding to each XSD type:

class IntegerLiteral(Literal):
    datatype = XSD.integer
    def __init__(self, value: int): ...

And then you could it work with the base Literal class using something like:

from typing import overload, Literal as TypingLiteral

class Literal(Identifier):
    @overload
    def __new__(
        cls,
        lexical_or_value: int,
        datatype: TypingLiteral[XSD.integer] = XSD.integer,
    ) -> IntegerLiteral:
        ...
    @overload
    def __new__(
        cls,
        lexical_or_value: bool,
        datatype: TypingLiteral[XSD.boolean] = XSD.boolean,
    ) -> BooleanLiteral:
        ...
    def __new__(
        cls,
        lexical_or_value: Any,
        lang: str | None = None,
        datatype: str | None = None,
        normalize: bool | None = None,
    ) -> Literal:

Alternatively, you could make Literal generic:

from typing import overload, Literal as TypingLiteral

class Literal[T](Identifier):
    @overload
    def __new__(
        cls,
        lexical_or_value: int,
        datatype: TypingLiteral[XSD.integer] = XSD.integer,
    ) -> Literal[int]:
        ...
    @overload
    def __new__(
        cls,
        lexical_or_value: bool,
        datatype: TypingLiteral[XSD.boolean] = XSD.boolean,
    ) -> Literal[bool]:
        ...
    def __new__(
        cls,
        lexical_or_value: Any,
        lang: str | None = None,
        datatype: str | None = None,
        normalize: bool | None = None,
    ) -> Literal:

Then my downstream code could do something like:

def some_function(
    arg: IntegerLiteral | URIRef
):
    ...

Activity

ashleysommer

ashleysommer commented on Apr 2, 2025

@ashleysommer
Contributor

@multimeric This is one of those things that seems logical and easy to implement on the surface, but is flawed in several ways that makes it very difficult or impossible to actually implement.
The main reason is that typings for RDF Literals does not map well to the Python typing system.

Eg, what happens if you parse a lexical that looks like this:

"true"^^xsd:integer

This is a valid RDF Literal, RDFLib will honor its explicit parsed datatype, but its value will be a String "true" and it is marked as ill-typed internally. Would the generic type of this be a Python bool, or a python Integer, or a python string?

Or similarly, you do this:

l = Literal(1.23, datatype=XSD.integer)

Again, this is an ill-typed Literal, but its still a valid RDF literal. Would the generic type of this be a Python float or a python Integer?

Even without ill-typing:

l = Literal(1.34, datatype=XSD:float)

All floating-point numbers in Python are "double-precision", its not possible to pass in a value to the constructor that matches the specified datatype.

We could implement some simple fool-proof cases, but it would probably never work how you want it to, and not all RDF typing issues would be caught by static type checkers.

multimeric

multimeric commented on Apr 2, 2025

@multimeric
Author

I think this is fine. My proposed implementation only affects the type system and not at runtime, so if you do Literal(1.23, datatype=XSD.integer) it will create a float literal with datatype integer as it does now. The only difference is that the type checker will complain. This is exactly what I want because it doesn't break anything but it does allow better static analysis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @ashleysommer@multimeric

        Issue actions

          More precise typing of `Literals` · Issue #3108 · RDFLib/rdflib