Skip to content

pyOnlineSpreadSheetEditing API Documentation

editconfig

Created on 2021-12-31

@author: wf

EditConfig

Edit and Query Configuration.

Attributes:

Name Type Description
name str

The name of the configuration.

sourceWikiId Optional[str]

The source Wiki ID.

targetWikiId Optional[str]

The target Wiki ID.

queries Dict[str, str]

The queries dictionary.

format Optional[str]

The format of the output.

Source code in onlinespreadsheet/editconfig.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@lod_storable
class EditConfig:
    """
    Edit and Query Configuration.

    Attributes:
        name (str): The name of the configuration.
        sourceWikiId (Optional[str]): The source Wiki ID.
        targetWikiId (Optional[str]): The target Wiki ID.
        queries (Dict[str, str]): The queries dictionary.
        format (Optional[str]): The format of the output.
    """

    name: str
    sourceWikiId: Optional[str] = None
    targetWikiId: Optional[str] = None
    queries: Dict[str, str] = field(default_factory=dict)
    format: Optional[str] = None

    def addQuery(self, name: str, query: str):
        self.queries[name] = query
        return self

    def toTableQuery(self) -> TableQuery:
        """
        convert me to a TableQuery

        Returns:
            TableQuery: the table query for my queries
        """
        tq = TableQuery()
        for name, query in self.queries.items():
            queryType = TableQuery.guessQueryType(query)
            if queryType is QueryType.INVALID:
                raise Exception(f"unknown / invalid query Type for query {name}")
            elif queryType is QueryType.ASK:
                tq.addAskQuery(self.sourceWikiId, name, query)
            elif queryType is QueryType.RESTful:
                tq.addRESTfulQuery(name=name, url=query)
            elif queryType is queryType.SPARQL:
                tq.addSparqlQuery(name=name, query=query)
            else:
                raise Exception(f"unimplemented query type {queryType}")
        return tq

toTableQuery()

convert me to a TableQuery

Returns:

Name Type Description
TableQuery TableQuery

the table query for my queries

Source code in onlinespreadsheet/editconfig.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def toTableQuery(self) -> TableQuery:
    """
    convert me to a TableQuery

    Returns:
        TableQuery: the table query for my queries
    """
    tq = TableQuery()
    for name, query in self.queries.items():
        queryType = TableQuery.guessQueryType(query)
        if queryType is QueryType.INVALID:
            raise Exception(f"unknown / invalid query Type for query {name}")
        elif queryType is QueryType.ASK:
            tq.addAskQuery(self.sourceWikiId, name, query)
        elif queryType is QueryType.RESTful:
            tq.addRESTfulQuery(name=name, url=query)
        elif queryType is queryType.SPARQL:
            tq.addSparqlQuery(name=name, query=query)
        else:
            raise Exception(f"unimplemented query type {queryType}")
    return tq

EditConfigs

manager for edit configurations

Source code in onlinespreadsheet/editconfig.py
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
@lod_storable
class EditConfigs:
    """
    manager for edit configurations
    """

    editConfigs: Dict[str, EditConfig] = field(default_factory=dict)

    @classmethod
    def get_yaml_path(cls):
        home = str(Path.home())
        path = f"{home}/.ose"
        yamlFileName = "editConfigs.yaml"
        yaml_path = f"{path}/{yamlFileName}"
        return yaml_path

    def add(self, editConfig):
        """
        add a editConfiguration
        """
        self.editConfigs[editConfig.name] = editConfig

    def save(self, yaml_path: str = None):
        if yaml_path is None:
            yaml_path = EditConfigs.get_yaml_path()
        os.makedirs(os.path.dirname(yaml_path), exist_ok=True)
        self.save_to_yaml_file(yaml_path)

    @classmethod
    def load(cls, yaml_path: str = None):
        if yaml_path is None:
            yaml_path = EditConfigs.get_yaml_path()
        edit_configs = cls.load_from_yaml_file(yaml_path)
        return edit_configs

add(editConfig)

add a editConfiguration

Source code in onlinespreadsheet/editconfig.py
79
80
81
82
83
def add(self, editConfig):
    """
    add a editConfiguration
    """
    self.editConfigs[editConfig.name] = editConfig

gsimport

Created on 2024-03-18

@author: wf

GsImportSolution

Bases: InputWebSolution

the google spreadsheet import solution

Source code in onlinespreadsheet/gsimport.py
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
class GsImportSolution(InputWebSolution):
    """
    the google spreadsheet import solution
    """

    def __init__(self, webserver: GsImportWebserver, client: Client):
        """
        Initialize the solution

        Calls the constructor of the base solution
        Args:
            webserver (GsImportWebserver): The webserver instance associated with this context.
            client (Client): The client instance this context is associated with.
        """
        super().__init__(webserver, client)  # Call to the superclass constructor
        # self.wdgrid: WikidataGrid = None
        # self.gridSync: GridSync = None

    def show_ui(self):
        """
        show my user interface
        """
        self.ssv = SpreadSheetView(self)

    async def home(
        self,
    ):
        """Generates the home page with a selection of examples and
        svg display
        """
        await self.setup_content_div(self.show_ui)

__init__(webserver, client)

Initialize the solution

Calls the constructor of the base solution Args: webserver (GsImportWebserver): The webserver instance associated with this context. client (Client): The client instance this context is associated with.

Source code in onlinespreadsheet/gsimport.py
50
51
52
53
54
55
56
57
58
59
def __init__(self, webserver: GsImportWebserver, client: Client):
    """
    Initialize the solution

    Calls the constructor of the base solution
    Args:
        webserver (GsImportWebserver): The webserver instance associated with this context.
        client (Client): The client instance this context is associated with.
    """
    super().__init__(webserver, client)  # Call to the superclass constructor

home() async

Generates the home page with a selection of examples and svg display

Source code in onlinespreadsheet/gsimport.py
69
70
71
72
73
74
75
async def home(
    self,
):
    """Generates the home page with a selection of examples and
    svg display
    """
    await self.setup_content_div(self.show_ui)

show_ui()

show my user interface

Source code in onlinespreadsheet/gsimport.py
63
64
65
66
67
def show_ui(self):
    """
    show my user interface
    """
    self.ssv = SpreadSheetView(self)

GsImportWebserver

Bases: InputWebserver

Google Spreadsheet Import and Wikidata Sync

Source code in onlinespreadsheet/gsimport.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class GsImportWebserver(InputWebserver):
    """
    Google Spreadsheet Import and Wikidata Sync
    """

    @classmethod
    def get_config(cls) -> WebserverConfig:
        copy_right = "(c)2022-2024 Wolfgang Fahl"
        config = WebserverConfig(
            copy_right=copy_right,
            version=Version(),
            default_port=9854,
            short_name="gsimport",
        )
        server_config = WebserverConfig.get(config)
        server_config.solution_class = GsImportSolution
        return server_config

    def __init__(self):
        """Constructs all the necessary attributes for the WebServer object."""
        config = GsImportWebserver.get_config()
        print("initializing Property Manager")
        self.wpm = WikidataPropertyManager.get_instance()
        print("Properties prepared ...")
        InputWebserver.__init__(self, config=config)

__init__()

Constructs all the necessary attributes for the WebServer object.

Source code in onlinespreadsheet/gsimport.py
36
37
38
39
40
41
42
def __init__(self):
    """Constructs all the necessary attributes for the WebServer object."""
    config = GsImportWebserver.get_config()
    print("initializing Property Manager")
    self.wpm = WikidataPropertyManager.get_instance()
    print("Properties prepared ...")
    InputWebserver.__init__(self, config=config)

gsimport_cmd

Created on 2024-03-18

@author: wf

GoogleSheetWikidataCmd

Bases: WebserverCmd

Command line tool for managing Google Sheets to Wikidata imports via a web server.

Source code in onlinespreadsheet/gsimport_cmd.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
class GoogleSheetWikidataCmd(WebserverCmd):
    """
    Command line tool for managing Google Sheets to Wikidata imports via a web server.
    """

    def getArgParser(self, description: str, version_msg: str) -> ArgumentParser:
        """
        Extend the default argument parser with Google Sheets to Wikidata specific arguments.
        """
        parser = super().getArgParser(description, version_msg)
        parser.add_argument(
            "-v",
            "--verbose",
            action="store_true",
            help="Show verbose output [default: %(default)s]",
        )
        parser.add_argument(
            "--url",
            required=True,
            help="URL of the Google Spreadsheet to import from",
        )
        parser.add_argument(
            "--sheets",
            nargs="+",
            required=True,
            help="Names of the sheets to import data from",
        )
        parser.add_argument(
            "--mappingSheet",
            default="WikidataMapping",
            help="Name of the sheet containing Wikidata mappings [default: %(default)s]",
        )
        parser.add_argument(
            "--pk",
            required=True,
            help="Primary key property to use for Wikidata queries",
        )
        parser.add_argument(
            "--endpoint",
            default="https://query.wikidata.org/sparql",
            help="SPARQL endpoint URL [default: %(default)s]",
        )
        parser.add_argument(
            "--lang",
            "--language",
            default="en",
            help="Language to use for labels [default: %(default)s]",
        )
        return parser

getArgParser(description, version_msg)

Extend the default argument parser with Google Sheets to Wikidata specific arguments.

Source code in onlinespreadsheet/gsimport_cmd.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
def getArgParser(self, description: str, version_msg: str) -> ArgumentParser:
    """
    Extend the default argument parser with Google Sheets to Wikidata specific arguments.
    """
    parser = super().getArgParser(description, version_msg)
    parser.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Show verbose output [default: %(default)s]",
    )
    parser.add_argument(
        "--url",
        required=True,
        help="URL of the Google Spreadsheet to import from",
    )
    parser.add_argument(
        "--sheets",
        nargs="+",
        required=True,
        help="Names of the sheets to import data from",
    )
    parser.add_argument(
        "--mappingSheet",
        default="WikidataMapping",
        help="Name of the sheet containing Wikidata mappings [default: %(default)s]",
    )
    parser.add_argument(
        "--pk",
        required=True,
        help="Primary key property to use for Wikidata queries",
    )
    parser.add_argument(
        "--endpoint",
        default="https://query.wikidata.org/sparql",
        help="SPARQL endpoint URL [default: %(default)s]",
    )
    parser.add_argument(
        "--lang",
        "--language",
        default="en",
        help="Language to use for labels [default: %(default)s]",
    )
    return parser

main(argv=None)

Main entry point for the command-line tool.

Source code in onlinespreadsheet/gsimport_cmd.py
66
67
68
69
70
71
72
73
74
75
def main(argv: list = None):
    """
    Main entry point for the command-line tool.
    """
    cmd = GoogleSheetWikidataCmd(
        config=GsImportWebserver.get_config(),
        webserver_cls=GsImportWebserver,
    )
    exit_code = cmd.cmd_main(argv)
    return exit_code

profile

Profile

Bases: JSONAble

Generates a profile page from a given wikidata id

Source code in onlinespreadsheet/profile.py
 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
class Profile(JSONAble):
    """
    Generates a profile page from a given wikidata id
    """

    def __init__(self, wikidataid: str):
        super().__init__()
        self.wikidataid = wikidataid
        self.fromDict(self.getUserInformation()[0])
        self.ids = self.getIdentifiers()

    def getSamples(self):
        samples = [
            {
                "wikidataid": "Q1910001",
                "firstname": "Matthias",
                "lastname": "Jarke",
                "image": "",
                "dateOfBirth": "1952-05-28",
                "homepage": "",
                "ids": {
                    "GND ID": {
                        "id": "121078221",
                        "url": "https://d-nb.info/gnd/121078221",
                    },
                    "DBLP author ID": {
                        "id": "j/MatthiasJarke",
                        "url": "https://dblp.org/pid/j/MatthiasJarke",
                    },
                    "ORCID iD": {
                        "id": "0000-0001-6169-2942",
                        "url": "https://orcid.org/0000-0001-6169-2942",
                    },
                },
            }
        ]

    @property
    def sparqlEndpoint(self) -> SPARQL:
        return SPARQL("https://query.wikidata.org/sparql")

    def getUserInformation(self) -> dict:
        """
        Retrieves basic information about the user from wikidata that is assigned to the id
        """
        sparql = self.sparqlEndpoint
        query = self.getUserInformationQuery()
        qres = sparql.queryAsListOfDicts(query.query)
        return qres

    def getIdentifiers(self) -> dict:
        """
        Retrieves all identifiers of the user that are assigned to the id

        Returns:
            dict of dict containing the id, idUrl, type of id
        """
        sparql = self.sparqlEndpoint
        query = self.getUserIdentifierQuery()
        qres = sparql.queryAsListOfDicts(query.query)
        ids = {}
        for record in qres:
            ids[record.get("propertyLabel")] = {
                "id": record.get("id"),
                "url": record.get("idUrl"),
            }
        return ids

    def getUserInformationQuery(self) -> Query:
        """
        Returns the query for basic user information
        """
        queryStr = (
            """
        SELECT ?firstname ?lastname ?image ?dateOfBirth ?homepage
        WHERE{
          VALUES ?user {wd:%s}

          ?user wdt:P31 wd:Q5.

          OPTIONAL{ ?user wdt:P735 ?f.
                    ?f rdfs:label ?firstname. 
                   FILTER(lang(?firstname)="en")
          }
          OPTIONAL{ ?user wdt:P734 ?l.
                    ?l rdfs:label ?lastname. 
                   FILTER(lang(?lastname)="en")
          }
          OPTIONAL{ ?user wdt:P18 ?image.}
          OPTIONAL{ ?user wdt:P569 ?dateOfBirth.}
          OPTIONAL{ ?user wdt:P856 ?homepage.}
        }
        """
            % self.wikidataid
        )
        query = Query(
            name="BasicUserInformation",
            title="Basic wikidata user information",
            lang="sparql",
            query=queryStr,
        )
        return query

    def getUserIdentifierQuery(self) -> Query:
        """
        Returns the query for the Identifiers of given human
        """
        queryStr = (
            r"""
        SELECT ?property ?propertyLabel ?id ?idUrl
        WHERE{
          VALUES ?user {wd:%s}
          ?user wdt:P31 wd:Q5.
          ?property wikibase:propertyType wikibase:ExternalId .    
          ?property wikibase:directClaim ?propertyclaim .                                              
          OPTIONAL {?property wdt:P1630 ?formatterURL .}   
          ?user ?propertyclaim ?id .    
          BIND(IF(BOUND(?formatterURL), IRI(REPLACE(?formatterURL, "\\$1", ?id)) , "") AS ?idUrl) 
          SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } 
        }"""
            % self.wikidataid
        )
        query = Query(
            name="UserIdentifiers",
            title="The Identifiers of given human",
            lang="sparql",
            query=queryStr,
        )
        return query

    def getIdIcon(self, idType):
        """
        Returns a url to the icon of the given id
        """
        iconMap = {
            "GND ID": "https://gnd.network/SiteGlobals/Frontend/gnd/Images/faviconGND.png?__blob=normal&v=5",
            "ORCID iD": "https://orcid.org/assets/vectors/orcid.logo.icon.svg",
            "DBLP author ID": "https://dblp.org/img/favicon.ico",
            "Twitter username": "https://upload.wikimedia.org/wikipedia/commons/4/4f/Twitter-logo.svg",
            "GitHub username": "https://github.githubassets.com/favicons/favicon.svg",
            "ISNI": "https://isni.oclc.org:2443/isni/psi_images/img_psi/3.0/logos/logo_xml_isni.png",
            "Google Scholar author ID": "google-scholar",
            "ACM Digital Library author ID": "acm",
            "Scopus author ID": "scopus",
            "WorldCat Identities ID": "https://upload.wikimedia.org/wikipedia/commons/a/a8/WorldCat_logo.svg",
            "LibraryThing author ID": "https://image.librarything.com/pics/LT-logo-square-75-bw-w2.png",
            "Dimensions author ID": "https://cdn-app.dimensions.ai/static/d8b0339df3b57265d674.png",
            "GEPRIS person ID": "https://gepris.dfg.de/gepris/images/GEPRIS_Logo.png",
            "Wikimedia username": "https://upload.wikimedia.org/wikipedia/commons/8/81/Wikimedia-logo.svg",
            "Mendeley person ID": "mendeley",
            "ResearchGate profile ID": "researchgate",
            "Publons author ID": "https://publons.com/static/images/logos/square/blue_white_shadow.png",
            "OpenReview.net profile ID": "https://openreview.net/images/openreview_logo_512.png",
            "Library of Congress authority ID": "https://loc.gov/static/images/logo-loc-new-branding.svg",
        }
        iconId = iconMap.get(idType)
        if iconId is not None:
            if iconId.startswith("http"):
                return f'<img src="{ iconId }"   alt="" style="max-width:50px;">'
            else:
                return f'<i class="ai ai-{ iconId } ai-3x"></i>'
        return ""

getIdIcon(idType)

Returns a url to the icon of the given id

Source code in onlinespreadsheet/profile.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def getIdIcon(self, idType):
    """
    Returns a url to the icon of the given id
    """
    iconMap = {
        "GND ID": "https://gnd.network/SiteGlobals/Frontend/gnd/Images/faviconGND.png?__blob=normal&v=5",
        "ORCID iD": "https://orcid.org/assets/vectors/orcid.logo.icon.svg",
        "DBLP author ID": "https://dblp.org/img/favicon.ico",
        "Twitter username": "https://upload.wikimedia.org/wikipedia/commons/4/4f/Twitter-logo.svg",
        "GitHub username": "https://github.githubassets.com/favicons/favicon.svg",
        "ISNI": "https://isni.oclc.org:2443/isni/psi_images/img_psi/3.0/logos/logo_xml_isni.png",
        "Google Scholar author ID": "google-scholar",
        "ACM Digital Library author ID": "acm",
        "Scopus author ID": "scopus",
        "WorldCat Identities ID": "https://upload.wikimedia.org/wikipedia/commons/a/a8/WorldCat_logo.svg",
        "LibraryThing author ID": "https://image.librarything.com/pics/LT-logo-square-75-bw-w2.png",
        "Dimensions author ID": "https://cdn-app.dimensions.ai/static/d8b0339df3b57265d674.png",
        "GEPRIS person ID": "https://gepris.dfg.de/gepris/images/GEPRIS_Logo.png",
        "Wikimedia username": "https://upload.wikimedia.org/wikipedia/commons/8/81/Wikimedia-logo.svg",
        "Mendeley person ID": "mendeley",
        "ResearchGate profile ID": "researchgate",
        "Publons author ID": "https://publons.com/static/images/logos/square/blue_white_shadow.png",
        "OpenReview.net profile ID": "https://openreview.net/images/openreview_logo_512.png",
        "Library of Congress authority ID": "https://loc.gov/static/images/logo-loc-new-branding.svg",
    }
    iconId = iconMap.get(idType)
    if iconId is not None:
        if iconId.startswith("http"):
            return f'<img src="{ iconId }"   alt="" style="max-width:50px;">'
        else:
            return f'<i class="ai ai-{ iconId } ai-3x"></i>'
    return ""

getIdentifiers()

Retrieves all identifiers of the user that are assigned to the id

Returns:

Type Description
dict

dict of dict containing the id, idUrl, type of id

Source code in onlinespreadsheet/profile.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def getIdentifiers(self) -> dict:
    """
    Retrieves all identifiers of the user that are assigned to the id

    Returns:
        dict of dict containing the id, idUrl, type of id
    """
    sparql = self.sparqlEndpoint
    query = self.getUserIdentifierQuery()
    qres = sparql.queryAsListOfDicts(query.query)
    ids = {}
    for record in qres:
        ids[record.get("propertyLabel")] = {
            "id": record.get("id"),
            "url": record.get("idUrl"),
        }
    return ids

getUserIdentifierQuery()

Returns the query for the Identifiers of given human

Source code in onlinespreadsheet/profile.py
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def getUserIdentifierQuery(self) -> Query:
    """
    Returns the query for the Identifiers of given human
    """
    queryStr = (
        r"""
    SELECT ?property ?propertyLabel ?id ?idUrl
    WHERE{
      VALUES ?user {wd:%s}
      ?user wdt:P31 wd:Q5.
      ?property wikibase:propertyType wikibase:ExternalId .    
      ?property wikibase:directClaim ?propertyclaim .                                              
      OPTIONAL {?property wdt:P1630 ?formatterURL .}   
      ?user ?propertyclaim ?id .    
      BIND(IF(BOUND(?formatterURL), IRI(REPLACE(?formatterURL, "\\$1", ?id)) , "") AS ?idUrl) 
      SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } 
    }"""
        % self.wikidataid
    )
    query = Query(
        name="UserIdentifiers",
        title="The Identifiers of given human",
        lang="sparql",
        query=queryStr,
    )
    return query

getUserInformation()

Retrieves basic information about the user from wikidata that is assigned to the id

Source code in onlinespreadsheet/profile.py
92
93
94
95
96
97
98
99
def getUserInformation(self) -> dict:
    """
    Retrieves basic information about the user from wikidata that is assigned to the id
    """
    sparql = self.sparqlEndpoint
    query = self.getUserInformationQuery()
    qres = sparql.queryAsListOfDicts(query.query)
    return qres

getUserInformationQuery()

Returns the query for basic user information

Source code in onlinespreadsheet/profile.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def getUserInformationQuery(self) -> Query:
    """
    Returns the query for basic user information
    """
    queryStr = (
        """
    SELECT ?firstname ?lastname ?image ?dateOfBirth ?homepage
    WHERE{
      VALUES ?user {wd:%s}

      ?user wdt:P31 wd:Q5.

      OPTIONAL{ ?user wdt:P735 ?f.
                ?f rdfs:label ?firstname. 
               FILTER(lang(?firstname)="en")
      }
      OPTIONAL{ ?user wdt:P734 ?l.
                ?l rdfs:label ?lastname. 
               FILTER(lang(?lastname)="en")
      }
      OPTIONAL{ ?user wdt:P18 ?image.}
      OPTIONAL{ ?user wdt:P569 ?dateOfBirth.}
      OPTIONAL{ ?user wdt:P856 ?homepage.}
    }
    """
        % self.wikidataid
    )
    query = Query(
        name="BasicUserInformation",
        title="Basic wikidata user information",
        lang="sparql",
        query=queryStr,
    )
    return query

ProfileBlueprint

Bases: object

Flask Blueprint providing routes to the profile pages

Source code in onlinespreadsheet/profile.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class ProfileBlueprint(object):
    """
    Flask Blueprint providing routes to the profile pages
    """

    def __init__(self, app, name: str, template_folder: str = None, appWrap=None):
        """
        construct me

        Args:
            name(str): my name
            welcome(str): the welcome page
            template_folder(str): the template folder
        """
        self.name = name
        if template_folder is not None:
            self.template_folder = template_folder
        else:
            self.template_folder = "profile"
        self.blueprint = Blueprint(
            name, __name__, template_folder=self.template_folder, url_prefix="/profile"
        )
        self.app = app
        self.appWrap = appWrap

        @self.blueprint.route("/<wikidataid>")
        def profile(wikidataid: str):
            return self.profile(wikidataid)

        app.register_blueprint(self.blueprint)

    def profile(self, wikidataid: str):
        title = "Profile"
        template = os.path.join(self.template_folder, "profile.html")
        activeItem = ""
        profile = Profile(wikidataid)
        html = self.appWrap.render_template(
            template, title=title, activeItem=activeItem, profile=profile
        )
        return html

__init__(app, name, template_folder=None, appWrap=None)

construct me

Parameters:

Name Type Description Default
name(str)

my name

required
welcome(str)

the welcome page

required
template_folder(str)

the template folder

required
Source code in onlinespreadsheet/profile.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def __init__(self, app, name: str, template_folder: str = None, appWrap=None):
    """
    construct me

    Args:
        name(str): my name
        welcome(str): the welcome page
        template_folder(str): the template folder
    """
    self.name = name
    if template_folder is not None:
        self.template_folder = template_folder
    else:
        self.template_folder = "profile"
    self.blueprint = Blueprint(
        name, __name__, template_folder=self.template_folder, url_prefix="/profile"
    )
    self.app = app
    self.appWrap = appWrap

    @self.blueprint.route("/<wikidataid>")
    def profile(wikidataid: str):
        return self.profile(wikidataid)

    app.register_blueprint(self.blueprint)

record_sync

Created on 2024-03-18

separating ui and functional concerns @author: wf

ComparisonData dataclass

Stores the property name and the values to compare

Source code in onlinespreadsheet/record_sync.py
37
38
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
@dataclass
class ComparisonData:
    """
    Stores the property name and the values to compare
    """

    property_name: str
    left_value: typing.Any
    right_value: typing.Any
    chosen_sync_option: SyncAction = None

    def get_sync_status(self):
        """
        compare the left and right value and return their sync status
        """
        status = None
        if str(self.left_value) == str(self.right_value):
            status = SyncStatus.IN_SYNC
        elif self.left_value is None or self.right_value is None:
            status = SyncStatus.SYNC_POSSIBLE
        else:
            status = SyncStatus.OUT_SYNC
        return status

    def get_chosen_sync_option(self) -> SyncAction:
        """
        chosen sync action to apply to the compared property values
        Returns:
            SyncAction: section to apply
        """
        if self.chosen_sync_option is not None:
            action = self.chosen_sync_option
        else:
            action = self.suggested_sync_action()
        return action

    def suggested_sync_action(self) -> SyncAction:
        """
        Evaluates the data difference and suggests a Sync Action
        """
        status = self.get_sync_status()
        if status is SyncStatus.SYNC_POSSIBLE:
            if self.left_value is None:
                action = SyncAction.LEFT_SYNC
            else:
                action = SyncAction.RIGHT_SYNC
        else:
            action = SyncAction.NOTHING
        return action

get_chosen_sync_option()

chosen sync action to apply to the compared property values Returns: SyncAction: section to apply

Source code in onlinespreadsheet/record_sync.py
61
62
63
64
65
66
67
68
69
70
71
def get_chosen_sync_option(self) -> SyncAction:
    """
    chosen sync action to apply to the compared property values
    Returns:
        SyncAction: section to apply
    """
    if self.chosen_sync_option is not None:
        action = self.chosen_sync_option
    else:
        action = self.suggested_sync_action()
    return action

get_sync_status()

compare the left and right value and return their sync status

Source code in onlinespreadsheet/record_sync.py
48
49
50
51
52
53
54
55
56
57
58
59
def get_sync_status(self):
    """
    compare the left and right value and return their sync status
    """
    status = None
    if str(self.left_value) == str(self.right_value):
        status = SyncStatus.IN_SYNC
    elif self.left_value is None or self.right_value is None:
        status = SyncStatus.SYNC_POSSIBLE
    else:
        status = SyncStatus.OUT_SYNC
    return status

suggested_sync_action()

Evaluates the data difference and suggests a Sync Action

Source code in onlinespreadsheet/record_sync.py
73
74
75
76
77
78
79
80
81
82
83
84
85
def suggested_sync_action(self) -> SyncAction:
    """
    Evaluates the data difference and suggests a Sync Action
    """
    status = self.get_sync_status()
    if status is SyncStatus.SYNC_POSSIBLE:
        if self.left_value is None:
            action = SyncAction.LEFT_SYNC
        else:
            action = SyncAction.RIGHT_SYNC
    else:
        action = SyncAction.NOTHING
    return action

ComparisonRecord

Compares two dicts

Source code in onlinespreadsheet/record_sync.py
 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
class ComparisonRecord:
    """
    Compares two dicts
    """

    def __init__(
        self,
        left_source_name: str,
        left_record: dict,
        right_source_name: str,
        right_record: dict,
    ):
        """
        constructor
        Args:
            left_record: record to compare
            right_record: record to compare
        """
        self.left_source_name = left_source_name
        self.right_source_name = right_source_name
        if left_record is None:
            left_record = dict()
        if right_record is None:
            right_record = dict()
        self.comparison_data = dict()
        property_names = []
        for p in list(left_record.keys()) + list(right_record.keys()):
            if p not in property_names:
                property_names.append(p)
        for property_name in property_names:
            cd = ComparisonData(
                property_name=property_name,
                left_value=left_record.get(property_name, None),
                right_value=right_record.get(property_name, None),
            )
            self.comparison_data[property_name] = cd

    def get_update_records(self) -> typing.Tuple[dict, dict]:
        """
        Get the update records for both sides

        Returns:
            (dict, dict): updates that should be applied to both sides
        """
        update_left = dict()
        update_right = dict()
        for cd in self.comparison_data.values():
            action = cd.get_chosen_sync_option()
            if action is SyncAction.LEFT_SYNC:
                # right to left
                update_left[cd.property_name] = cd.right_value
            elif action is SyncAction.RIGHT_SYNC:
                # left to right
                update_right[cd.property_name] = cd.left_value
        return update_left, update_right

    def get_update_record_of(self, source_name: str) -> dict:
        """
        Get the update record for the given source name
        Args:
            source_name: name of one of the sources

        Returns:
            dict: update for the given source
        """
        update_rec_left, update_rec_right = self.get_update_records()
        update_record = dict()
        if source_name == self.left_source_name:
            update_record = update_rec_left
        elif source_name == self.right_source_name:
            update_record = update_rec_right
        return update_record

__init__(left_source_name, left_record, right_source_name, right_record)

constructor Args: left_record: record to compare right_record: record to compare

Source code in onlinespreadsheet/record_sync.py
 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
def __init__(
    self,
    left_source_name: str,
    left_record: dict,
    right_source_name: str,
    right_record: dict,
):
    """
    constructor
    Args:
        left_record: record to compare
        right_record: record to compare
    """
    self.left_source_name = left_source_name
    self.right_source_name = right_source_name
    if left_record is None:
        left_record = dict()
    if right_record is None:
        right_record = dict()
    self.comparison_data = dict()
    property_names = []
    for p in list(left_record.keys()) + list(right_record.keys()):
        if p not in property_names:
            property_names.append(p)
    for property_name in property_names:
        cd = ComparisonData(
            property_name=property_name,
            left_value=left_record.get(property_name, None),
            right_value=right_record.get(property_name, None),
        )
        self.comparison_data[property_name] = cd

get_update_record_of(source_name)

Get the update record for the given source name Args: source_name: name of one of the sources

Returns:

Name Type Description
dict dict

update for the given source

Source code in onlinespreadsheet/record_sync.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def get_update_record_of(self, source_name: str) -> dict:
    """
    Get the update record for the given source name
    Args:
        source_name: name of one of the sources

    Returns:
        dict: update for the given source
    """
    update_rec_left, update_rec_right = self.get_update_records()
    update_record = dict()
    if source_name == self.left_source_name:
        update_record = update_rec_left
    elif source_name == self.right_source_name:
        update_record = update_rec_right
    return update_record

get_update_records()

Get the update records for both sides

Returns:

Type Description
(dict, dict)

updates that should be applied to both sides

Source code in onlinespreadsheet/record_sync.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def get_update_records(self) -> typing.Tuple[dict, dict]:
    """
    Get the update records for both sides

    Returns:
        (dict, dict): updates that should be applied to both sides
    """
    update_left = dict()
    update_right = dict()
    for cd in self.comparison_data.values():
        action = cd.get_chosen_sync_option()
        if action is SyncAction.LEFT_SYNC:
            # right to left
            update_left[cd.property_name] = cd.right_value
        elif action is SyncAction.RIGHT_SYNC:
            # left to right
            update_right[cd.property_name] = cd.left_value
    return update_left, update_right

SyncAction

Bases: Enum

synchronization action

Source code in onlinespreadsheet/record_sync.py
23
24
25
26
27
28
29
30
31
32
33
34
class SyncAction(Enum):
    """
    synchronization action
    """

    LEFT_SYNC = "←"
    RIGHT_SYNC = "→"
    NOTHING = ""
    SYNC = "⇆"

    def __missing__(self, _key):
        return self.NOTHING

SyncRequest dataclass

Synchronization request containing the sync action to apply and the corresponding data

Source code in onlinespreadsheet/record_sync.py
162
163
164
165
166
167
168
169
@dataclass
class SyncRequest:
    """
    Synchronization request containing the sync action to apply and the corresponding data
    """

    action: SyncAction
    data: ComparisonRecord

SyncStatus

Bases: Enum

synchronization status

Source code in onlinespreadsheet/record_sync.py
13
14
15
16
17
18
19
20
class SyncStatus(Enum):
    """
    synchronization status
    """

    IN_SYNC = "✓"
    SYNC_POSSIBLE = ""
    OUT_SYNC = "❌"

record_sync_view

Created on 2024-03-18

@author: wf

SyncDialog

dialog widget to synchronize two data records

Source code in onlinespreadsheet/record_sync_view.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 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
class SyncDialog:
    """
    dialog widget to synchronize two data records
    """

    def __init__(
        self,
        solution: InputWebSolution,
        comparison_record: ComparisonRecord,
        sync_callback: typing.Callable[[SyncRequest], None] = None,
        value_enhancement_callback: typing.Callable[["SyncDialogRow"], None] = None,
    ):
        """
        constructor
        """
        self.solution = (solution,)
        self.comparison_record = comparison_record
        if sync_callback is None:
            sync_callback = self.__fallback_sync_callback
        self.sync_callback = sync_callback
        self.rows = []
        for cd in self.comparison_record.comparison_data.values():
            sdr = SyncDialogRow(cd, a=self.table_body, classes="")
            self.rows.append(sdr)
        self.value_enhancement_callback = value_enhancement_callback
        self.enhance_row_values()

    def setup_ui(self):
        """ """
        self.setup_header()
        self.setup_controls()

    def setup_header(self):
        """
        setup header column
        """
        self.header = ui.html()
        jp.Th(a=self.header, text="Property")
        jp.Th(a=self.header, text=self.comparison_record.left_source_name)
        action_header = jp.Th(a=self.header, style="width:60px")
        color = "grey"
        selector = EnumSelector(
            enum=SyncAction,
            exclude=[SyncAction.NOTHING],
            a=action_header,
            value=SyncAction.SYNC.name,
            on_change=self.handle_sync_action_change,
        )
        jp.Th(a=self.header, text=self.comparison_record.right_source_name)

    def setup_controls(self):
        with ui.row() as self.button_row:
            btn_sync_left = ui.button(
                f"update {self.comparison_record.left_source_name}",
                on_click=self.handle_sync_left_click,
            ).style("btn-primary")

            btn_sync_both = ui.button(
                "update both", on_click=self.handle_sync_click
            ).style("btn-primary")

            btn_sync_right = ui.button(
                f"update {self.comparison_record.right_source_name}",
                on_click=self.handle_sync_right_click,
            ).style("btn-primary")

    def handle_sync_action_change(self, msg):
        """
        Handles change in selected global sync action
        """
        global_action = SyncAction[msg.value]
        for row in self.rows:
            new_action = row.comparison_data.chosen_sync_option
            if global_action in [SyncAction.LEFT_SYNC, SyncAction.RIGHT_SYNC]:
                # apply selected sync action to all rows
                new_action = global_action
            elif global_action is SyncAction.SYNC:
                new_action = row.comparison_data.suggested_sync_action()
            row.comparison_data.chosen_sync_option = new_action
            row.sync_action_selector.value = new_action.name

    def handle_sync_left_click(self, _msg):
        self.handover_sync_callback(SyncAction.LEFT_SYNC)

    def handle_sync_right_click(self, _msg):
        self.handover_sync_callback(SyncAction.RIGHT_SYNC)

    def handle_sync_click(self, _msg):
        self.handover_sync_callback(SyncAction.SYNC)

    def handover_sync_callback(self, action: SyncAction):
        """
        Generates the SyncRequest and hands it over to the defined callback function
        Args:
            action: sync action to apply
        """
        sync_request = SyncRequest(action=action, data=self.comparison_record)
        self.sync_callback(sync_request)

    def __fallback_sync_callback(self, req: SyncRequest):
        """
        Fallback Sync handler
        """
        msg = f"No synchronization callback defined {req}"
        print(msg)

    def enhance_row_values(self):
        """
        Enhance the row values
        """
        for row in self.rows:
            if self.value_enhancement_callback is not None:
                self.value_enhancement_callback(row)

__fallback_sync_callback(req)

Fallback Sync handler

Source code in onlinespreadsheet/record_sync_view.py
109
110
111
112
113
114
def __fallback_sync_callback(self, req: SyncRequest):
    """
    Fallback Sync handler
    """
    msg = f"No synchronization callback defined {req}"
    print(msg)

__init__(solution, comparison_record, sync_callback=None, value_enhancement_callback=None)

constructor

Source code in onlinespreadsheet/record_sync_view.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def __init__(
    self,
    solution: InputWebSolution,
    comparison_record: ComparisonRecord,
    sync_callback: typing.Callable[[SyncRequest], None] = None,
    value_enhancement_callback: typing.Callable[["SyncDialogRow"], None] = None,
):
    """
    constructor
    """
    self.solution = (solution,)
    self.comparison_record = comparison_record
    if sync_callback is None:
        sync_callback = self.__fallback_sync_callback
    self.sync_callback = sync_callback
    self.rows = []
    for cd in self.comparison_record.comparison_data.values():
        sdr = SyncDialogRow(cd, a=self.table_body, classes="")
        self.rows.append(sdr)
    self.value_enhancement_callback = value_enhancement_callback
    self.enhance_row_values()

enhance_row_values()

Enhance the row values

Source code in onlinespreadsheet/record_sync_view.py
116
117
118
119
120
121
122
def enhance_row_values(self):
    """
    Enhance the row values
    """
    for row in self.rows:
        if self.value_enhancement_callback is not None:
            self.value_enhancement_callback(row)

handle_sync_action_change(msg)

Handles change in selected global sync action

Source code in onlinespreadsheet/record_sync_view.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def handle_sync_action_change(self, msg):
    """
    Handles change in selected global sync action
    """
    global_action = SyncAction[msg.value]
    for row in self.rows:
        new_action = row.comparison_data.chosen_sync_option
        if global_action in [SyncAction.LEFT_SYNC, SyncAction.RIGHT_SYNC]:
            # apply selected sync action to all rows
            new_action = global_action
        elif global_action is SyncAction.SYNC:
            new_action = row.comparison_data.suggested_sync_action()
        row.comparison_data.chosen_sync_option = new_action
        row.sync_action_selector.value = new_action.name

handover_sync_callback(action)

Generates the SyncRequest and hands it over to the defined callback function Args: action: sync action to apply

Source code in onlinespreadsheet/record_sync_view.py
100
101
102
103
104
105
106
107
def handover_sync_callback(self, action: SyncAction):
    """
    Generates the SyncRequest and hands it over to the defined callback function
    Args:
        action: sync action to apply
    """
    sync_request = SyncRequest(action=action, data=self.comparison_record)
    self.sync_callback(sync_request)

setup_header()

setup header column

Source code in onlinespreadsheet/record_sync_view.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def setup_header(self):
    """
    setup header column
    """
    self.header = ui.html()
    jp.Th(a=self.header, text="Property")
    jp.Th(a=self.header, text=self.comparison_record.left_source_name)
    action_header = jp.Th(a=self.header, style="width:60px")
    color = "grey"
    selector = EnumSelector(
        enum=SyncAction,
        exclude=[SyncAction.NOTHING],
        a=action_header,
        value=SyncAction.SYNC.name,
        on_change=self.handle_sync_action_change,
    )
    jp.Th(a=self.header, text=self.comparison_record.right_source_name)

setup_ui()

Source code in onlinespreadsheet/record_sync_view.py
37
38
39
40
def setup_ui(self):
    """ """
    self.setup_header()
    self.setup_controls()

SyncDialogRow

row in the SyncDialog

Source code in onlinespreadsheet/record_sync_view.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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
193
194
195
class SyncDialogRow:
    """
    row in the SyncDialog
    """

    def __init__(self, data: ComparisonData):
        """
        constructor
        Args:
            data: data to compare/sync in this row
        """
        self.comparison_data = data
        row_color = self.get_row_color()
        cell_classes = f"border border-green-600 mx-2 my-1 p-2 {row_color}"
        self.property_name_div = jp.Td(
            a=self, text=self.comparison_data.property_name, classes=cell_classes
        )
        self.left_value_div = jp.Td(
            a=self, text=self.comparison_data.left_value, classes=cell_classes
        )
        self.sync_status_div = jp.Td(a=self, classes=cell_classes)
        self.sync_action_selector = self.setup_sync_action_selector()
        self.right_value_div = jp.Td(
            a=self, text=self.comparison_data.right_value, classes=cell_classes
        )
        self.enhance_value_display()

    def get_row_color(self):
        """
        defines the row background color based on the sync status
        """
        color = ""
        status = self.comparison_data.get_sync_status()
        if status is SyncStatus.IN_SYNC:
            color = "green"
        elif status is SyncStatus.OUT_SYNC:
            color = "red"
        intensity = 200
        return f"bg-{color}-{intensity}"

    def setup_sync_action_selector(self):
        """
        setup sync action selector and choose default action based on the data
        """
        # div = jp.Div(a=self.sync_status_div, classes="flex justify-end")
        # status_div = jp.Div(a=div, text=self.comparison_data.get_sync_status().value)
        # color = "grey"
        # selector = EnumSelector(
        #    enum=SyncAction,
        #    exclude=[SyncAction.SYNC],
        #    a=div,
        #    value=self.comparison_data.get_chosen_sync_option().name,
        #    on_change=self.change_sync_action,
        # )
        selector = None
        return selector

    def change_sync_action(self, _msg):
        """
        handle change in sync action
        """
        new_action = SyncAction[self.sync_action_selector.value]
        print(
            f"Changing sync action from {self.comparison_data.chosen_sync_option} to {new_action}"
        )
        self.comparison_data.chosen_sync_option = new_action

    def enhance_value_display(self):
        """
        Enhances the displayed value
        """

__init__(data)

constructor Args: data: data to compare/sync in this row

Source code in onlinespreadsheet/record_sync_view.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def __init__(self, data: ComparisonData):
    """
    constructor
    Args:
        data: data to compare/sync in this row
    """
    self.comparison_data = data
    row_color = self.get_row_color()
    cell_classes = f"border border-green-600 mx-2 my-1 p-2 {row_color}"
    self.property_name_div = jp.Td(
        a=self, text=self.comparison_data.property_name, classes=cell_classes
    )
    self.left_value_div = jp.Td(
        a=self, text=self.comparison_data.left_value, classes=cell_classes
    )
    self.sync_status_div = jp.Td(a=self, classes=cell_classes)
    self.sync_action_selector = self.setup_sync_action_selector()
    self.right_value_div = jp.Td(
        a=self, text=self.comparison_data.right_value, classes=cell_classes
    )
    self.enhance_value_display()

change_sync_action(_msg)

handle change in sync action

Source code in onlinespreadsheet/record_sync_view.py
182
183
184
185
186
187
188
189
190
def change_sync_action(self, _msg):
    """
    handle change in sync action
    """
    new_action = SyncAction[self.sync_action_selector.value]
    print(
        f"Changing sync action from {self.comparison_data.chosen_sync_option} to {new_action}"
    )
    self.comparison_data.chosen_sync_option = new_action

enhance_value_display()

Enhances the displayed value

Source code in onlinespreadsheet/record_sync_view.py
192
193
194
195
def enhance_value_display(self):
    """
    Enhances the displayed value
    """

get_row_color()

defines the row background color based on the sync status

Source code in onlinespreadsheet/record_sync_view.py
152
153
154
155
156
157
158
159
160
161
162
163
def get_row_color(self):
    """
    defines the row background color based on the sync status
    """
    color = ""
    status = self.comparison_data.get_sync_status()
    if status is SyncStatus.IN_SYNC:
        color = "green"
    elif status is SyncStatus.OUT_SYNC:
        color = "red"
    intensity = 200
    return f"bg-{color}-{intensity}"

setup_sync_action_selector()

setup sync action selector and choose default action based on the data

Source code in onlinespreadsheet/record_sync_view.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
def setup_sync_action_selector(self):
    """
    setup sync action selector and choose default action based on the data
    """
    # div = jp.Div(a=self.sync_status_div, classes="flex justify-end")
    # status_div = jp.Div(a=div, text=self.comparison_data.get_sync_status().value)
    # color = "grey"
    # selector = EnumSelector(
    #    enum=SyncAction,
    #    exclude=[SyncAction.SYNC],
    #    a=div,
    #    value=self.comparison_data.get_chosen_sync_option().name,
    #    on_change=self.change_sync_action,
    # )
    selector = None
    return selector

spreadsheet_view

Created on 2024-03-19

@author: wf

SpreadSheetView

shows a Spreadsheet

Source code in onlinespreadsheet/spreadsheet_view.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 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
class SpreadSheetView:
    """
    shows a Spreadsheet
    """

    def __init__(self, solution):
        self.solution = solution
        self.debug = self.solution.debug
        self.args = solution.args
        self.url = self.args.url
        self.sheetNames = self.args.sheets
        if len(self.sheetNames) < 1:
            raise Exception("need at least one sheetName in sheets argument")
        self.sheetName = self.sheetNames[0]
        self.mappingSheetName = self.args.mappingSheet
        self.endpoint = self.args.endpoint
        self.sparql = SPARQL(self.endpoint)
        self.lang = self.args.lang
        self.setup_ui()
        # self.grid_sync=GridSync()

    def setup_ui(self):
        """
        setup my user interface
        """
        with ui.row() as self.log_row:
            self.log_view = ui.html()
        with ui.row() as self.input_row:
            url_label_text = "Google Spreadsheet Url"
            ui.label(url_label_text)
            ui.input(
                value=self.url,
                placeholder=f"Enter new {url_label_text}",
                on_change=self.on_change_url,
            )
            ui.button("reload", on_click=self.reload)
        with ui.row() as self.grid_row:
            self.lod_grid = ListOfDictsGrid()

        # ui.timer(0, self.reload, once=True)

    def load_items_from_selected_sheet(self) -> List[dict]:
        """
        Extract the records from the selected sheet and returns them as LoD

        Returns:
            List of dicts containing the sheet content
        """

        self.wbQueries = GoogleSheet.toWikibaseQuery(
            self.url, self.mappingSheetName, debug=self.debug
        )
        if len(self.wbQueries) == 0:
            print(
                f"Warning Wikidata mapping sheet {self.mappingSheetName} not defined!"
            )
        self.gs = GoogleSheet(self.url)
        self.gs.open([self.sheetName])
        items = self.gs.asListOfDicts(self.sheetName)
        wbQuery = self.wbQueries.get(self.sheetName, None)
        # self.gridSync.wbQuery = wbQuery
        return items

    def load_sheet(self):
        """
        load sheet in background
        """
        with self.solution.content_div:
            try:
                items = self.load_items_from_selected_sheet()
                self.lod_grid.load_lod(items)
                ui.notify(f"loaded {len(items)} items")
                self.lod_grid.update()
            except Exception as ex:
                self.solution.handle_exception(ex)

    async def reload(self):
        """
        reload my spreadsheet
        """
        try:
            link = Link.create(self.url, self.sheetNames[0])
            self.log_view.content = (
                f"{link}<br>{self.lang} {self.endpoint} {self.sheetNames}"
            )
            await run.io_bound(self.load_sheet)
        except Exception as ex:
            self.solution.handle_exception(ex)

    async def on_change_url(self, args):
        """
        handle selection of a different url
        """
        self.url = args.value
        await self.reload()
        pass

load_items_from_selected_sheet()

Extract the records from the selected sheet and returns them as LoD

Returns:

Type Description
List[dict]

List of dicts containing the sheet content

Source code in onlinespreadsheet/spreadsheet_view.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def load_items_from_selected_sheet(self) -> List[dict]:
    """
    Extract the records from the selected sheet and returns them as LoD

    Returns:
        List of dicts containing the sheet content
    """

    self.wbQueries = GoogleSheet.toWikibaseQuery(
        self.url, self.mappingSheetName, debug=self.debug
    )
    if len(self.wbQueries) == 0:
        print(
            f"Warning Wikidata mapping sheet {self.mappingSheetName} not defined!"
        )
    self.gs = GoogleSheet(self.url)
    self.gs.open([self.sheetName])
    items = self.gs.asListOfDicts(self.sheetName)
    wbQuery = self.wbQueries.get(self.sheetName, None)
    # self.gridSync.wbQuery = wbQuery
    return items

load_sheet()

load sheet in background

Source code in onlinespreadsheet/spreadsheet_view.py
82
83
84
85
86
87
88
89
90
91
92
93
def load_sheet(self):
    """
    load sheet in background
    """
    with self.solution.content_div:
        try:
            items = self.load_items_from_selected_sheet()
            self.lod_grid.load_lod(items)
            ui.notify(f"loaded {len(items)} items")
            self.lod_grid.update()
        except Exception as ex:
            self.solution.handle_exception(ex)

on_change_url(args) async

handle selection of a different url

Source code in onlinespreadsheet/spreadsheet_view.py
108
109
110
111
112
113
114
async def on_change_url(self, args):
    """
    handle selection of a different url
    """
    self.url = args.value
    await self.reload()
    pass

reload() async

reload my spreadsheet

Source code in onlinespreadsheet/spreadsheet_view.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
async def reload(self):
    """
    reload my spreadsheet
    """
    try:
        link = Link.create(self.url, self.sheetNames[0])
        self.log_view.content = (
            f"{link}<br>{self.lang} {self.endpoint} {self.sheetNames}"
        )
        await run.io_bound(self.load_sheet)
    except Exception as ex:
        self.solution.handle_exception(ex)

setup_ui()

setup my user interface

Source code in onlinespreadsheet/spreadsheet_view.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def setup_ui(self):
    """
    setup my user interface
    """
    with ui.row() as self.log_row:
        self.log_view = ui.html()
    with ui.row() as self.input_row:
        url_label_text = "Google Spreadsheet Url"
        ui.label(url_label_text)
        ui.input(
            value=self.url,
            placeholder=f"Enter new {url_label_text}",
            on_change=self.on_change_url,
        )
        ui.button("reload", on_click=self.reload)
    with ui.row() as self.grid_row:
        self.lod_grid = ListOfDictsGrid()

tablequery

Created on 2021-12-09

@author: wf

QueryType

Bases: Enum

Query type

Source code in onlinespreadsheet/tablequery.py
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class QueryType(Enum):
    """
    Query type
    """

    SQL = auto()
    RESTful = auto()
    ASK = auto()
    SPARQL = auto()
    INVALID = auto()

    @staticmethod
    def match(pattern: str, string: str):
        """
        re match search for the given pattern with ignore case
        """
        return re.search(pattern=pattern, string=string, flags=re.IGNORECASE)

match(pattern, string) staticmethod

re match search for the given pattern with ignore case

Source code in onlinespreadsheet/tablequery.py
82
83
84
85
86
87
@staticmethod
def match(pattern: str, string: str):
    """
    re match search for the given pattern with ignore case
    """
    return re.search(pattern=pattern, string=string, flags=re.IGNORECASE)

SmwWikiAccess

Access to Semantic MediaWiki

Source code in onlinespreadsheet/tablequery.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
class SmwWikiAccess:
    """
    Access to Semantic MediaWiki
    """

    # TODO move to general project

    def __init__(
        self,
        wikiId: str,
        showProgress=False,
        queryDivision=1,
        debug=False,
        lenient=True,
    ):
        """
        constructor
        """
        self.debug = debug
        self.wikiUser = WikiUser.ofWikiId(wikiId, lenient=lenient)
        self.wikiClient = WikiClient.ofWikiUser(self.wikiUser)
        self.smwClient = SMWClient(
            self.wikiClient.getSite(),
            showProgress=showProgress,
            queryDivision=queryDivision,
            debug=self.debug,
        )
        # self.wikiPush = WikiPush(fromWikiId=self.wikiUser.wikiId)

    def login(self):
        self.wikiClient.login()

    def query(self, query: str):
        """
        query with auto-login
        """
        try:
            qres = self.smwClient.query(query)
        except APIError as apie:
            if "readapidenied" in str(apie):
                # retry with login
                self.login()
                qres = self.smwClient.query(query)
            else:
                raise apie
        return qres

__init__(wikiId, showProgress=False, queryDivision=1, debug=False, lenient=True)

constructor

Source code in onlinespreadsheet/tablequery.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def __init__(
    self,
    wikiId: str,
    showProgress=False,
    queryDivision=1,
    debug=False,
    lenient=True,
):
    """
    constructor
    """
    self.debug = debug
    self.wikiUser = WikiUser.ofWikiId(wikiId, lenient=lenient)
    self.wikiClient = WikiClient.ofWikiUser(self.wikiUser)
    self.smwClient = SMWClient(
        self.wikiClient.getSite(),
        showProgress=showProgress,
        queryDivision=queryDivision,
        debug=self.debug,
    )

query(query)

query with auto-login

Source code in onlinespreadsheet/tablequery.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def query(self, query: str):
    """
    query with auto-login
    """
    try:
        qres = self.smwClient.query(query)
    except APIError as apie:
        if "readapidenied" in str(apie):
            # retry with login
            self.login()
            qres = self.smwClient.query(query)
        else:
            raise apie
    return qres

TableQuery

Bases: object

prepare a Spreadsheet editing

Source code in onlinespreadsheet/tablequery.py
 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
class TableQuery(object):
    """
    prepare a Spreadsheet editing
    """

    def __init__(self, debug=False):
        """
        Constructor
        """
        self.debug = debug
        self.wikiAccessMap = {}
        self.queries = {}
        self.tableEditing = TableEditing()
        self.errors = []

    def addQuery(self, query: Query):
        """
        add the query with the given name to my queries

        query(Query): the query to add
        """
        self.queries[query.name] = query

    def fetchQueryResults(self):
        """
        fetch the QueryResults

        """
        for queryName, query in self.queries.items():
            qres = None
            if query.lang == "ask":
                if not hasattr(query, "wikiAccess") or query.wikiAccess is None:
                    raise (
                        f"wikiAccess needs to be configured for Semantic MediaWiki ask query '{query.name}'"
                    )
                qres = query.wikiAccess.query(query.query)
                # workaround: undict if dict of dict is returned
                # TODO: check whether this may be fixed upstream
                if isinstance(qres, dict):
                    qres = list(qres.values())
            elif query.lang.lower() == "sparql":
                if not hasattr(query, "endpoint") or query.endpoint is None:
                    raise (
                        f"endpoint needs to be configured for SPARQL query '{query.name}'"
                    )
                qres = query.endpoint.queryAsListOfDicts(query.query)
            elif query.lang.lower() == "restful":
                response = requests.request("GET", query.query)
                if response.status_code == 200:
                    qres = response.json()
                else:
                    self.errors.append(
                        f"{query.query} failed with status {response.status_code}"
                    )
            if qres is not None:
                if isinstance(qres, list):
                    self.tableEditing.addLoD(query.name, qres)
                elif isinstance(qres, dict):
                    for name, lod in qres.items():
                        self.tableEditing.addLoD(f"{queryName}_{name}", lod)

    def addAskQuery(
        self, wikiId: str, name, ask: str, title: str = None, description: str = None
    ):
        """
        add an ask query for the given wiki

        Args:
              wikiId(str): the id of the wiki to add
              name(str): the name of the query to add
              ask(str): the SMW ask query
              title(str): the title of the query
              description(str): the description of the query
        """
        if wikiId not in self.wikiAccessMap:
            self.wikiAccessMap[wikiId] = SmwWikiAccess(wikiId)
        wikiAccess = self.wikiAccessMap[wikiId]
        query = Query(
            name=name,
            query=ask,
            lang="ask",
            title=title,
            description=description,
            debug=self.debug,
        )
        query.wikiAccess = wikiAccess
        self.addQuery(query)

    def fromAskQueries(self, wikiId: str, askQueries: list, withFetch: bool = True):
        """
        initialize me from the given Queries
        """
        for askQuery in askQueries:
            name = askQuery["name"]
            ask = askQuery["ask"]
            title = askQuery["title"] if "title" in askQuery else None
            description = askQuery["description"] if "description" in askQuery else None
            self.addAskQuery(wikiId, name, ask, title, description)
        if withFetch:
            self.fetchQueryResults()

    def addRESTfulQuery(
        self, name: str, url: str, title: str = None, description: str = None
    ):
        """
        add RESTFful query to the queries

        Args:
            url(str): RESTful query URL optionally with parameters
            name(str): name of the query
            title(str): title of the query
            description(str): description of the query
        """
        query = Query(
            name=name,
            query=url,
            lang="restful",
            title=title,
            description=description,
            debug=self.debug,
        )
        self.addQuery(query)

    def addSparqlQuery(
        self,
        name: str,
        query: str,
        endpointUrl: str = "https://query.wikidata.org/sparql",
        title: str = None,
        description: str = None,
    ):
        """
        add SPARQL query to the queries

        Args:
            name(str): name of the query
            query(str): the SPARQL query to execute
            endpointUrl(str): the url of the endpoint to use
            title(str): title of the query
            description(str): description of the query
        """
        query = Query(
            name=name,
            query=query,
            lang="sparql",
            title=title,
            description=description,
            debug=self.debug,
        )
        query.endpoint = SPARQL(endpointUrl)
        self.addQuery(query)

    @staticmethod
    def guessQueryType(query: str) -> Optional[QueryType]:
        """
        Tries to guess the query type of the given query

        Args:
            query(str): query

        Returns:
            QueryType
        """
        query = query.lower().strip()
        if query.startswith("http"):
            return QueryType.RESTful
        elif query.startswith("{{#ask:"):
            return QueryType.ASK
        elif (
            QueryType.match(r"prefix", query)
            or QueryType.match(r"\s*select\s+\?", query)
        ) or QueryType.match(r"#.*SPARQL", query):
            return QueryType.SPARQL
        elif QueryType.match(r"\s*select", query) and QueryType.match(
            r"\s*from\s+", query
        ):
            return QueryType.SQL
        else:
            return QueryType.INVALID

__init__(debug=False)

Constructor

Source code in onlinespreadsheet/tablequery.py
 95
 96
 97
 98
 99
100
101
102
103
def __init__(self, debug=False):
    """
    Constructor
    """
    self.debug = debug
    self.wikiAccessMap = {}
    self.queries = {}
    self.tableEditing = TableEditing()
    self.errors = []

addAskQuery(wikiId, name, ask, title=None, description=None)

add an ask query for the given wiki

Parameters:

Name Type Description Default
wikiId(str)

the id of the wiki to add

required
name(str)

the name of the query to add

required
ask(str)

the SMW ask query

required
title(str)

the title of the query

required
description(str)

the description of the query

required
Source code in onlinespreadsheet/tablequery.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def addAskQuery(
    self, wikiId: str, name, ask: str, title: str = None, description: str = None
):
    """
    add an ask query for the given wiki

    Args:
          wikiId(str): the id of the wiki to add
          name(str): the name of the query to add
          ask(str): the SMW ask query
          title(str): the title of the query
          description(str): the description of the query
    """
    if wikiId not in self.wikiAccessMap:
        self.wikiAccessMap[wikiId] = SmwWikiAccess(wikiId)
    wikiAccess = self.wikiAccessMap[wikiId]
    query = Query(
        name=name,
        query=ask,
        lang="ask",
        title=title,
        description=description,
        debug=self.debug,
    )
    query.wikiAccess = wikiAccess
    self.addQuery(query)

addQuery(query)

add the query with the given name to my queries

query(Query): the query to add

Source code in onlinespreadsheet/tablequery.py
105
106
107
108
109
110
111
def addQuery(self, query: Query):
    """
    add the query with the given name to my queries

    query(Query): the query to add
    """
    self.queries[query.name] = query

addRESTfulQuery(name, url, title=None, description=None)

add RESTFful query to the queries

Parameters:

Name Type Description Default
url(str)

RESTful query URL optionally with parameters

required
name(str)

name of the query

required
title(str)

title of the query

required
description(str)

description of the query

required
Source code in onlinespreadsheet/tablequery.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
def addRESTfulQuery(
    self, name: str, url: str, title: str = None, description: str = None
):
    """
    add RESTFful query to the queries

    Args:
        url(str): RESTful query URL optionally with parameters
        name(str): name of the query
        title(str): title of the query
        description(str): description of the query
    """
    query = Query(
        name=name,
        query=url,
        lang="restful",
        title=title,
        description=description,
        debug=self.debug,
    )
    self.addQuery(query)

addSparqlQuery(name, query, endpointUrl='https://query.wikidata.org/sparql', title=None, description=None)

add SPARQL query to the queries

Parameters:

Name Type Description Default
name(str)

name of the query

required
query(str)

the SPARQL query to execute

required
endpointUrl(str)

the url of the endpoint to use

required
title(str)

title of the query

required
description(str)

description of the query

required
Source code in onlinespreadsheet/tablequery.py
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
def addSparqlQuery(
    self,
    name: str,
    query: str,
    endpointUrl: str = "https://query.wikidata.org/sparql",
    title: str = None,
    description: str = None,
):
    """
    add SPARQL query to the queries

    Args:
        name(str): name of the query
        query(str): the SPARQL query to execute
        endpointUrl(str): the url of the endpoint to use
        title(str): title of the query
        description(str): description of the query
    """
    query = Query(
        name=name,
        query=query,
        lang="sparql",
        title=title,
        description=description,
        debug=self.debug,
    )
    query.endpoint = SPARQL(endpointUrl)
    self.addQuery(query)

fetchQueryResults()

fetch the QueryResults

Source code in onlinespreadsheet/tablequery.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def fetchQueryResults(self):
    """
    fetch the QueryResults

    """
    for queryName, query in self.queries.items():
        qres = None
        if query.lang == "ask":
            if not hasattr(query, "wikiAccess") or query.wikiAccess is None:
                raise (
                    f"wikiAccess needs to be configured for Semantic MediaWiki ask query '{query.name}'"
                )
            qres = query.wikiAccess.query(query.query)
            # workaround: undict if dict of dict is returned
            # TODO: check whether this may be fixed upstream
            if isinstance(qres, dict):
                qres = list(qres.values())
        elif query.lang.lower() == "sparql":
            if not hasattr(query, "endpoint") or query.endpoint is None:
                raise (
                    f"endpoint needs to be configured for SPARQL query '{query.name}'"
                )
            qres = query.endpoint.queryAsListOfDicts(query.query)
        elif query.lang.lower() == "restful":
            response = requests.request("GET", query.query)
            if response.status_code == 200:
                qres = response.json()
            else:
                self.errors.append(
                    f"{query.query} failed with status {response.status_code}"
                )
        if qres is not None:
            if isinstance(qres, list):
                self.tableEditing.addLoD(query.name, qres)
            elif isinstance(qres, dict):
                for name, lod in qres.items():
                    self.tableEditing.addLoD(f"{queryName}_{name}", lod)

fromAskQueries(wikiId, askQueries, withFetch=True)

initialize me from the given Queries

Source code in onlinespreadsheet/tablequery.py
178
179
180
181
182
183
184
185
186
187
188
189
def fromAskQueries(self, wikiId: str, askQueries: list, withFetch: bool = True):
    """
    initialize me from the given Queries
    """
    for askQuery in askQueries:
        name = askQuery["name"]
        ask = askQuery["ask"]
        title = askQuery["title"] if "title" in askQuery else None
        description = askQuery["description"] if "description" in askQuery else None
        self.addAskQuery(wikiId, name, ask, title, description)
    if withFetch:
        self.fetchQueryResults()

guessQueryType(query) staticmethod

Tries to guess the query type of the given query

Parameters:

Name Type Description Default
query(str)

query

required

Returns:

Type Description
Optional[QueryType]

QueryType

Source code in onlinespreadsheet/tablequery.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
@staticmethod
def guessQueryType(query: str) -> Optional[QueryType]:
    """
    Tries to guess the query type of the given query

    Args:
        query(str): query

    Returns:
        QueryType
    """
    query = query.lower().strip()
    if query.startswith("http"):
        return QueryType.RESTful
    elif query.startswith("{{#ask:"):
        return QueryType.ASK
    elif (
        QueryType.match(r"prefix", query)
        or QueryType.match(r"\s*select\s+\?", query)
    ) or QueryType.match(r"#.*SPARQL", query):
        return QueryType.SPARQL
    elif QueryType.match(r"\s*select", query) and QueryType.match(
        r"\s*from\s+", query
    ):
        return QueryType.SQL
    else:
        return QueryType.INVALID

version

Created on 2022-03-06

@author: wf

Version dataclass

Version handling for pyOnlineSpreadsheetEditing

Source code in onlinespreadsheet/version.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@dataclass
class Version:
    """
    Version handling for pyOnlineSpreadsheetEditing
    """

    name = "pyOnlineSpreadsheetEditing"
    version = onlinespreadsheet.__version__
    date = "2021-12-11"
    updated = "2024-08-22"
    description = "python Online SpreadSheet Editing tool with configurable enhancer/importer and check phase"
    authors = "Wolfgang Fahl/Tim Holzheim"
    doc_url = "https://wiki.bitplan.com/index.php/PyOnlineSpreadSheetEditing"
    chat_url = "https://github.com/WolfgangFahl/pyOnlineSpreadSheetEditing/discussions"
    cm_url = "https://github.com/WolfgangFahl/pyOnlineSpreadSheetEditing"

    license = f"""Copyright 2021-2024 contributors. All rights reserved.

  Licensed under the Apache License 2.0
  http://www.apache.org/licenses/LICENSE-2.0

  Distributed on an "AS IS" basis without warranties
  or conditions of any kind, either express or implied."""
    longDescription = f"""{name} version {version}
{description}

  Created by {authors} on {date} last updated {updated}"""

wdgrid

Created on 2023-01-11

@author: wf

GridSync

allow syncing the grid with data from wikibase

Source code in onlinespreadsheet/wdgrid.py
280
281
282
283
284
285
286
287
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
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
class GridSync:
    """
    allow syncing the grid with data from wikibase
    """

    def __init__(
        self,
        wdgrid: WikidataGrid,
        entityName: str,
        pk: str,
        sparql: SPARQL,
        debug: bool = False,
    ):
        """
        constructor

        Args:
            wdgrid(WikiDataGrid): the wikidata grid to use
            entityName: name of the sheet
            pk: primary key
            sparql(SPARQL): the sparql endpoint access to use
            debug(bool): if True show debug information
        """
        self.wdgrid = wdgrid
        self.app = wdgrid.app
        self.entityName = entityName
        self.pk = pk
        self.sparql = sparql
        self.debug = debug
        self.wdgrid.additional_reload_callback = self.setup_aggrid_post_reload
        self.wdgrid.row_selected_callback = self.handle_row_selected
        self.wbQuery = None

    def loadItems(self):
        # we assume the grid has already been loaded here
        self.itemRows = self.wdgrid.lod
        self.pkColumn, self.pkType, self.pkProp = self.getColumnTypeAndVarname(self.pk)
        self.itemsByPk, _dup = LOD.getLookup(self.itemRows, self.pkColumn)
        if self.debug:
            print(f"{self.entityName} by {self.pkColumn}:{list(self.itemsByPk.keys())}")
            pass

    def setup(self, a, header):
        """
        initialize my components

        Args:
            a(HtmlComponent): the parent component
            header(HtmlComponent): the header for the primary key selector

        """
        selectorClasses = "w-32 m-2 p-2 bg-white"
        self.toolbar = self.app.jp.QToolbar(a=a, classes="flex flex-row gap-2")
        # for icons see  https://quasar.dev/vue-components/icon
        # see justpy/templates/local/materialdesignicons/iconfont/codepoints for available icons
        self.reloadButton = IconButton(
            a=self.toolbar,
            text="",
            title="reload",
            iconName="refresh-circle",
            click=self.wdgrid.reload,
            classes="btn btn-primary btn-sm col-1",
        )
        self.checkButton = IconButton(
            a=self.toolbar,
            text="",
            title="check",
            iconName="check",
            click=self.onCheckWikidata,
            classes="btn btn-primary btn-sm col-1",
        )
        self.loginButton = IconButton(
            a=self.toolbar,
            title="login",
            iconName="login",
            text="",
            click=self.onLogin,
            classes="btn btn-primary btn-sm col-1",
        )
        self.passwordDialog = QPasswordDialog(a=self.app.wp)
        # selector for column/property
        self.pkSelect = self.app.jp.Select(
            classes=selectorClasses, a=header, value=self.pk, change=self.onChangePk
        )

    def setup_aggrid_post_reload(self):
        """
        setup the aggrid
        """
        viewLod = self.wdgrid.viewLod
        self.wdgrid.agGrid.html_columns = self.getHtmlColumns()
        self.wdgrid.linkWikidataItems(viewLod)
        self.pkSelect.delete_components()
        self.pkSelect.add(self.app.jp.Option(value="item", text="item"))
        if self.wbQuery is not None:
            for propertyName, row in self.wbQuery.propertiesByName.items():
                columnName = row["Column"]
                if columnName:
                    self.pkSelect.add(
                        self.app.jp.Option(value=propertyName, text=columnName)
                    )

    async def onChangePk(self, msg: dict):
        """
        handle selection of a different primary key

        Args:
            msg(dict): the justpy message
        """
        self.pk = msg.value
        if self.debug:
            print(f"changed primary key of {self.entityName} to {self.pk}")
        try:
            await self.wdgrid.reload()
        except Exception as ex:
            self.app.handleException(ex)

    def onCheckWikidata(self, msg=None):
        """
        check clicked - check the wikidata content

        Args:
            msg(dict): the justpy message
        """
        if self.debug:
            print(msg)
        try:
            self.app.clearErrors()
            self.loadItems()
            # prepare syncing the table results with the wikibase query result
            # query based on table content
            self.query(self.sparql)
            # get the view copy to insert result as html statements
            viewLod = self.wdgrid.viewLod
            self.addHtmlMarkupToViewLod(viewLod)
            # reload the AG Grid with the html enriched content
            self.wdgrid.reloadAgGrid(viewLod)
        except Exception as ex:
            self.app.handleException(ex)

    def query(self, sparql):
        """
        query the wikibase instance based on the list of dict
        """
        lang = "en" if self.pkType == "text" else None
        valuesClause = self.wbQuery.getValuesClause(
            self.itemsByPk.keys(), self.pkProp, propType=self.pkType, lang=lang
        )
        self.sparqlQuery = self.wbQuery.asSparql(
            filterClause=valuesClause,
            orderClause=f"ORDER BY ?{self.pkProp}",
            pk=self.pk,
        )
        if self.debug:
            print(self.sparqlQuery)
        self.wbRows = sparql.queryAsListOfDicts(self.sparqlQuery)
        if self.debug:
            pprint.pprint(self.wbRows)

    def checkCell(
        self,
        viewLodRow,
        column,
        value,
        propVarname,
        propType,
        propLabel,
        propUrl: str = None,
    ):
        """
        update the cell value for the given

        Args:
            viewLodRow(dict): the row to modify
            value(object): the value to set for the cell
            propVarName(str): the name of the property Variable set in the SPARQL statement
            propType(str): the abbreviation for the property Type
            propLabel(str): the propertyLabel (if any)
            propUrl(str): the propertyUrl (if any)
        """
        cellValue = viewLodRow[column]
        valueType = type(value)
        print(
            f"{column}({propVarname})={value}({propLabel}:{propUrl}:{valueType})⮂{cellValue}"
        )
        # overwrite empty cells
        overwrite = not cellValue
        if cellValue:
            # overwrite values with links
            if propUrl and cellValue == value:
                overwrite = True
        if overwrite and value:
            doadd = True
            # create links for item  properties
            if not propType:
                value = self.wdgrid.createLink(value, propLabel)
            elif propType == "extid" or propType == "url":
                value = self.wdgrid.createLink(propUrl, value)
            if valueType == str:
                pass
            elif valueType == datetime.datetime:
                value = value.strftime("%Y-%m-%d")
            else:
                doadd = False
                print(f"{valueType} not added")
            if doadd:
                viewLodRow[column] = value

    def addHtmlMarkupToViewLod(self, viewLod: list):
        """
        add HtmlMarkup to the view list of dicts
        viewLod(list): a list of dict for the mark result
        """
        # now check the wikibase rows retrieved in comparison
        # to the current view List of Dicts Markup
        for wbRow in self.wbRows:
            # get the primary key value
            pkValue = wbRow[self.pkProp]
            pkValue = re.sub(
                r"http://www.wikidata.org/entity/(Q[0-9]+)", r"\1", pkValue
            )
            # if we have the primary key then we mark the whole row
            if pkValue in self.itemsByPk:
                if self.debug:
                    print(f"adding html markup for {pkValue}")
                # https://stackoverflow.com/questions/14538885/how-to-get-the-index-with-the-key-in-a-dictionary
                lodRow = self.itemsByPk[pkValue]
                rowIndex = lodRow[self.wdgrid.lodRowIndex_column]
                viewLodRow = viewLod[rowIndex]
                itemLink = self.wdgrid.createLink(wbRow["item"], wbRow["itemLabel"])
                viewLodRow["item"] = itemLink
                itemDescription = wbRow.get("itemDescription", "")
                self.checkCell(
                    viewLodRow,
                    "description",
                    itemDescription,
                    propVarname="itemDescription",
                    propType="string",
                    propLabel="",
                )
                # loop over the result items
                for propVarname, value in wbRow.items():
                    # remap the property variable name to the original property description
                    if propVarname in self.wbQuery.propertiesByVarname:
                        propRow = self.wbQuery.propertiesByVarname[propVarname]
                        column = propRow["Column"]
                        propType = propRow["Type"]
                        if not propType:
                            propLabel = wbRow[f"{propVarname}Label"]
                        else:
                            propLabel = ""
                        if propType == "extid":
                            propUrl = wbRow[f"{propVarname}Url"]
                        elif propType == "url":
                            propUrl = wbRow[f"{propVarname}"]
                        else:
                            propUrl = ""
                        # Linked Or
                        if (
                            type(value) == str
                            and value.startswith("http://www.wikidata.org/entity/")
                            and f"{propVarname}Label" in wbRow
                        ):
                            propUrl = value
                            propLabel = wbRow[f"{propVarname}Label"]
                            value = propLabel
                        if column in lodRow:
                            self.checkCell(
                                viewLodRow,
                                column,
                                value,
                                propVarname,
                                propType,
                                propLabel,
                                propUrl,
                            )

    def getColumnTypeAndVarname(self, propName: str):
        """
        slightly modified getter to account for "item" special case

        Args:
            propName(str): the name of the property
        """
        if propName == "item":
            column = "item"
            propType = "item"
            varName = "item"
        else:
            column, propType, varName = self.wbQuery.getColumnTypeAndVarname(propName)
        return column, propType, varName

    def getHtmlColumns(self):
        """
        get the columns that have html content(links)
        """
        htmlColumns = [0]
        # loop over columns of list of dicts
        wbQuery = self.wbQuery
        if wbQuery is not None:
            for columnIndex, column in enumerate(self.wdgrid.columns):
                # check whether there is metadata for the column
                if column in wbQuery.propertiesByColumn:
                    propRow = wbQuery.propertiesByColumn[column]
                    propType = propRow["Type"]
                    if not propType or propType == "extid" or propType == "url":
                        htmlColumns.append(columnIndex)
        return htmlColumns

    def add_record_to_wikidata(
        self,
        record: dict,
        row_index: int,
        write: bool = False,
        ignore_errors: bool = False,
    ):
        """
        add a record to wikidata when the row has been selected

        Args:
            record(dict): the data to be added to wikidata
            row_index(int): the row index
            write(bool): if True actually write data
            ignore_errors(bool): if True ignore errors that might occur
        """
        if not "label" in record:
            raise Exception(f"label missing in {record}")
        label = record["label"]
        mapDict = self.wbQuery.propertiesById
        rowData = record.copy()
        # remove index
        if self.wdgrid.lodRowIndex_column in rowData:
            rowData.pop(self.wdgrid.lodRowIndex_column)
        qid, errors = self.wdgrid.wd.addDict(
            rowData, mapDict, write=write, ignoreErrors=ignore_errors
        )
        if qid is not None:
            # set item link
            link = self.wdgrid.createLink(
                f"https://www.wikidata.org/wiki/{qid}", f"{label}"
            )
            self.wdgrid.viewLod[row_index]["item"] = link
            self.wdgrid.agGrid.load_lod(self.wdgrid.viewLod)
            self.wdgrid.refreshGridSettings()
        # @TODO improve error handling
        if len(errors) > 0:
            self.wdgrid.app.errors.text = errors
            print(errors)
        # dry run and error display
        if not write or len(errors) > 0:
            prettyData = pprint.pformat(rowData)
            html = Markup(f"<pre>{prettyData}</pre>")
            # create an alert
            alert = Alert(text="", a=self.wdgrid.app.rowA)
            alert.contentDiv.inner_html = html

    def handle_row_selected(
        self,
        record: dict,
        row_index: int,
        write: bool = False,
        ignore_errors: bool = False,
    ):
        record = record.copy()
        record = {k: v if v != "" else None for k, v in record.items()}
        prop_maps = self.get_property_mappings()
        item_prop = PropertyMapping.get_item_mapping(prop_maps)
        item_id = record.get(item_prop.column, None)
        # sanitize record
        for key in [item_prop.column, "lodRowIndex"]:
            if key in record:
                record.pop(key)
        # limit record to properties that are synced with wikidata
        prop_by_col = [pm.column for pm in prop_maps if not pm.is_item_itself()]
        prop_by_col.extend(["label", "description"])
        record = {k: v for k, v in record.items() if k in prop_by_col}
        # fetch record from wikidata
        wd_record = dict()
        if item_id is not None and item_id != "":
            wd_record = self.wdgrid.wd.get_record(item_id, prop_maps)
            wd_record = {k: v for k, v in wd_record.items() if k in prop_by_col}
        # normalize records
        record = self.wdgrid.wd.normalize_records(record, prop_maps)
        wd_record = self.wdgrid.wd.normalize_records(wd_record, prop_maps)

        cr = ComparisonRecord(self.wdgrid.source, record, "wikidata", wd_record)
        # save item specific attrs
        cr.lodRowIndex = row_index
        cr.qid = item_id
        # show SyncDialog
        self.wdgrid.sync_dialog_div.delete_components()
        sync_dialog = SyncDialog(
            cr,
            sync_callback=self._sync_callback,
            value_enhancement_callback=self.enhance_value_display,
            a=self.wdgrid.sync_dialog_div,
        )

    def _sync_callback(self, sync_request: SyncRequest):
        """
        Handle the given sync request
        """
        write = not self.wdgrid.dryRun
        lodRowIndex = getattr(sync_request.data, "lodRowIndex", None)
        update_sources = []
        if sync_request.action in [SyncAction.SYNC, SyncAction.RIGHT_SYNC]:
            update_sources.append(sync_request.data.right_source_name)
        if sync_request.action in [SyncAction.SYNC, SyncAction.LEFT_SYNC]:
            update_sources.append(sync_request.data.left_source_name)
        for source in update_sources:
            record = sync_request.data.get_update_record_of(source)
            for key, value in record.items():
                if isinstance(value, WikidataItem):
                    record[key] = value.qid
            for key in ["label", "desc"]:
                if key not in record and sync_request.data.comparison_data.get(
                    key, None
                ):
                    record[key] = sync_request.data.comparison_data.get(key).left_value
            prop_maps = self.get_property_mappings()
            item_pm = PropertyMapping.get_item_mapping(prop_maps)
            record[item_pm.column] = getattr(sync_request.data, "qid")
            if source == "wikidata":
                try:
                    self.add_record_to_wikidata(
                        record=record,
                        row_index=lodRowIndex,
                        write=write,
                        ignore_errors=self.wdgrid.ignoreErrors,
                    )
                    self.wdgrid.sync_dialog_div.delete_components()
                except Exception as ex:
                    self.app.handleException(ex)
            else:
                self.app.handleException(
                    Exception(f"Updating of source {source} is not supported")
                )

    def enhance_value_display(self, row: SyncDialogRow):
        """
        Enhances the displayed value
        """
        value_div_pairs = [
            (row.comparison_data.left_value, row.left_value_div),
            (row.comparison_data.right_value, row.right_value_div),
        ]
        for value_raw, div in value_div_pairs:
            values = value_raw if isinstance(value_raw, list) else [value_raw]
            for i, value in enumerate(values):
                if i > 0:
                    Br(a=div)
                if isinstance(value, WikidataItem):
                    div.text = ""
                    Link(a=div, href=value.get_url(), text=value.label)
                elif isinstance(value, str) and value.startswith("http"):
                    div.text = ""
                    Link(a=div, href=value, text=value)

__init__(wdgrid, entityName, pk, sparql, debug=False)

constructor

Parameters:

Name Type Description Default
wdgrid(WikiDataGrid)

the wikidata grid to use

required
entityName str

name of the sheet

required
pk str

primary key

required
sparql(SPARQL)

the sparql endpoint access to use

required
debug(bool)

if True show debug information

required
Source code in onlinespreadsheet/wdgrid.py
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
def __init__(
    self,
    wdgrid: WikidataGrid,
    entityName: str,
    pk: str,
    sparql: SPARQL,
    debug: bool = False,
):
    """
    constructor

    Args:
        wdgrid(WikiDataGrid): the wikidata grid to use
        entityName: name of the sheet
        pk: primary key
        sparql(SPARQL): the sparql endpoint access to use
        debug(bool): if True show debug information
    """
    self.wdgrid = wdgrid
    self.app = wdgrid.app
    self.entityName = entityName
    self.pk = pk
    self.sparql = sparql
    self.debug = debug
    self.wdgrid.additional_reload_callback = self.setup_aggrid_post_reload
    self.wdgrid.row_selected_callback = self.handle_row_selected
    self.wbQuery = None

addHtmlMarkupToViewLod(viewLod)

add HtmlMarkup to the view list of dicts viewLod(list): a list of dict for the mark result

Source code in onlinespreadsheet/wdgrid.py
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
def addHtmlMarkupToViewLod(self, viewLod: list):
    """
    add HtmlMarkup to the view list of dicts
    viewLod(list): a list of dict for the mark result
    """
    # now check the wikibase rows retrieved in comparison
    # to the current view List of Dicts Markup
    for wbRow in self.wbRows:
        # get the primary key value
        pkValue = wbRow[self.pkProp]
        pkValue = re.sub(
            r"http://www.wikidata.org/entity/(Q[0-9]+)", r"\1", pkValue
        )
        # if we have the primary key then we mark the whole row
        if pkValue in self.itemsByPk:
            if self.debug:
                print(f"adding html markup for {pkValue}")
            # https://stackoverflow.com/questions/14538885/how-to-get-the-index-with-the-key-in-a-dictionary
            lodRow = self.itemsByPk[pkValue]
            rowIndex = lodRow[self.wdgrid.lodRowIndex_column]
            viewLodRow = viewLod[rowIndex]
            itemLink = self.wdgrid.createLink(wbRow["item"], wbRow["itemLabel"])
            viewLodRow["item"] = itemLink
            itemDescription = wbRow.get("itemDescription", "")
            self.checkCell(
                viewLodRow,
                "description",
                itemDescription,
                propVarname="itemDescription",
                propType="string",
                propLabel="",
            )
            # loop over the result items
            for propVarname, value in wbRow.items():
                # remap the property variable name to the original property description
                if propVarname in self.wbQuery.propertiesByVarname:
                    propRow = self.wbQuery.propertiesByVarname[propVarname]
                    column = propRow["Column"]
                    propType = propRow["Type"]
                    if not propType:
                        propLabel = wbRow[f"{propVarname}Label"]
                    else:
                        propLabel = ""
                    if propType == "extid":
                        propUrl = wbRow[f"{propVarname}Url"]
                    elif propType == "url":
                        propUrl = wbRow[f"{propVarname}"]
                    else:
                        propUrl = ""
                    # Linked Or
                    if (
                        type(value) == str
                        and value.startswith("http://www.wikidata.org/entity/")
                        and f"{propVarname}Label" in wbRow
                    ):
                        propUrl = value
                        propLabel = wbRow[f"{propVarname}Label"]
                        value = propLabel
                    if column in lodRow:
                        self.checkCell(
                            viewLodRow,
                            column,
                            value,
                            propVarname,
                            propType,
                            propLabel,
                            propUrl,
                        )

add_record_to_wikidata(record, row_index, write=False, ignore_errors=False)

add a record to wikidata when the row has been selected

Parameters:

Name Type Description Default
record(dict)

the data to be added to wikidata

required
row_index(int)

the row index

required
write(bool)

if True actually write data

required
ignore_errors(bool)

if True ignore errors that might occur

required
Source code in onlinespreadsheet/wdgrid.py
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
def add_record_to_wikidata(
    self,
    record: dict,
    row_index: int,
    write: bool = False,
    ignore_errors: bool = False,
):
    """
    add a record to wikidata when the row has been selected

    Args:
        record(dict): the data to be added to wikidata
        row_index(int): the row index
        write(bool): if True actually write data
        ignore_errors(bool): if True ignore errors that might occur
    """
    if not "label" in record:
        raise Exception(f"label missing in {record}")
    label = record["label"]
    mapDict = self.wbQuery.propertiesById
    rowData = record.copy()
    # remove index
    if self.wdgrid.lodRowIndex_column in rowData:
        rowData.pop(self.wdgrid.lodRowIndex_column)
    qid, errors = self.wdgrid.wd.addDict(
        rowData, mapDict, write=write, ignoreErrors=ignore_errors
    )
    if qid is not None:
        # set item link
        link = self.wdgrid.createLink(
            f"https://www.wikidata.org/wiki/{qid}", f"{label}"
        )
        self.wdgrid.viewLod[row_index]["item"] = link
        self.wdgrid.agGrid.load_lod(self.wdgrid.viewLod)
        self.wdgrid.refreshGridSettings()
    # @TODO improve error handling
    if len(errors) > 0:
        self.wdgrid.app.errors.text = errors
        print(errors)
    # dry run and error display
    if not write or len(errors) > 0:
        prettyData = pprint.pformat(rowData)
        html = Markup(f"<pre>{prettyData}</pre>")
        # create an alert
        alert = Alert(text="", a=self.wdgrid.app.rowA)
        alert.contentDiv.inner_html = html

checkCell(viewLodRow, column, value, propVarname, propType, propLabel, propUrl=None)

update the cell value for the given

Parameters:

Name Type Description Default
viewLodRow(dict)

the row to modify

required
value(object)

the value to set for the cell

required
propVarName(str)

the name of the property Variable set in the SPARQL statement

required
propType(str)

the abbreviation for the property Type

required
propLabel(str)

the propertyLabel (if any)

required
propUrl(str)

the propertyUrl (if any)

required
Source code in onlinespreadsheet/wdgrid.py
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
def checkCell(
    self,
    viewLodRow,
    column,
    value,
    propVarname,
    propType,
    propLabel,
    propUrl: str = None,
):
    """
    update the cell value for the given

    Args:
        viewLodRow(dict): the row to modify
        value(object): the value to set for the cell
        propVarName(str): the name of the property Variable set in the SPARQL statement
        propType(str): the abbreviation for the property Type
        propLabel(str): the propertyLabel (if any)
        propUrl(str): the propertyUrl (if any)
    """
    cellValue = viewLodRow[column]
    valueType = type(value)
    print(
        f"{column}({propVarname})={value}({propLabel}:{propUrl}:{valueType})⮂{cellValue}"
    )
    # overwrite empty cells
    overwrite = not cellValue
    if cellValue:
        # overwrite values with links
        if propUrl and cellValue == value:
            overwrite = True
    if overwrite and value:
        doadd = True
        # create links for item  properties
        if not propType:
            value = self.wdgrid.createLink(value, propLabel)
        elif propType == "extid" or propType == "url":
            value = self.wdgrid.createLink(propUrl, value)
        if valueType == str:
            pass
        elif valueType == datetime.datetime:
            value = value.strftime("%Y-%m-%d")
        else:
            doadd = False
            print(f"{valueType} not added")
        if doadd:
            viewLodRow[column] = value

enhance_value_display(row)

Enhances the displayed value

Source code in onlinespreadsheet/wdgrid.py
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
def enhance_value_display(self, row: SyncDialogRow):
    """
    Enhances the displayed value
    """
    value_div_pairs = [
        (row.comparison_data.left_value, row.left_value_div),
        (row.comparison_data.right_value, row.right_value_div),
    ]
    for value_raw, div in value_div_pairs:
        values = value_raw if isinstance(value_raw, list) else [value_raw]
        for i, value in enumerate(values):
            if i > 0:
                Br(a=div)
            if isinstance(value, WikidataItem):
                div.text = ""
                Link(a=div, href=value.get_url(), text=value.label)
            elif isinstance(value, str) and value.startswith("http"):
                div.text = ""
                Link(a=div, href=value, text=value)

getColumnTypeAndVarname(propName)

slightly modified getter to account for "item" special case

Parameters:

Name Type Description Default
propName(str)

the name of the property

required
Source code in onlinespreadsheet/wdgrid.py
557
558
559
560
561
562
563
564
565
566
567
568
569
570
def getColumnTypeAndVarname(self, propName: str):
    """
    slightly modified getter to account for "item" special case

    Args:
        propName(str): the name of the property
    """
    if propName == "item":
        column = "item"
        propType = "item"
        varName = "item"
    else:
        column, propType, varName = self.wbQuery.getColumnTypeAndVarname(propName)
    return column, propType, varName

getHtmlColumns()

get the columns that have html content(links)

Source code in onlinespreadsheet/wdgrid.py
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
def getHtmlColumns(self):
    """
    get the columns that have html content(links)
    """
    htmlColumns = [0]
    # loop over columns of list of dicts
    wbQuery = self.wbQuery
    if wbQuery is not None:
        for columnIndex, column in enumerate(self.wdgrid.columns):
            # check whether there is metadata for the column
            if column in wbQuery.propertiesByColumn:
                propRow = wbQuery.propertiesByColumn[column]
                propType = propRow["Type"]
                if not propType or propType == "extid" or propType == "url":
                    htmlColumns.append(columnIndex)
    return htmlColumns

onChangePk(msg) async

handle selection of a different primary key

Parameters:

Name Type Description Default
msg(dict)

the justpy message

required
Source code in onlinespreadsheet/wdgrid.py
382
383
384
385
386
387
388
389
390
391
392
393
394
395
async def onChangePk(self, msg: dict):
    """
    handle selection of a different primary key

    Args:
        msg(dict): the justpy message
    """
    self.pk = msg.value
    if self.debug:
        print(f"changed primary key of {self.entityName} to {self.pk}")
    try:
        await self.wdgrid.reload()
    except Exception as ex:
        self.app.handleException(ex)

onCheckWikidata(msg=None)

check clicked - check the wikidata content

Parameters:

Name Type Description Default
msg(dict)

the justpy message

required
Source code in onlinespreadsheet/wdgrid.py
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
def onCheckWikidata(self, msg=None):
    """
    check clicked - check the wikidata content

    Args:
        msg(dict): the justpy message
    """
    if self.debug:
        print(msg)
    try:
        self.app.clearErrors()
        self.loadItems()
        # prepare syncing the table results with the wikibase query result
        # query based on table content
        self.query(self.sparql)
        # get the view copy to insert result as html statements
        viewLod = self.wdgrid.viewLod
        self.addHtmlMarkupToViewLod(viewLod)
        # reload the AG Grid with the html enriched content
        self.wdgrid.reloadAgGrid(viewLod)
    except Exception as ex:
        self.app.handleException(ex)

query(sparql)

query the wikibase instance based on the list of dict

Source code in onlinespreadsheet/wdgrid.py
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
def query(self, sparql):
    """
    query the wikibase instance based on the list of dict
    """
    lang = "en" if self.pkType == "text" else None
    valuesClause = self.wbQuery.getValuesClause(
        self.itemsByPk.keys(), self.pkProp, propType=self.pkType, lang=lang
    )
    self.sparqlQuery = self.wbQuery.asSparql(
        filterClause=valuesClause,
        orderClause=f"ORDER BY ?{self.pkProp}",
        pk=self.pk,
    )
    if self.debug:
        print(self.sparqlQuery)
    self.wbRows = sparql.queryAsListOfDicts(self.sparqlQuery)
    if self.debug:
        pprint.pprint(self.wbRows)

setup(a, header)

initialize my components

Parameters:

Name Type Description Default
a(HtmlComponent)

the parent component

required
header(HtmlComponent)

the header for the primary key selector

required
Source code in onlinespreadsheet/wdgrid.py
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
def setup(self, a, header):
    """
    initialize my components

    Args:
        a(HtmlComponent): the parent component
        header(HtmlComponent): the header for the primary key selector

    """
    selectorClasses = "w-32 m-2 p-2 bg-white"
    self.toolbar = self.app.jp.QToolbar(a=a, classes="flex flex-row gap-2")
    # for icons see  https://quasar.dev/vue-components/icon
    # see justpy/templates/local/materialdesignicons/iconfont/codepoints for available icons
    self.reloadButton = IconButton(
        a=self.toolbar,
        text="",
        title="reload",
        iconName="refresh-circle",
        click=self.wdgrid.reload,
        classes="btn btn-primary btn-sm col-1",
    )
    self.checkButton = IconButton(
        a=self.toolbar,
        text="",
        title="check",
        iconName="check",
        click=self.onCheckWikidata,
        classes="btn btn-primary btn-sm col-1",
    )
    self.loginButton = IconButton(
        a=self.toolbar,
        title="login",
        iconName="login",
        text="",
        click=self.onLogin,
        classes="btn btn-primary btn-sm col-1",
    )
    self.passwordDialog = QPasswordDialog(a=self.app.wp)
    # selector for column/property
    self.pkSelect = self.app.jp.Select(
        classes=selectorClasses, a=header, value=self.pk, change=self.onChangePk
    )

setup_aggrid_post_reload()

setup the aggrid

Source code in onlinespreadsheet/wdgrid.py
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
def setup_aggrid_post_reload(self):
    """
    setup the aggrid
    """
    viewLod = self.wdgrid.viewLod
    self.wdgrid.agGrid.html_columns = self.getHtmlColumns()
    self.wdgrid.linkWikidataItems(viewLod)
    self.pkSelect.delete_components()
    self.pkSelect.add(self.app.jp.Option(value="item", text="item"))
    if self.wbQuery is not None:
        for propertyName, row in self.wbQuery.propertiesByName.items():
            columnName = row["Column"]
            if columnName:
                self.pkSelect.add(
                    self.app.jp.Option(value=propertyName, text=columnName)
                )

WikidataGrid

a grid with tabular data from wikidata to work with

Source code in onlinespreadsheet/wdgrid.py
 33
 34
 35
 36
 37
 38
 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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
236
237
238
239
240
241
242
243
244
245
246
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
class WikidataGrid:
    """
    a grid with tabular data from wikidata to work with
    """

    def __init__(
        self,
        solution: WebSolution,
        entityName: str,
        entityPluralName: typing.Optional[str],
        source: str,
        getLod: Callable,
        additional_reload_callback: typing.Union[Callable, None] = None,
        row_selected_callback: typing.Callable = None,
        lodRowIndex_column: str = "lodRowIndex",
        debug: bool = False,
    ):
        """
        constructor
        Args:
            solution(Websolution): the web solution context of this grid
            entityName(str): the name of the entity that this grid is for
            entityPluralName(str): the plural name of the entity type of items displayed in this grid
            source(str): the name of my source (where the data for this grid comes from)
            getLod(Callable): the function to get my list of dicts
            additional_reload_callback: Function to be called after fetching the new data and before updating aggrid
            lodRowIndex_column(str): the column/attribute to use for tracking the index in the lod
            debug(bool): if True show debug information
        """
        self.solution = solution
        self.lod_grid = None
        self.setEntityName(entityName, entityPluralName)
        self.lodRowIndex_column = lodRowIndex_column
        self.getLod = getLod
        self.additional_reload_callback = additional_reload_callback
        self.row_selected_callback = row_selected_callback
        self.source = source
        self.debug = debug
        self.dryRun = True
        self.ignoreErrors = False
        # @TODO make endpoint configurable
        self.wd = Wikidata("https://www.wikidata.org", debug=True)

    def setEntityName(self, entityName: str, entityPluralName: str = None):
        self.entityName = entityName
        self.entityPluralName = (
            entityPluralName if entityPluralName is not None else entityName
        )

    def setup(self, a):
        """
        setup the Wikidata grid nicegui components
        """
        if getattr(self, "container", None) is not None:
            self.container.delete_components()
        self.container = Div(a=a)
        self.controls_div = Div(
            a=self.container, classes="flex flex-row items-center m-2 p-2 gap-2"
        )
        self.alert_div = Div(a=self.container)
        self.dryRunButton = Switch(
            a=self.controls_div,
            labelText="dry run",
            checked=True,
            disable=True,
            on_input=self.onChangeDryRun,
        )
        self.ignoreErrorsButton = Switch(
            a=self.controls_div,
            labelText="ignore errors",
            checked=False,
            on_input=self.onChangeIgnoreErrors,
        )
        self.addFitSizeButton()
        self.assureAgGrid()
        self.sync_dialog_div = Div(a=self.alert_div, classes="container")

    def setViewLod(self, lod: list, nonValue: str = "-"):
        """
        add lodRowIndex column to list of dicts and
        use a copy of the given list of dicts for the view
        modify datetime columns to avoid problems with justpy

        Args:
            lod(list): the list of dicts
            nonValue(str): the string to use for "None" values
        """
        for index, row in enumerate(lod):
            row[self.lodRowIndex_column] = index
        self.viewLod = copy.deepcopy(lod)
        # fix non values
        for record in self.viewLod:
            for key in list(record):
                value = record[key]
                if value is None:
                    record[key] = nonValue
                vtype = type(value)
                # fix datetime entries
                if vtype is datetime.datetime:
                    value = str(value)
                    record[key] = value
        pass

    def reloadAgGrid(self, viewLod: list, showLimit: int = 10):
        """
        reload the agGrid with the given list of Dicts

        Args:
            viewLod(list): the list of dicts for the current view
            showLimit: number of rows to print when debugging
        """
        if self.agGrid is None:
            return
        self.agGrid.load_lod(viewLod)
        if self.debug:
            pprint.pprint(viewLod[:showLimit])
        self.refreshGridSettings()

    def setDefaultColDef(self, agGrid):
        """
        set the default column definitions
        Args:
            agGrid: agGrid to set the column definitions for
        """
        defaultColDef = agGrid.options.defaultColDef
        defaultColDef.resizable = True
        defaultColDef.sortable = True
        # https://www.ag-grid.com/javascript-data-grid/grid-size/
        defaultColDef.wrapText = True
        defaultColDef.autoHeight = True

    def refreshGridSettings(self):
        """
        refresh the ag grid settings e.g. enable the row selection event handler
        enable row selection event handler
        """
        self.agGrid.on("rowSelected", self.onRowSelected)
        self.agGrid.options.columnDefs[0].checkboxSelection = True

    def linkWikidataItems(self, viewLod, itemColumn: str = "item"):
        """
        link the wikidata entries in the given item column if containing Q values

        Args:
            viewLod(list): the list of dicts for the view
            itemColumn(str): the name of the column to handle
        """
        for row in viewLod:
            if itemColumn in row:
                item = row[itemColumn]
                if re.match(r"Q[0-9]+", item):
                    itemLink = self.createLink(
                        f"https://www.wikidata.org/wiki/{item}", item
                    )
                    row[itemColumn] = itemLink

    async def reload(self, _msg=None, clearErrors=True):
        """
        reload the table content via my getLod function

        Args:
            clearErrors(bool): if True clear Errors before reloading
        """
        try:
            if clearErrors:
                self.app.clearErrors()
            msg = (
                f"reload called ... fetching {self.entityPluralName} from {self.source}"
            )
            if self.debug:
                print(msg)
            _alert = Alert(a=self.alert_div, text=msg)
            await self.app.wp.update()
            items = self.getLod()
            self.setLod(items)
            _alert.delete_alert(None)
            msg = f"found {len(items)} {self.entityPluralName}"
            _alert = Alert(a=self.alert_div, text=msg)
            await self.app.wp.update()
            if self.debug:
                print(json.dumps(self.viewLod, indent=2, default=str))
            if callable(self.additional_reload_callback):
                self.additional_reload_callback()
            self.reloadAgGrid(self.viewLod)
            await self.app.wp.update()
            await asyncio.sleep(0.2)
            await self.agGrid.run_api("sizeColumnsToFit()", self.app.wp)
        except Exception as ex:
            _error = Span(a=_alert, text=f"Error: {str(ex)}", style="color:red")
            self.app.handleException(ex)

    def onChangeDryRun(self, msg: dict):
        """
        handle change of DryRun setting

        Args:
            msg(dict): the justpy message
        """
        self.dryRun = msg.value

    def onChangeIgnoreErrors(self, msg: dict):
        """
        handle change of IgnoreErrors setting

        Args:
            msg(dict): the justpy message
        """
        self.ignoreErrors = msg.value

    async def onRowSelected(self, msg):
        """
        row selection event handler

        Args:
            msg(dict): row selection information
        """
        if self.debug:
            print(msg)
        self.app.clearErrors()
        if msg.selected:
            self.rowSelected = msg.rowIndex
            # check whether a lodeRowIndex Index is available
            lodByRowIndex, _dup = LOD.getLookup(self.lod, self.lodRowIndex_column)
            if len(lodByRowIndex) == len(self.lod):
                lodRowIndex = msg.data[self.lodRowIndex_column]
            else:
                lodRowIndex = self.rowSelected
            record = self.lod[lodRowIndex]
            write = not self.dryRun
            # show spinner
            webpage: WebPage = msg.page
            self.sync_dialog_div.delete_components()
            Spinner(a=self.sync_dialog_div, classes="container")
            await webpage.update()
            try:
                if callable(self.row_selected_callback):
                    self.row_selected_callback(
                        record=record,
                        row_index=lodRowIndex,
                        write=write,
                        ignore_errors=self.ignoreErrors,
                    )
            except Exception as ex:
                self.sync_dialog_div.delete_components()
                self.app.handleException(ex)

__init__(solution, entityName, entityPluralName, source, getLod, additional_reload_callback=None, row_selected_callback=None, lodRowIndex_column='lodRowIndex', debug=False)

constructor Args: solution(Websolution): the web solution context of this grid entityName(str): the name of the entity that this grid is for entityPluralName(str): the plural name of the entity type of items displayed in this grid source(str): the name of my source (where the data for this grid comes from) getLod(Callable): the function to get my list of dicts additional_reload_callback: Function to be called after fetching the new data and before updating aggrid lodRowIndex_column(str): the column/attribute to use for tracking the index in the lod debug(bool): if True show debug information

Source code in onlinespreadsheet/wdgrid.py
38
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
def __init__(
    self,
    solution: WebSolution,
    entityName: str,
    entityPluralName: typing.Optional[str],
    source: str,
    getLod: Callable,
    additional_reload_callback: typing.Union[Callable, None] = None,
    row_selected_callback: typing.Callable = None,
    lodRowIndex_column: str = "lodRowIndex",
    debug: bool = False,
):
    """
    constructor
    Args:
        solution(Websolution): the web solution context of this grid
        entityName(str): the name of the entity that this grid is for
        entityPluralName(str): the plural name of the entity type of items displayed in this grid
        source(str): the name of my source (where the data for this grid comes from)
        getLod(Callable): the function to get my list of dicts
        additional_reload_callback: Function to be called after fetching the new data and before updating aggrid
        lodRowIndex_column(str): the column/attribute to use for tracking the index in the lod
        debug(bool): if True show debug information
    """
    self.solution = solution
    self.lod_grid = None
    self.setEntityName(entityName, entityPluralName)
    self.lodRowIndex_column = lodRowIndex_column
    self.getLod = getLod
    self.additional_reload_callback = additional_reload_callback
    self.row_selected_callback = row_selected_callback
    self.source = source
    self.debug = debug
    self.dryRun = True
    self.ignoreErrors = False
    # @TODO make endpoint configurable
    self.wd = Wikidata("https://www.wikidata.org", debug=True)

linkWikidataItems(viewLod, itemColumn='item')

link the wikidata entries in the given item column if containing Q values

Parameters:

Name Type Description Default
viewLod(list)

the list of dicts for the view

required
itemColumn(str)

the name of the column to handle

required
Source code in onlinespreadsheet/wdgrid.py
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def linkWikidataItems(self, viewLod, itemColumn: str = "item"):
    """
    link the wikidata entries in the given item column if containing Q values

    Args:
        viewLod(list): the list of dicts for the view
        itemColumn(str): the name of the column to handle
    """
    for row in viewLod:
        if itemColumn in row:
            item = row[itemColumn]
            if re.match(r"Q[0-9]+", item):
                itemLink = self.createLink(
                    f"https://www.wikidata.org/wiki/{item}", item
                )
                row[itemColumn] = itemLink

onChangeDryRun(msg)

handle change of DryRun setting

Parameters:

Name Type Description Default
msg(dict)

the justpy message

required
Source code in onlinespreadsheet/wdgrid.py
224
225
226
227
228
229
230
231
def onChangeDryRun(self, msg: dict):
    """
    handle change of DryRun setting

    Args:
        msg(dict): the justpy message
    """
    self.dryRun = msg.value

onChangeIgnoreErrors(msg)

handle change of IgnoreErrors setting

Parameters:

Name Type Description Default
msg(dict)

the justpy message

required
Source code in onlinespreadsheet/wdgrid.py
233
234
235
236
237
238
239
240
def onChangeIgnoreErrors(self, msg: dict):
    """
    handle change of IgnoreErrors setting

    Args:
        msg(dict): the justpy message
    """
    self.ignoreErrors = msg.value

onRowSelected(msg) async

row selection event handler

Parameters:

Name Type Description Default
msg(dict)

row selection information

required
Source code in onlinespreadsheet/wdgrid.py
242
243
244
245
246
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
async def onRowSelected(self, msg):
    """
    row selection event handler

    Args:
        msg(dict): row selection information
    """
    if self.debug:
        print(msg)
    self.app.clearErrors()
    if msg.selected:
        self.rowSelected = msg.rowIndex
        # check whether a lodeRowIndex Index is available
        lodByRowIndex, _dup = LOD.getLookup(self.lod, self.lodRowIndex_column)
        if len(lodByRowIndex) == len(self.lod):
            lodRowIndex = msg.data[self.lodRowIndex_column]
        else:
            lodRowIndex = self.rowSelected
        record = self.lod[lodRowIndex]
        write = not self.dryRun
        # show spinner
        webpage: WebPage = msg.page
        self.sync_dialog_div.delete_components()
        Spinner(a=self.sync_dialog_div, classes="container")
        await webpage.update()
        try:
            if callable(self.row_selected_callback):
                self.row_selected_callback(
                    record=record,
                    row_index=lodRowIndex,
                    write=write,
                    ignore_errors=self.ignoreErrors,
                )
        except Exception as ex:
            self.sync_dialog_div.delete_components()
            self.app.handleException(ex)

refreshGridSettings()

refresh the ag grid settings e.g. enable the row selection event handler enable row selection event handler

Source code in onlinespreadsheet/wdgrid.py
164
165
166
167
168
169
170
def refreshGridSettings(self):
    """
    refresh the ag grid settings e.g. enable the row selection event handler
    enable row selection event handler
    """
    self.agGrid.on("rowSelected", self.onRowSelected)
    self.agGrid.options.columnDefs[0].checkboxSelection = True

reload(_msg=None, clearErrors=True) async

reload the table content via my getLod function

Parameters:

Name Type Description Default
clearErrors(bool)

if True clear Errors before reloading

required
Source code in onlinespreadsheet/wdgrid.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
async def reload(self, _msg=None, clearErrors=True):
    """
    reload the table content via my getLod function

    Args:
        clearErrors(bool): if True clear Errors before reloading
    """
    try:
        if clearErrors:
            self.app.clearErrors()
        msg = (
            f"reload called ... fetching {self.entityPluralName} from {self.source}"
        )
        if self.debug:
            print(msg)
        _alert = Alert(a=self.alert_div, text=msg)
        await self.app.wp.update()
        items = self.getLod()
        self.setLod(items)
        _alert.delete_alert(None)
        msg = f"found {len(items)} {self.entityPluralName}"
        _alert = Alert(a=self.alert_div, text=msg)
        await self.app.wp.update()
        if self.debug:
            print(json.dumps(self.viewLod, indent=2, default=str))
        if callable(self.additional_reload_callback):
            self.additional_reload_callback()
        self.reloadAgGrid(self.viewLod)
        await self.app.wp.update()
        await asyncio.sleep(0.2)
        await self.agGrid.run_api("sizeColumnsToFit()", self.app.wp)
    except Exception as ex:
        _error = Span(a=_alert, text=f"Error: {str(ex)}", style="color:red")
        self.app.handleException(ex)

reloadAgGrid(viewLod, showLimit=10)

reload the agGrid with the given list of Dicts

Parameters:

Name Type Description Default
viewLod(list)

the list of dicts for the current view

required
showLimit int

number of rows to print when debugging

10
Source code in onlinespreadsheet/wdgrid.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def reloadAgGrid(self, viewLod: list, showLimit: int = 10):
    """
    reload the agGrid with the given list of Dicts

    Args:
        viewLod(list): the list of dicts for the current view
        showLimit: number of rows to print when debugging
    """
    if self.agGrid is None:
        return
    self.agGrid.load_lod(viewLod)
    if self.debug:
        pprint.pprint(viewLod[:showLimit])
    self.refreshGridSettings()

setDefaultColDef(agGrid)

set the default column definitions Args: agGrid: agGrid to set the column definitions for

Source code in onlinespreadsheet/wdgrid.py
151
152
153
154
155
156
157
158
159
160
161
162
def setDefaultColDef(self, agGrid):
    """
    set the default column definitions
    Args:
        agGrid: agGrid to set the column definitions for
    """
    defaultColDef = agGrid.options.defaultColDef
    defaultColDef.resizable = True
    defaultColDef.sortable = True
    # https://www.ag-grid.com/javascript-data-grid/grid-size/
    defaultColDef.wrapText = True
    defaultColDef.autoHeight = True

setViewLod(lod, nonValue='-')

add lodRowIndex column to list of dicts and use a copy of the given list of dicts for the view modify datetime columns to avoid problems with justpy

Parameters:

Name Type Description Default
lod(list)

the list of dicts

required
nonValue(str)

the string to use for "None" values

required
Source code in onlinespreadsheet/wdgrid.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def setViewLod(self, lod: list, nonValue: str = "-"):
    """
    add lodRowIndex column to list of dicts and
    use a copy of the given list of dicts for the view
    modify datetime columns to avoid problems with justpy

    Args:
        lod(list): the list of dicts
        nonValue(str): the string to use for "None" values
    """
    for index, row in enumerate(lod):
        row[self.lodRowIndex_column] = index
    self.viewLod = copy.deepcopy(lod)
    # fix non values
    for record in self.viewLod:
        for key in list(record):
            value = record[key]
            if value is None:
                record[key] = nonValue
            vtype = type(value)
            # fix datetime entries
            if vtype is datetime.datetime:
                value = str(value)
                record[key] = value
    pass

setup(a)

setup the Wikidata grid nicegui components

Source code in onlinespreadsheet/wdgrid.py
 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
def setup(self, a):
    """
    setup the Wikidata grid nicegui components
    """
    if getattr(self, "container", None) is not None:
        self.container.delete_components()
    self.container = Div(a=a)
    self.controls_div = Div(
        a=self.container, classes="flex flex-row items-center m-2 p-2 gap-2"
    )
    self.alert_div = Div(a=self.container)
    self.dryRunButton = Switch(
        a=self.controls_div,
        labelText="dry run",
        checked=True,
        disable=True,
        on_input=self.onChangeDryRun,
    )
    self.ignoreErrorsButton = Switch(
        a=self.controls_div,
        labelText="ignore errors",
        checked=False,
        on_input=self.onChangeIgnoreErrors,
    )
    self.addFitSizeButton()
    self.assureAgGrid()
    self.sync_dialog_div = Div(a=self.alert_div, classes="container")