• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

askomics / flaskomics / 8143972779

04 Mar 2024 04:54PM UTC coverage: 83.219%. Remained the same
8143972779

Pull #449

github

web-flow
Merge 6d2bc1d30 into 91939bc97
Pull Request #449: Bump xml2js and parse-bmfont-xml

6283 of 7550 relevant lines covered (83.22%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.5
/askomics/libaskomics/SparqlQuery.py
1
import re
1✔
2
import textwrap
1✔
3

4
from askomics.libaskomics.Params import Params
1✔
5
from askomics.libaskomics.PrefixManager import PrefixManager
1✔
6
from askomics.libaskomics.SparqlQueryLauncher import SparqlQueryLauncher
1✔
7
from askomics.libaskomics.Utils import Utils
1✔
8

9
from collections import defaultdict
1✔
10

11

12
class SparqlQuery(Params):
1✔
13
    """Format a sparql query
14

15
    Attributes
16
    ----------
17
    private_graphs : list
18
        all user private graph
19
    public_graphs : list
20
        all public graph
21
    """
22

23
    def __init__(self, app, session, json_query=None, get_graphs=False):
1✔
24
        """init
25

26
        Parameters
27
        ----------
28
        app : Flask
29
            Flask app
30
        session :
31
            AskOmics session
32
        """
33
        Params.__init__(self, app, session)
1✔
34

35
        self.json = json_query
1✔
36
        self.sparql = None
1✔
37

38
        self.graphs = []
1✔
39
        self.endpoints = []
1✔
40
        self.remote_graphs = defaultdict(list)
1✔
41
        self.selects = []
1✔
42
        self.federated = False
1✔
43

44
        # local endpoint (for federated query engine)
45
        self.local_endpoint_f = self.settings.get('triplestore', 'endpoint')
1✔
46
        try:
1✔
47
            self.local_endpoint_f = self.settings.get('federation', 'local_endpoint')
1✔
48
        except Exception:
×
49
            pass
×
50
        # No need to call this twice if we need it later (sparql queries)
51
        if get_graphs:
1✔
52
            self.set_graphs_and_endpoints()
1✔
53

54
    def set_graphs(self, graphs):
1✔
55
        """Set graphs
56

57
        Parameters
58
        ----------
59
        graphs : list
60
            graphs
61
        """
62
        self.graphs = graphs
1✔
63

64
    def set_endpoints(self, endpoints):
1✔
65
        """Set endpoints
66

67
        Parameters
68
        ----------
69
        endpoints : list
70
            Endpoints
71
        """
72
        self.endpoints = endpoints
1✔
73

74
    def set_remote_graph(self, remote_graphs):
1✔
75
        """Set endpoints
76

77
        Parameters
78
        ----------
79
        endpoints : list
80
            Endpoints
81
        """
82
        self.remote_graphs = remote_graphs
×
83

84
    def is_federated(self):
1✔
85
        """Return True if there is more than 1 endpoint
86

87
        Returns
88
        -------
89
        bool
90
            True or False
91
        """
92
        if len(self.endpoints) > 1:
1✔
93
            return True
×
94
        return False
1✔
95

96
    def replace_froms(self):
1✔
97
        """True if not federated and endpoint is local
98

99
        Returns
100
        -------
101
        bool
102
            True or False
103
        """
104
        if not self.is_federated():
1✔
105
            if self.endpoints == [self.local_endpoint_f]:
1✔
106
                return True
1✔
107

108
        return False
×
109

110
    def get_federated_froms(self):
1✔
111
        """Get @from string fir the federated query engine
112

113
        Returns
114
        -------
115
        string
116
            The from string
117
        """
118
        from_string = "@from <{}>".format(self.local_endpoint_f)
×
119
        for graph in self.graphs:
×
120
            from_string += " <{}>".format(graph)
×
121

122
        return from_string
×
123

124
    def get_froms(self):
1✔
125
        """Get FROM string
126

127
        Returns
128
        -------
129
        string
130
            FROM string
131
        """
132
        from_string = ''
1✔
133
        for graph in self.graphs:
1✔
134
            from_string += '\nFROM <{}>'.format(graph)
1✔
135

136
        return from_string
1✔
137

138
    def get_federated_line(self):
1✔
139
        """Get federtated line
140

141
        Returns
142
        -------
143
        string
144
            @federate <endpoint1> <endpoint1> ...
145
        """
146
        federated_string = '@federate '
×
147
        for endpoint in self.endpoints:
×
148
            federated_string += '<{}> '.format(endpoint)
×
149

150
        return federated_string
×
151

152
    def format_graph_name(self, graph):
1✔
153
        """Format graph name by removing base graph and timestamp
154

155
        Parameters
156
        ----------
157
        graph : string
158
            The graph name
159

160
        Returns
161
        -------
162
        string
163
            Formated graph name
164
        """
165
        regexp = r"{}:.*:".format(
1✔
166
            self.settings.get("triplestore", "default_graph"),
167
        )
168

169
        return "_".join(re.sub(regexp, "", graph).split("_")[:-1])
1✔
170

171
    def format_endpoint_name(self, endpoint):
1✔
172
        """Replace local url by "local triplestore"
173

174
        Parameters
175
        ----------
176
        endpoint : string
177
            The endpoint name
178

179
        Returns
180
        -------
181
        string
182
            Formated endpoint name
183
        """
184
        if endpoint in (self.settings.get("triplestore", "endpoint"), self.local_endpoint_f):
1✔
185
            return "local triplestore"
1✔
186
        return endpoint
×
187

188
    def get_graphs_and_endpoints(self, selected_graphs=[], selected_endpoints=[], all_selected=False):
1✔
189
        """get graphs and endpoints (uri and names)
190

191
        Returns
192
        -------
193
        list
194
            List of dict uri name
195
        """
196
        graphs = {}
1✔
197
        endpoints = {}
1✔
198
        for graph in self.graphs:
1✔
199
            graphs[graph] = {
1✔
200
                "uri": graph,
201
                "name": self.format_graph_name(graph),
202
                "selected": True if all_selected else True if graph in selected_graphs else False
203
            }
204
        for endpoint in self.endpoints:
1✔
205
            endpoints[endpoint] = {
1✔
206
                "uri": endpoint,
207
                "name": self.format_endpoint_name(endpoint),
208
                "selected": True if all_selected else True if endpoint in selected_endpoints else False
209
            }
210

211
        return (graphs, endpoints)
1✔
212

213
    def get_default_query(self):
1✔
214
        """Get the default query
215

216
        Returns
217
        -------
218
        str
219
            the default query
220
        """
221
        query = textwrap.dedent(
1✔
222
            '''
223
            SELECT DISTINCT ?s ?p ?o
224
            WHERE {
225
                ?s ?p ?o
226
            }
227
            '''
228
        )
229

230
        return query
1✔
231

232
    def prefix_query(self, query):
1✔
233
        """Add prefix and dedent a sparql query string
234

235
        Parameters
236
        ----------
237
        query : string
238
            The sparql query
239

240
        Returns
241
        -------
242
        string
243
            Formatted query
244
        """
245
        prefix_manager = PrefixManager(self.app, self.session)
1✔
246
        query = textwrap.dedent(query)
1✔
247
        return '{}{}'.format(
1✔
248
            prefix_manager.get_prefix(),
249
            query
250
        )
251

252
    def toggle_public(self, graph, public):
1✔
253
        """Change public status of data into the triplestore
254

255
        Parameters
256
        ----------
257
        graph : string
258
            Graph to update public status
259
        public : string
260
            true or false (string)
261
        """
262
        query = '''
1✔
263
        WITH GRAPH <{graph}>
264
        DELETE {{
265
            <{graph}> askomics:public ?public .
266
        }}
267
        INSERT {{
268
            <{graph}> askomics:public <{public}> .
269
        }}
270
        WHERE {{
271
            <{graph}> askomics:public ?public .
272
        }}
273
        '''.format(graph=graph, public=public)
274

275
        query_launcher = SparqlQueryLauncher(self.app, self.session)
1✔
276
        query_launcher.execute_query(self.prefix_query(query), is_update=True)
1✔
277

278
    def get_default_query_with_prefix(self):
1✔
279
        """Get default query with the prefixes
280

281
        Returns
282
        -------
283
        str
284
            default query with prefixes
285
        """
286
        prefix_manager = PrefixManager(self.app, self.session)
×
287
        return '{}{}'.format(
×
288
            prefix_manager.get_prefix(),
289
            self.get_default_query()
290
        )
291

292
    def format_query(self, query, limit=30, replace_froms=True, federated=False, ignore_single_tenant=True):
1✔
293
        """Format the Sparql query
294

295
        - remove all FROM
296
        - add FROM <graph> (public graph and user graph)
297
        - set a limit if not (or if its to big)
298

299
        Parameters
300
        ----------
301
        query : string
302
            sparql query to format
303
        limit : int, optional
304
            Description
305

306
        Returns
307
        -------
308
        string
309
            formatted sparql query
310
        """
311
        froms = ''
1✔
312

313
        if federated:
1✔
314
            federated_line = "" if self.settings.getboolean("askomics", "single_tenant", fallback=False) else "{}\n{}".format(self.get_federated_line(), self.get_federated_froms())
×
315
            federated_graphs_string = self.get_federated_remote_from_graphs()
×
316
        else:
317
            if replace_froms and (not self.settings.getboolean("askomics", "single_tenant", fallback=False)):
1✔
318
                froms = self.get_froms()
1✔
319

320
        query_lines = query.split('\n')
1✔
321

322
        new_query = ''
1✔
323

324
        for line in query_lines:
1✔
325
            # Remove all FROM and LIMIT and @federated
326
            if not line.upper().lstrip().startswith('FROM') and not line.upper().lstrip().startswith('LIMIT') and not line.upper().lstrip().startswith('@FEDERATE'):
1✔
327
                if line.upper().lstrip().startswith('SELECT') and federated:
1✔
328
                    new_query += "\n{}\n".format(federated_line)
×
329
                    new_query += "\n{}\n".format(federated_graphs_string)
×
330
                new_query += '\n{}'.format(line)
1✔
331
            # Add new FROM
332
            if line.upper().lstrip().startswith('SELECT'):
1✔
333
                self.selects = [i for i in line.split() if i.startswith('?')]
1✔
334
                new_query += froms
1✔
335
            # Compute the limit
336
            if line.upper().lstrip().startswith('LIMIT') and limit:
1✔
337
                given_lim = int(re.findall(r'\d+', line)[0])
×
338
                if given_lim < limit and given_lim != 0:
×
339
                    limit = given_lim
×
340
                continue
×
341

342
        # Add limit
343
        if limit:
1✔
344
            new_query += '\nLIMIT {}'.format(limit)
1✔
345

346
        return new_query
1✔
347

348
    def get_checked_asked_graphs(self, asked_graphs):
1✔
349
        """Check if asked graphs are present in public and private graphs
350

351
        Parameters
352
        ----------
353
        asked_graphs : list
354
            list of graphs asked by the user
355

356
        Returns
357
        -------
358
        list
359
            list of graphs asked by the user, in the public and private graphs
360
        """
361
        return Utils.intersect(asked_graphs, self.private_graphs + self.public_graphs)
×
362

363
    def get_froms_from_graphs(self, graphs):
1✔
364
        """Get FROM's form a list of graphs
365

366
        Parameters
367
        ----------
368
        graphs : list
369
            List of graphs
370

371
        Returns
372
        -------
373
        str
374
            from string
375
        """
376
        from_string = ''
1✔
377

378
        for graph in graphs:
1✔
379
            from_string += '\nFROM <{}>'.format(graph)
1✔
380

381
        return from_string
1✔
382

383
    def get_federated_froms_from_graphs(self, graphs):
1✔
384
        """Get @from string fir the federated query engine
385

386
        Returns
387
        -------
388
        string
389
            The from string
390
        """
391
        from_string = "@from <{}>".format(self.local_endpoint_f)
1✔
392
        for graph in graphs:
1✔
393
            from_string += " <{}>".format(graph)
1✔
394
        return from_string
1✔
395

396
    def get_federated_remote_from_graphs(self):
1✔
397
        """Get @from string fir the federated query engine
398

399
        Returns
400
        -------
401
        string
402
            The from string
403
        """
404
        from_string = ""
1✔
405

406
        for endpoint in self.endpoints:
1✔
407
            remote_graphs = self.remote_graphs.get(endpoint, [])
1✔
408
            if len(remote_graphs) == 1:
1✔
409
                from_string += "\n@graph <{}> <{}>".format(endpoint, remote_graphs[0])
×
410

411
        return from_string
1✔
412

413
    def get_endpoints_string(self):
1✔
414
        """get endpoint strngs for the federated query engine
415

416
        Returns
417
        -------
418
        string
419
            the endpoint string
420
        """
421
        endpoints_string = '@federate '
1✔
422
        for endpoint in self.endpoints:
1✔
423
            endpoints_string += "<{}> ".format(endpoint)
1✔
424

425
        return endpoints_string
1✔
426

427
    def set_graphs_and_endpoints(self, entities=None, graphs=None, endpoints=None, ontologies={}):
1✔
428
        """Get all public and private graphs containing the given entities
429

430
        Parameters
431
        ----------
432
        entities : list, optional
433
            list of entity uri
434
        """
435
        filter_entity_string = ''
1✔
436
        if entities:
1✔
437
            substr = ",".join(["<{}>".format(entity) for entity in entities])
1✔
438
            filter_entity_string = 'FILTER (?entity_uri IN( ' + substr + ' ))'
1✔
439

440
        filter_public_string = 'FILTER (?public = <true>)'
1✔
441
        if 'user' in self.session:
1✔
442
            filter_public_string = 'FILTER (?public = <true> || ?creator = <{}>)'.format(self.session["user"]["username"])
1✔
443

444
        query = '''
1✔
445
        SELECT DISTINCT ?graph ?endpoint ?entity_uri ?remote_graph
446
        WHERE {{
447
          ?graph_abstraction askomics:public ?public .
448
          ?graph_abstraction dc:creator ?creator .
449
          ?graph askomics:public ?public .
450
          ?graph dc:creator ?creator .
451
          GRAPH ?graph_abstraction {{
452
            ?graph_abstraction prov:atLocation ?endpoint .
453
            OPTIONAL {{?graph_abstraction dcat:Dataset ?remote_graph .}}
454
            ?entity_uri a ?askomics_type .
455
          }}
456
          GRAPH ?graph {{
457
            {{ [] a ?entity_uri . }}
458
            UNION
459
            {{ ?entity_uri a ?askomics_type . }}
460
          }}
461
          VALUES ?askomics_type {{askomics:entity askomics:ontology}}
462
          {}
463
          {}
464
        }}
465
        '''.format(filter_public_string, filter_entity_string)
466

467
        query_launcher = SparqlQueryLauncher(self.app, self.session)
1✔
468
        header, results = query_launcher.process_query(self.prefix_query(query))
1✔
469
        self.graphs = []
1✔
470
        self.endpoints = []
1✔
471
        self.remote_graphs = defaultdict(list)
1✔
472
        for res in results:
1✔
473
            if not graphs or res["graph"] in graphs:
1✔
474
                # Override with onto graph if matching uri
475
                if ontologies.get(res['entity_uri']):
1✔
476
                    graph = ontologies[res['entity_uri']]['graph']
×
477
                else:
478
                    graph = res["graph"]
1✔
479
                self.graphs.append(graph)
1✔
480

481
            # If local triplestore url is not accessible by federated query engine
482
            if res["endpoint"] == self.settings.get('triplestore', 'endpoint') and self.local_endpoint_f is not None:
1✔
483
                endpoint = self.local_endpoint_f
1✔
484
            else:
485
                endpoint = res["endpoint"]
×
486

487
            if not endpoints or endpoint in endpoints:
1✔
488
                self.endpoints.append(endpoint)
1✔
489
                if res.get("remote_graph"):
1✔
490
                    self.remote_graphs[endpoint].append(res.get("remote_graph"))
×
491

492
        self.endpoints = Utils.unique(self.endpoints)
1✔
493
        self.federated = len(self.endpoints) > 1
1✔
494

495
    def get_uri_parameters(self, uri, endpoints):
1✔
496
        """Get parameters for a specific data URI
497

498
        Parameters
499
        ----------
500
        uri : string
501
            URI to search
502

503
        Returns
504
        -------
505
        dict
506
            The corresponding parameters
507
        """
508
        raw_query = '''
1✔
509
        SELECT DISTINCT ?predicate ?object ?faldo_value ?faldo_relation
510
        WHERE {{
511
          ?URI ?predicate ?object .
512
          ?URI a ?entitytype .
513

514
          FILTER(! STRSTARTS(STR(?predicate), STR(askomics:)))
515
          OPTIONAL {{
516

517
            ?faldo_uri rdfs:domain ?entitytype .
518
            ?faldo_uri rdfs:label ?attribute_label .
519

520
            OPTIONAL {{
521
            ?object faldo:begin/faldo:position ?faldo_value .
522
            ?faldo_uri rdf:type askomics:faldoStart
523
            }}
524

525
            OPTIONAL {{
526
            ?object faldo:end/faldo:position ?faldo_value .
527
            ?faldo_uri rdf:type askomics:faldoEnd
528
            }}
529

530
            OPTIONAL {{
531
            ?object faldo:begin/faldo:reference/rdfs:label ?faldo_value .
532
            ?faldo_uri rdf:type askomics:faldoReference
533
            }}
534

535
            OPTIONAL {{
536
            ?object faldo:begin/rdf:type/rdfs:label ?faldo_value .
537
            ?faldo_uri rdf:type askomics:faldoStrand .
538
            }}
539

540
            OPTIONAL {{
541
              ?faldo_uri askomics:uri ?node_uri
542
            }}
543

544
            VALUES ?predicate {{faldo:location}}
545
          }}
546
          VALUES ?URI {{{}}}
547
          BIND(IF(isBlank(?faldo_uri), ?node_uri ,?faldo_uri) as ?faldo_relation)
548
        }}
549
        '''.format(uri)
550

551
        federated = self.is_federated()
1✔
552
        replace_froms = self.replace_froms()
1✔
553

554
        raw_query = self.prefix_query(raw_query)
1✔
555

556
        sparql = self.format_query(raw_query, replace_froms=replace_froms, federated=federated)
1✔
557

558
        query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=federated, endpoints=endpoints)
1✔
559
        _, data = query_launcher.process_query(sparql)
1✔
560

561
        formated_data = []
1✔
562
        for row in data:
1✔
563

564
            predicate = row['predicate']
1✔
565
            object = row['object']
1✔
566

567
            if row.get('faldo_relation'):
1✔
568
                predicate = row.get("faldo_relation")
1✔
569

570
            if row.get('faldo_value'):
1✔
571
                object = row.get('faldo_value')
1✔
572

573
            formated_data.append({
1✔
574
                'predicate': predicate,
575
                'object': object,
576
            })
577

578
        return formated_data
1✔
579

580
    def autocomplete_local_ontology(self, uri, query, max_terms, label):
1✔
581
        """Get results for a specific query
582

583
        Parameters
584
        ----------
585
        uri : string
586
            ontology uri
587
        query : string
588
            query to search
589

590
        Returns
591
        -------
592
        dict
593
            The corresponding parameters
594
        """
595

596
        subquery = ""
1✔
597

598
        if query:
1✔
599
            subquery = 'FILTER(contains(lcase(?label), "{}"))'.format(query.lower())
1✔
600
        raw_query = '''
1✔
601
        SELECT DISTINCT ?label
602
        WHERE {{
603
          ?uri rdf:type owl:Class .
604
          ?uri {} ?label .
605
          {}
606
        }}
607
        '''.format(label, subquery)
608

609
        raw_query = self.prefix_query(raw_query)
1✔
610

611
        is_federated = self.is_federated()
1✔
612

613
        sparql = self.format_query(raw_query, limit=max_terms, replace_froms=True, federated=is_federated)
1✔
614

615
        query_launcher = SparqlQueryLauncher(self.app, self.session, get_result_query=True, federated=is_federated)
1✔
616
        _, data = query_launcher.process_query(sparql)
1✔
617

618
        formated_data = []
1✔
619
        for row in data:
1✔
620
            formated_data.append(row['label'])
1✔
621

622
        return formated_data
1✔
623

624
    def format_sparql_variable(self, name):
1✔
625
        """Format a name into a sparql variable by remove spacial char and add a ?
626

627
        Parameters
628
        ----------
629
        name : string
630
            name to convert
631

632
        Returns
633
        -------
634
        string
635
            The corresponding sparql variable
636
        """
637
        return "?{}".format(re.sub(r'[^a-zA-Z0-9]+', '_', name))
1✔
638

639
    def is_bnode(self, uri, entities):
1✔
640
        """Check if a node uri is a blank node
641

642
        Parameters
643
        ----------
644
        uri : string
645
            node uri
646
        entities : list
647
            all the entities
648

649
        Returns
650
        -------
651
        Bool
652
            True if uri correspond to a blank node
653
        """
654
        for entity in entities:
1✔
655
            if entity["uri"] == uri and entity["type"] == "bnode":
1✔
656
                return True
×
657
        return False
1✔
658

659
    def get_block_ids(self, node_id):
1✔
660
        """Get blockid and sblockid of a node with its id
661

662
        Parameters
663
        ----------
664
        node_id : int
665
            Node id
666

667
        Returns
668
        -------
669
        int, int
670
            blockid and sblockid
671
        """
672
        for node in self.json["nodes"]:
1✔
673
            if node["id"] == node_id:
1✔
674
                return node["specialNodeId"], node["specialNodeGroupId"], node["specialPreviousIds"], node.get("depth")
1✔
675

676
        return None, None, (None, None), None
×
677

678
    def triple_block_to_string(self, triple_block):
1✔
679
        """Convert a triple block to a SPARQL string
680

681
        Parameters
682
        ----------
683
        triple_block : dict
684
            A triple block
685

686
        Returns
687
        -------
688
        str
689
            Corresponding SPARQL
690
        """
691
        if triple_block["type"] in ("UNION", "MINUS"):
×
692

693
            block_string = "{\n"
×
694
            i = 0
×
695
            current_spacing = "    "
×
696

697
            for sblock in triple_block["sblocks"]:
×
698
                sblock_string = "{"
×
699
                triples_string = '\n{}'.format(current_spacing * 2).join([self.triple_dict_to_string(triple_dict) for triple_dict in sblock["triples"]])
×
700
                triples_string += '\n{}'.format(current_spacing * 2)
×
701
                triples_string += '\n{}'.format(current_spacing * 2).join([filtr for filtr in sblock["filters"]])
×
702
                triples_string += '\n{}'.format(current_spacing * 2)
×
703
                triples_string += '\n{}'.format(current_spacing * 2).join([value for value in sblock["values"]])
×
704
                sblock_string += "\n{}{}\n{}}}".format(current_spacing * 2, triples_string, current_spacing * 2)
×
705

706
                block_string += "{}{} ".format(current_spacing * 2, triple_block["type"]) if (triple_block["type"] == "MINUS" or i > 0) else current_spacing * 2
×
707
                block_string += sblock_string + "\n"
×
708
                i += 1
×
709

710
            block_string += current_spacing + "}\n"
×
711

712
        return block_string
×
713

714
    def triple_dict_to_string(self, triple_dict):  # pragma: no cover
715
        """Convert a triple dict into a triple string
716

717
        Parameters
718
        ----------
719
        triple_dict : dict
720
            The triple dict
721

722
        Returns
723
        -------
724
        string
725
            The triple string
726
        """
727
        triple = "{} {} {} .".format(triple_dict["subject"], triple_dict["predicate"], triple_dict["object"])
728
        if triple_dict["optional"]:
729
            if triple_dict.get("nested_start"):
730
                triple = "OPTIONAL {{{}".format(triple)
731
            else:
732
                triple = "OPTIONAL {{{}}}".format(triple)
733
        # Close the }} if end of the nest
734
        if triple_dict.get("nested_end"):
735
            triple = "    " + triple + "}"
736
        elif triple_dict.get("nested"):
737
            triple = "    " + triple
738

739
        return triple
740

741
    def get_uri_filter_value(self, value):
1✔
742
        """Get full uri from a filter value (curie or uri)
743

744
        :xxx                     -->  :xxx
745
        uniprot:xxx              -->  <http://purl.uniprot.org/core/xxx>
746
        http://example.org/xxx   -->  <http://example.org/xxx>
747

748
        xxx                      -->  xxx is not a valid URI or CURIE
749
        bla:xxx                  -->  bla: is not a known prefix
750

751
        Parameters
752
        ----------
753
        value : string
754
            Input filter
755

756
        Returns
757
        -------
758
        string
759
            corresponding uri
760

761
        Raises
762
        ------
763
        ValueError
764
            Invalid URI or CURIE return a value error
765
        """
766
        if Utils().is_url(value):
1✔
767
            return "<{}>".format(value)
1✔
768
        elif ":" in value:
1✔
769
            prefix, val = value.split(":")
1✔
770
            if prefix:
1✔
771
                prefix_manager = PrefixManager(self.app, self.session)
1✔
772
                namespace = prefix_manager.get_namespace(prefix)
1✔
773
                if namespace:
1✔
774
                    return "<{}{}>".format(namespace, val)
1✔
775
                else:
776
                    raise ValueError("{}: is not a known prefix".format(prefix))
1✔
777
            else:
778
                return value
1✔
779

780
        raise ValueError("{} is not a valid URI or CURIE".format(value))
1✔
781

782
    def get_block_type(self, blockid):  # pragma: no cover
783
        """Summary
784

785
        Parameters
786
        ----------
787
        blockid : TYPE
788
            Description
789

790
        Returns
791
        -------
792
        TYPE
793
            Description
794
        """
795
        types_dict = {
796
            "unionNode": "UNION",
797
            "minusNode": "MINUS"
798
        }
799

800
        # Union sub-block
801
        if '_' in str(blockid):
802
            return "DEFAULT"
803

804
        for node in self.json["nodes"]:
805
            if node["type"] in ("unionNode", "minusNode"):
806
                if node["specialNodeId"] == blockid:
807
                    return types_dict[node["type"]]
808
        return None
809

810
    def store_triple(self, triple, blockid, sblockid, pblock_ids, depth):
1✔
811
        """Store a triple inthe right list
812

813
        Parameters
814
        ----------
815
        triple : dict
816
            triple dict
817
        typ : str
818
            relatin or attribute
819
        block_info : None, optional
820
            block info if triple is part of a block
821
        """
822
        if blockid:
1✔
823
            if depth:
×
824
                self.update_block_dict(depth, "triples", triple)
×
825
            else:
826
                self.store_block(triple, blockid, sblockid, pblock_ids)
×
827
        else:
828
            self.triples.append(triple)
1✔
829

830
    def store_filter(self, filtr, blockid, sblockid, pblock_ids, depth):
1✔
831
        """Store a FILTER in the right list
832

833
        Parameters
834
        ----------
835
        triple : dict
836
            triple dict
837
        typ : str
838
            relatin or attribute
839
        block_info : None, optional
840
            block info if triple is part of a block
841
        """
842
        if blockid:
×
843
            if depth:
×
844
                self.update_block_dict(depth, "filters", filtr)
×
845
            else:
846
                self.store_filter_block(filtr, blockid, sblockid)
×
847
        else:
848
            self.filters.append(filtr)
×
849

850
    def store_value(self, value, blockid, sblockid, pblock_ids, depth):
1✔
851
        """Store a VALUES inthe right list
852

853
        Parameters
854
        ----------
855
        triple : dict
856
            triple dict
857
        typ : str
858
            relatin or attribute
859
        block_info : None, optional
860
            block info if triple is part of a block
861
        """
862
        if blockid:
1✔
863
            if depth:
×
864
                self.update_block_dict(depth, "values", value)
×
865
            else:
866
                self.store_values_block(value, blockid, sblockid)
×
867
        else:
868
            self.values.append(value)
1✔
869

870
    def store_values_block(self, value, blockid, sblockid):  # pragma: no cover
871
        """Add a VALUES in a block. If block exist, add the triples, else, create a new block.
872

873
        Same for the sub block
874

875
        Parameters
876
        ----------
877
        triple : dict
878
            The triple dict to add
879
        blockid : int
880
            Block id
881
        sblockid : int
882
            Sub block id
883
        """
884
        for block in self.triples_blocks:
885
            if block["id"] == blockid:
886
                for sblock in block["sblocks"]:
887
                    if sblock["id"] == sblockid:
888
                        sblock["values"].append(value)
889
                        return
890
                block["sblocks"].append({
891
                    "id": sblockid,
892
                    "triples": [],
893
                    "filters": [],
894
                    "values": [value, ]
895
                })
896
                return
897
        self.triples_blocks.append({
898
            "id": blockid,
899
            "type": self.get_block_type(blockid),
900
            "sblocks": [{
901
                "id": sblockid,
902
                "triples": [],
903
                "filters": [],
904
                "values": [value, ]
905
            }, ]
906
        })
907

908
    def store_filter_block(self, filtr, blockid, sblockid):  # pragma: no cover
909
        """Add a FILTER in a block. If block exist, add the triples, else, create a new block.
910

911
        Same for the sub block
912

913
        Parameters
914
        ----------
915
        triple : dict
916
            The triple dict to add
917
        blockid : int
918
            Block id
919
        sblockid : int
920
            Sub block id
921
        """
922
        for block in self.triples_blocks:
923
            if block["id"] == blockid:
924
                for sblock in block["sblocks"]:
925
                    if sblock["id"] == sblockid:
926
                        sblock["filters"].append(filtr)
927
                        return
928
                block["sblocks"].append({
929
                    "id": sblockid,
930
                    "triples": [],
931
                    "filters": [filtr, ],
932
                    "values": []
933
                })
934
                return
935
        self.triples_blocks.append({
936
            "id": blockid,
937
            "type": self.get_block_type(blockid),
938
            "sblocks": [{
939
                "id": sblockid,
940
                "triples": [],
941
                "filters": [filtr, ],
942
                "values": []
943
            }, ]
944
        })
945

946
    def store_block(self, triple, blockid, sblockid, pblock_ids):  # pragma: no cover
947
        """Add a triple in a block. If block exist, add the triples, else, create a new block.
948

949
        Same for the sub block
950

951
        Parameters
952
        ----------
953
        triple : dict
954
            The triple dict to add
955
        blockid : int
956
            Block id
957
        sblockid : int
958
            Sub block id
959
        """
960
        for block in self.triples_blocks:
961
            if block["id"] == blockid:
962
                for sblock in block["sblocks"]:
963
                    if sblock["id"] == sblockid:
964
                        sblock["triples"].append(triple)
965
                        return
966
                block["sblocks"].append({
967
                    "id": sblockid,
968
                    "triples": [triple, ],
969
                    "filters": [],
970
                    "values": []
971
                })
972
                return
973
        self.triples_blocks.append({
974
            "id": blockid,
975
            "type": self.get_block_type(blockid),
976
            "sblocks": [{
977
                "id": sblockid,
978
                "triples": [triple, ],
979
                "filters": [],
980
                "values": []
981
            }, ]
982
        })
983

984
    def update_sub_block(self, block_dict, depths, type, value, current_depth):  # pragma: no cover
985
        depth = depths[current_depth]
986
        if depth not in block_dict:
987
            block_dict[depth] = {
988
                "type": self.get_block_type(depth),
989
                "triples": [],
990
                "filters": [],
991
                "values": [],
992
                "sub_blocks": {}
993
            }
994
        # End of branch
995
        if current_depth == len(depths) - 1:
996
            block_dict[depth][type].append(value)
997
        else:
998
            self.update_sub_block(block_dict[depth]["sub_blocks"], depths, type, value, current_depth + 1)
999

1000
    def update_block_dict(self, depths, type, value):  # pragma: no cover
1001
        self.update_sub_block(self.triples_blocks_dict, depths, type, value, 0)
1002

1003
    def replace_variables_in_triples(self, var_to_replace):
1✔
1004
        """Replace variables in triples
1005

1006
        Parameters
1007
        ----------
1008
        var_to_replace : list of tuples
1009
            var to replace in triples
1010
        """
1011
        for tpl_var in var_to_replace:
1✔
1012
            var_source = tpl_var[0]
×
1013
            var_target = tpl_var[1]
×
1014
            for i, triple_dict in enumerate(self.triples):
×
1015
                for key, value in triple_dict.items():
×
1016
                    if key not in ["optional", "nested", "nested_start", "nested_end", "form"]:
×
1017
                        self.triples[i][key] = value.replace(var_source, var_target)
×
1018
            for i, select in enumerate(self.selects):
×
1019
                self.selects[i] = select.replace(var_source, var_target)
×
1020
            for i, filtr in enumerate(self.filters):
×
1021
                self.filters[i] = filtr.replace(var_source, var_target)
×
1022

1023
        # uniq lists
1024
        self.triples = Utils.unique(self.triples)
1✔
1025
        self.selects = Utils.unique(self.selects)
1✔
1026

1027
    def replace_variables_in_sub_block(self, var_to_replace, content):  # pragma: no cover
1028
        for var_source, var_target in var_to_replace:
1029
            for ntriple, triple_dict in enumerate(content["triples"]):
1030
                for key, value in triple_dict.items():
1031
                    if key not in ["optional", "nested", "nested_start", "nested_end", "form"]:
1032
                        content["triples"][ntriple][key] = value.replace(var_source, var_target)
1033

1034
            for i, filtr in enumerate(content["filters"]):
1035
                content["filters"][i] = filtr.replace(var_source, var_target)
1036

1037
            for i, value in enumerate(content["values"]):
1038
                content["values"][i] = value.replace(var_source, var_target)
1039
        content["triples"] = Utils.unique(content["triples"])
1040

1041
        if content['sub_blocks']:
1042
            for sub_block in content['sub_blocks'].values():
1043
                self.replace_variables_in_sub_block(var_to_replace, sub_block)
1044

1045
    def replace_variables_in_blocks_dict(self, var_to_replace):  # pragma: no cover
1046
        """Replace variables in blocks
1047

1048
        Parameters
1049
        ----------
1050
        var_to_replace : list of tuples
1051
            var to replace in block
1052
        """
1053
        for block in self.triples_blocks_dict.values():
1054
            self.replace_variables_in_sub_block(var_to_replace, block)
1055

1056
    def triple_sub_block_to_string(self, block, indent="    "):  # pragma: no cover
1057
        new_indent = indent + "    "
1058
        sub_content = ""
1059
        if block['sub_blocks']:
1060
            if block["type"] == "UNION":
1061
                sub_content = "\n{}UNION ".format(new_indent).join([self.triple_sub_block_to_string(sub_block, new_indent) for sub_block in block['sub_blocks'].values()])
1062
            elif block["type"] == "MINUS":
1063
                sub_content = "MINUS " + "\n{}MINUS ".format(indent).join([self.triple_sub_block_to_string(sub_block, new_indent) for sub_block in block['sub_blocks'].values()])
1064
            else:
1065
                sub_content = "\n{}".format(indent).join([self.triple_sub_block_to_string(sub_block, new_indent) for sub_block in block['sub_blocks'].values()])
1066

1067
        content = "{{\n{}".format(new_indent)
1068
        triples_string = '\n{}'.format(new_indent).join([self.triple_dict_to_string(triple_dict) for triple_dict in block["triples"]])
1069
        triples_string += '\n{}'.format(new_indent) if block["filters"] else ""
1070
        triples_string += '\n{}'.format(new_indent).join([filtr for filtr in block["filters"]])
1071
        triples_string += '\n{}'.format(new_indent) if block["values"] else ""
1072
        triples_string += '\n{}'.format(new_indent).join([value for value in block["values"]])
1073
        content += triples_string
1074
        content += '\n{}'.format(new_indent) if sub_content and triples_string else ""
1075
        content += sub_content
1076

1077
        content += "\n{}}}".format(indent)
1078

1079
        return content
1080

1081
    def triple_blocks_dict_to_string(self):  # pragma: no cover
1082
        return '\n    '.join([self.triple_sub_block_to_string(triple_block) for triple_block in self.triples_blocks_dict.values()])
1083

1084
    def blocks_to_string(self):  # pragma: no cover
1085
        if self.legacy_block:
1086
            return '\n    '.join([self.triple_block_to_string(triple_block) for triple_block in self.triples_blocks])
1087
        return self.triple_blocks_dict_to_string()
1088

1089
    def replace_variables_in_blocks(self, var_to_replace):  # pragma: no cover
1090
        """Replace variables in blocks
1091

1092
        Parameters
1093
        ----------
1094
        var_to_replace : list of tuples
1095
            var to replace in block
1096
        """
1097
        for var_source, var_target in var_to_replace:
1098
            # Interate throught blocks
1099
            for nblock, block in enumerate(self.triples_blocks):
1100
                # Iterate over sub-blocks
1101
                for nsblock, sblock in enumerate(block["sblocks"]):
1102
                    # Iterate over triples
1103
                    for ntriple, triple_dict in enumerate(sblock["triples"]):
1104
                        for key, value in triple_dict.items():
1105
                            if key not in ["optional", "nested", "nested_start", "nested_end", "form"]:
1106
                                self.triples_blocks[nblock]["sblocks"][nsblock]["triples"][ntriple][key] = value.replace(var_source, var_target)
1107

1108
                    for i, filtr in enumerate(sblock["filters"]):
1109
                        self.triples_blocks[nblock]["sblocks"][nsblock]["filters"][i] = filtr.replace(var_source, var_target)
1110

1111
                    for i, value in enumerate(sblock["values"]):
1112
                        self.triples_blocks[nblock]["sblocks"][nsblock]["values"][i] = value.replace(var_source, var_target)
1113

1114
                self.triples_blocks[nblock]["sblocks"][nsblock]["triples"] = Utils.unique(self.triples_blocks[nblock]["sblocks"][nsblock]["triples"])
1115

1116
    def get_source_of_special_node(self, special_node_id):  # pragma: no cover
1117
        """Get if of original node of a special one
1118

1119
        Parameters
1120
        ----------
1121
        special_node_id : int
1122
            Special node id
1123

1124
        Returns
1125
        -------
1126
        int or None
1127
            Original node id
1128
        """
1129
        for link in self.json["links"]:
1130
            if link["type"] == "specialLink":
1131
                # Source is also a special node. Get source of source
1132
                if link["source"]['type'] in ['unionNode', 'minusNode']:
1133
                    return self.get_source_of_special_node(link["source"]['id'])
1134
                if link["target"]["id"] == special_node_id:
1135
                    return link["source"]["id"]
1136
        return None
1137

1138
    def build_query_from_json(self, preview=False, for_editor=False):
1✔
1139
        """Build a sparql query for the json dict of the query builder
1140

1141
        Parameters
1142
        ----------
1143
        preview : bool, optional
1144
            Build a preview query (with LIMIT)
1145
        for_editor : bool, optional
1146
            Remove FROMS and @federate
1147
        """
1148
        # Circular import
1149
        from askomics.libaskomics.OntologyManager import OntologyManager
1✔
1150
        entities = []
1✔
1151
        attributes = {}
1✔
1152
        linked_attributes = []
1✔
1153

1154
        self.selects = []
1✔
1155

1156
        self.triples = []
1✔
1157
        self.triples_blocks = []
1✔
1158
        self.triples_blocks_dict = {}
1✔
1159

1160
        self.values = []
1✔
1161
        self.filters = []
1✔
1162

1163
        start_end = []
1✔
1164
        strands = []
1✔
1165

1166
        var_to_replace = []
1✔
1167

1168
        ontologies = {}
1✔
1169
        om = OntologyManager(self.app, self.session)
1✔
1170

1171
        # Browse attributes to get entities
1172
        for attr in self.json["attr"]:
1✔
1173
            entities = entities + attr["entityUris"]
1✔
1174
            if attr["type"] == "uri" and attr.get("ontology", False) is True and not attr["entityUris"][0] in ontologies:
1✔
1175
                ontologies[attr["entityUris"][0]] = om.get_ontology(uri=attr["entityUris"][0])
×
1176

1177
        # Check if legacy block mode (ie, stored queries)
1178
        self.legacy_block = any([entity.get('legacyBlock') for entity in self.json['nodes']])
1✔
1179

1180
        entities = list(set(entities))  # uniq list
1✔
1181

1182
        # Set graphs in function of entities needed
1183
        self.set_graphs_and_endpoints(entities=entities, ontologies=ontologies)
1✔
1184

1185
        # self.log.debug(self.json)
1186

1187
        # Browse links (relations)
1188
        for link in self.json["links"]:
1✔
1189
            if not link["suggested"]:
1✔
1190

1191
                # if link is special, replace the special node variable with its real node
1192
                if link["type"] == "specialLink":
1✔
1193
                    special_node = link["target"]
×
1194
                    real_node = link["source"]
×
1195
                    real_node_id = real_node['id']
×
1196

1197
                    # Both end are special nodes.
1198
                    if real_node['type'] in ['minusNode', 'unionNode']:
×
1199
                        real_node_id = self.get_source_of_special_node(real_node_id)
×
1200

1201
                    var_to_replace.append((
×
1202
                        self.format_sparql_variable("{}{}_uri".format(special_node["label"], special_node["id"])),
1203
                        self.format_sparql_variable("{}{}_uri".format(real_node["label"], real_node_id))
1204
                    ))
1205

1206
                    continue
×
1207

1208
                source = self.format_sparql_variable("{}{}_uri".format(link["source"]["label"], link["source"]["id"]))
1✔
1209
                target = self.format_sparql_variable("{}{}_uri".format(link["target"]["label"], link["target"]["id"]))
1✔
1210

1211
                # Check if relation is in a block
1212
                block_id = None
1✔
1213
                sblock_id = None
1✔
1214
                pblock_ids = (None, None)
1✔
1215
                depth = None
1✔
1216
                if link["source"]["specialNodeId"] or link["target"]["specialNodeId"]:
1✔
1217
                    block_id = link["source"]["specialNodeId"]
×
1218
                    sblock_id = link["source"]["specialNodeGroupId"] if link["source"]["specialNodeGroupId"] else link["target"]["specialNodeGroupId"]
×
1219
                    pblock_ids = link["source"]["specialPreviousIds"]
×
1220
                    # Correct depth is the longest one
1221
                    depth = link["target"].get("depth", [])
×
1222
                    if len(link["source"].get("depth", [])) > len(depth):
×
1223
                        depth = link["source"].get("depth", [])
×
1224

1225
                # Position
1226
                if link["uri"] in ('included_in', 'overlap_with', 'distance_from'):
1✔
1227

1228
                    # If source of target is a special node, replace the id with the id of the concerned node
1229
                    source_id = link["source"]["id"]
×
1230
                    target_id = link["target"]["id"]
×
1231
                    if link["source"]["type"] in ("unionNode", "minusNode"):
×
1232
                        source_id = self.get_source_of_special_node(link["source"]["id"])
×
1233
                    if link["target"]["type"] in ("unionNode", "minusNode"):
×
1234
                        target_id = self.get_source_of_special_node(link["target"]["id"])
×
1235

1236
                    common_block = self.format_sparql_variable("block_{}_{}".format(link["source"]["id"], link["target"]["id"]))
×
1237
                    # Get start & end sparql variables
1238
                    for attr in self.json["attr"]:
×
1239
                        if not attr["faldo"]:
×
1240
                            continue
×
1241
                        if attr["nodeId"] == source_id:
×
1242
                            if attr["faldo"].endswith("faldoStart"):
×
1243
                                start_end.append(attr["id"])
×
1244
                                start_1 = self.format_sparql_variable("{}{}_{}".format(attr["entityLabel"], attr["nodeId"], attr["label"]))
×
1245
                            if attr["faldo"].endswith("faldoEnd"):
×
1246
                                start_end.append(attr["id"])
×
1247
                                end_1 = self.format_sparql_variable("{}{}_{}".format(attr["entityLabel"], attr["nodeId"], attr["label"]))
×
1248
                        if attr["nodeId"] == target_id:
×
1249
                            if attr["faldo"].endswith("faldoStart"):
×
1250
                                start_end.append(attr["id"])
×
1251
                                start_2 = self.format_sparql_variable("{}{}_{}".format(attr["entityLabel"], attr["nodeId"], attr["label"]))
×
1252
                            if attr["faldo"].endswith("faldoEnd"):
×
1253
                                start_end.append(attr["id"])
×
1254
                                end_2 = self.format_sparql_variable("{}{}_{}".format(attr["entityLabel"], attr["nodeId"], attr["label"]))
×
1255

1256
                    block_uri = "includeIn"
×
1257
                    if link["sameRef"]:
×
1258
                        block_uri = "includeInReference"
×
1259
                        if link["sameStrand"]:
×
1260
                            block_uri = "includeInReferenceStrand"
×
1261
                    elif link["sameStrand"]:
×
1262
                        block_uri = "includeInStrand"
×
1263

1264
                    if link["uri"] in ('included_in', 'overlap_with'):
×
1265
                        self.store_triple({
×
1266
                            "subject": source,
1267
                            "predicate": "askomics:{}".format(block_uri),
1268
                            "object": common_block,
1269
                            "optional": False
1270

1271
                        }, block_id, sblock_id, pblock_ids, depth)
1272

1273
                        self.store_triple({
×
1274
                            "subject": target,
1275
                            "predicate": "askomics:{}".format(block_uri),
1276
                            "object": common_block,
1277
                            "optional": False
1278

1279
                        }, block_id, sblock_id, pblock_ids, depth)
1280

1281
                    equal_sign = "" if link["strict"] else "="
×
1282

1283
                    if link["uri"] == "included_in":
×
1284
                        self.store_filter("FILTER ({start1} >{equalsign} {start2} && {end1} <{equalsign} {end2}) .".format(
×
1285
                            start1=start_1,
1286
                            start2=start_2,
1287
                            end1=end_1,
1288
                            end2=end_2,
1289
                            equalsign=equal_sign
1290
                        ), block_id, sblock_id, pblock_ids, depth)
1291

1292
                    elif link["uri"] == "overlap_with":
×
1293
                        self.store_filter("FILTER (({start2} >{equalsign} {start1} && {start2} <{equalsign} {end1}) || ({end2} >{equalsign} {start1} && {end2} <{equalsign} {end1}) || ({start1} >{equalsign} {start2} && {end1} <{equalsign} {end2}))".format(
×
1294
                            start1=start_1,
1295
                            start2=start_2,
1296
                            end1=end_1,
1297
                            end2=end_2,
1298
                            equalsign=equal_sign
1299
                        ), block_id, sblock_id, pblock_ids, depth)
1300
                    else:
1301
                        if link["sameRef"]:
×
1302
                            if link['sameStrand']:
×
1303
                                self.store_triple({
×
1304
                                    "subject": source,
1305
                                    "predicate": "askomics:referenceStrand",
1306
                                    "object": common_block,
1307
                                    "optional": False
1308

1309
                                }, block_id, sblock_id, pblock_ids, depth)
1310

1311
                                self.store_triple({
×
1312
                                    "subject": target,
1313
                                    "predicate": "askomics:referenceStrand",
1314
                                    "object": common_block,
1315
                                    "optional": False
1316

1317
                                }, block_id, sblock_id, pblock_ids, depth)
1318
                            else:
1319
                                self.store_triple({
×
1320
                                    "subject": source,
1321
                                    "predicate": "askomics:faldoReference",
1322
                                    "object": common_block,
1323
                                    "optional": False
1324

1325
                                }, block_id, sblock_id, pblock_ids, depth)
1326

1327
                                self.store_triple({
×
1328
                                    "subject": target,
1329
                                    "predicate": "askomics:faldoReference",
1330
                                    "object": common_block,
1331
                                    "optional": False
1332

1333
                                }, block_id, sblock_id, pblock_ids, depth)
1334

1335
                        elif link["sameStrand"]:
×
1336
                            self.store_triple({
×
1337
                                "subject": source,
1338
                                "predicate": "askomics:faldoStrand",
1339
                                "object": common_block,
1340
                                "optional": False
1341

1342
                            }, block_id, sblock_id, pblock_ids, depth)
1343

1344
                            self.store_triple({
×
1345
                                "subject": target,
1346
                                "predicate": "askomics:faldoStrand",
1347
                                "object": common_block,
1348
                                "optional": False
1349

1350
                            }, block_id, sblock_id, pblock_ids, depth)
1351

1352
                        for filter in link.get('faldoFilters', []):
×
1353
                            modifier_string = ""
×
1354
                            if filter['filterValue']:
×
1355
                                modifier_string = " {} {}".format(filter['filterModifier'], filter['filterValue'])
×
1356

1357
                            start = start_1 if filter['filterStart'] == "start" else end_1
×
1358
                            end = start_2 if filter['filterEnd'] == "start" else end_2
×
1359
                            filter_string = "FILTER ( {} {} {} {} ) .".format(start, filter['filterSign'], end, modifier_string)
×
1360
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1361

1362
                # Classic relation
1363
                else:
1364
                    # Manage ontology stuff
1365
                    is_inverse = link.get("reverse", False)
1✔
1366
                    inverse = ""
1✔
1367
                    is_recursive = link.get("recursive", False)
1✔
1368
                    recursive = ""
1✔
1369
                    relation = link["uri"]
1✔
1370

1371
                    if is_inverse:
1✔
1372
                        inverse = "^"
×
1373

1374
                    if is_recursive:
1✔
1375
                        recursive = "*"
×
1376

1377
                    relation = inverse + "<{}>".format(relation) + recursive
1✔
1378
                    triple = {
1✔
1379
                        "subject": source,
1380
                        "predicate": relation,
1381
                        "object": target,
1382
                        "optional": False
1383
                    }
1384

1385
                    if not link.get('indirect', False):
1✔
1386
                        self.store_triple(triple, block_id, sblock_id, pblock_ids, depth)
1✔
1387

1388
        # Store linked attributes
1389
        for attribute in self.json["attr"]:
1✔
1390
            attributes[attribute["id"]] = {
1✔
1391
                "label": attribute["label"],
1392
                "entity_label": attribute["entityLabel"],
1393
                "entity_id": attribute["nodeId"]
1394
            }
1395
            if attribute["linked"] and attribute["linkedWith"]:
1✔
1396
                linked_attributes.extend((attribute["id"], attribute["linkedWith"]))
×
1397

1398
        # Browse attributes
1399
        for attribute in self.json["attr"]:
1✔
1400
            # Get blockid and sblockid of the attribute node
1401
            block_id, sblock_id, pblock_ids, depth = self.get_block_ids(attribute["nodeId"])
1✔
1402

1403
            # URI ---
1404
            if attribute["type"] == "uri":
1✔
1405
                subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
1✔
1406
                predicate = attribute["uri"]
1✔
1407
                obj = "<{}>".format(attribute["entityUris"][0])
1✔
1408
                if not (self.is_bnode(attribute["entityUris"][0], self.json["nodes"]) or attribute.get("ontology", False) is True):
1✔
1409
                    self.store_triple({
1✔
1410
                        "subject": subject,
1411
                        "predicate": predicate,
1412
                        "object": obj,
1413
                        "optional": False
1414
                    }, block_id, sblock_id, pblock_ids, depth)
1415
                if attribute.get("ontology", False) is True:
1✔
1416
                    self.store_triple({
×
1417
                        "subject": subject,
1418
                        "predicate": predicate,
1419
                        "object": "owl:Class",
1420
                        "optional": False
1421
                    }, block_id, sblock_id, pblock_ids, depth)
1422

1423
                if attribute["visible"]:
1✔
1424
                    self.selects.append(subject)
1✔
1425
                # filters/values
1426
                if attribute["filterValue"] != "" and not attribute["linked"]:
1✔
1427
                    filter_value = self.get_uri_filter_value(attribute["filterValue"])
1✔
1428
                    if attribute["filterType"] == "regexp":
1✔
1429
                        negative_sign = ""
×
1430
                        if attribute["negative"]:
×
1431
                            negative_sign = "!"
×
1432
                            self.store_filter("FILTER ({}regex({}, {}, 'i'))".format(negative_sign, subject, filter_value), block_id, sblock_id, pblock_ids, depth)
×
1433
                    elif attribute["filterType"] == "exact":
1✔
1434
                        if attribute["negative"]:
1✔
1435
                            self.store_filter("FILTER (str({}) != {}) .".format(subject, filter_value), block_id, sblock_id, pblock_ids, depth)
×
1436
                        else:
1437
                            self.store_value("VALUES {} {{ {} }} .".format(subject, filter_value), block_id, sblock_id, pblock_ids, depth)
1✔
1438

1439
                if attribute["linked"] and attribute["linkedWith"]:
1✔
1440
                    var_2 = self.format_sparql_variable("{}{}_uri".format(
×
1441
                        attributes[attribute["linkedWith"]]["entity_label"],
1442
                        attributes[attribute["linkedWith"]]["entity_id"]
1443
                    ))
1444
                    var_to_replace.append((subject, var_2))
×
1445

1446
            if attribute["type"] == "boolean":
1✔
1447
                if attribute["visible"] or attribute["filterSelectedValues"] != [] or attribute["id"] in linked_attributes:
×
1448
                    subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
×
1449
                    predicate = "<{}>".format(attribute["uri"])
×
1450
                    obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["humanNodeId"], attribute["label"]))
×
1451

1452
                    self.store_triple({
×
1453
                        "subject": subject,
1454
                        "predicate": predicate,
1455
                        "object": obj,
1456
                        "optional": True if attribute["optional"] else False
1457
                    }, block_id, sblock_id, pblock_ids, depth)
1458

1459
                    if attribute["visible"]:
×
1460
                        self.selects.append(obj)
×
1461

1462
                # values
1463
                if attribute["filterSelectedValues"] != [] and not attribute["optional"] and not attribute["linked"]:
×
1464
                    uri_val_list = []
×
1465
                    for value in attribute["filterSelectedValues"]:
×
1466
                        if value == "true":
×
1467
                            bool_value = "'true'^^xsd:boolean"
×
1468
                        else:
1469
                            bool_value = "'false'^^xsd:boolean"
×
1470
                        value_var = obj
×
1471
                        uri_val_list.append(bool_value)
×
1472

1473
                    if uri_val_list:
×
1474
                        self.store_value("VALUES {} {{ {} }}".format(value_var, ' '.join(uri_val_list)), block_id, sblock_id, pblock_ids, depth)
×
1475
                if attribute["linked"] and attribute["linkedWith"]:
×
1476
                    var_2 = self.format_sparql_variable("{}{}_{}".format(
×
1477
                        attributes[attribute["linkedWith"]]["entity_label"],
1478
                        attributes[attribute["linkedWith"]]["entity_id"],
1479
                        attributes[attribute["linkedWith"]]["label"]
1480
                    ))
1481
                    if not attribute.get('linkedNegative', False):
×
1482
                        var_to_replace.append((obj, var_2))
×
1483
                    else:
1484
                        filter_string = "FILTER ( {} {} {} ) .".format(obj, "!=", var_2)
×
1485
                        self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1486

1487
            # Text
1488
            if attribute["type"] == "text":
1✔
1489
                if attribute["visible"] or attribute["filterValue"] != "" or attribute["id"] in linked_attributes:
1✔
1490
                    subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
1✔
1491
                    if attribute["uri"] == "rdfs:label":
1✔
1492
                        predicate = attribute["uri"]
1✔
1493
                        if ontologies.get(attribute["entityUris"][0]):
1✔
1494
                            predicate = ontologies[attribute["entityUris"][0]]["label_uri"]
×
1495
                    else:
1496
                        predicate = "<{}>".format(attribute["uri"])
×
1497

1498
                    obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["humanNodeId"], attribute["label"]))
1✔
1499

1500
                    self.store_triple({
1✔
1501
                        "subject": subject,
1502
                        "predicate": predicate,
1503
                        "object": obj,
1504
                        "optional": True if attribute["optional"] else False
1505
                    }, block_id, sblock_id, pblock_ids, depth)
1506
                    if attribute["visible"]:
1✔
1507
                        self.selects.append(obj)
1✔
1508
                # filters/values
1509
                if attribute["filterValue"] != "" and not attribute["optional"] and not attribute["linked"]:
1✔
1510
                    if attribute["filterType"] == "regexp":
×
1511
                        negative_sign = ""
×
1512
                        if attribute["negative"]:
×
1513
                            negative_sign = "!"
×
1514
                        self.store_filter("FILTER ({}regex({}, '{}', 'i'))".format(negative_sign, obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids, depth)
×
1515
                    elif attribute["filterType"] == "exact":
×
1516
                        if attribute["negative"]:
×
1517
                            self.store_filter("FILTER (str({}) != '{}') .".format(obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids, depth)
×
1518
                        else:
1519
                            self.store_value("VALUES {} {{ '{}' }} .".format(obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids, depth)
×
1520
                if attribute["linked"] and attribute["linkedWith"]:
1✔
1521
                    var_2 = self.format_sparql_variable("{}{}_{}".format(
×
1522
                        attributes[attribute["linkedWith"]]["entity_label"],
1523
                        attributes[attribute["linkedWith"]]["entity_id"],
1524
                        attributes[attribute["linkedWith"]]["label"]
1525
                    ))
1526
                    if not (attribute.get('linkedNegative', False) or attribute.get('linkedFilterValue')):
×
1527
                        var_to_replace.append((obj, var_2))
×
1528
                    else:
1529
                        filter = "!" if attribute.get('linkedNegative', False) else ""
×
1530
                        regex_clause = "{} = {}".format(obj, var_2)
×
1531
                        if attribute.get('linkedFilterValue'):
×
1532
                            regex_clause = r"REGEX({}, REPLACE('{}', '\\$1', {}), 'i')".format(obj, attribute.get('linkedFilterValue', "$1"), var_2)
×
1533
                        filter_string = "FILTER ( {} {} ) .".format(filter, regex_clause)
×
1534
                        self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1535

1536
            # Numeric
1537
            if attribute["type"] == "decimal":
1✔
1538
                if attribute["visible"] or Utils.check_key_in_list_of_dict(attribute["filters"], "filterValue") or attribute["id"] in start_end or attribute["id"] in linked_attributes:
1✔
1539
                    subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
×
1540
                    if attribute["faldo"]:
×
1541
                        predicate = "faldo:location/faldo:{}/faldo:position".format("begin" if attribute["faldo"].endswith("faldoStart") else "end")
×
1542
                        # Use faldo shortcut for faldo queries
1543
                        if attribute["id"] in start_end or attribute["id"] in linked_attributes:
×
1544
                            predicate = "askomics:{}".format("faldoBegin" if attribute["faldo"].endswith("faldoStart") else "faldoEnd")
×
1545
                    else:
1546
                        predicate = "<{}>".format(attribute["uri"])
×
1547
                    obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["nodeId"], attribute["label"]))
×
1548
                    self.store_triple({
×
1549
                        "subject": subject,
1550
                        "predicate": predicate,
1551
                        "object": obj,
1552
                        "optional": True if attribute["optional"] else False
1553
                    }, block_id, sblock_id, pblock_ids, depth)
1554
                    if attribute["visible"]:
×
1555
                        self.selects.append(obj)
×
1556
                # filters
1557
                for filtr in attribute["filters"]:
1✔
1558
                    if filtr["filterValue"] != "" and not attribute["optional"] and not attribute["linked"]:
1✔
1559
                        if filtr['filterSign'] == "=":
×
1560
                            self.store_value("VALUES {} {{ {} }} .".format(obj, filtr["filterValue"]), block_id, sblock_id, pblock_ids, depth)
×
1561
                        else:
1562
                            filter_string = "FILTER ( {} {} {} ) .".format(obj, filtr["filterSign"], filtr["filterValue"])
×
1563
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1564
                if attribute["linked"] and attribute["linkedWith"]:
1✔
1565
                    var_2 = self.format_sparql_variable("{}{}_{}".format(
×
1566
                        attributes[attribute["linkedWith"]]["entity_label"],
1567
                        attributes[attribute["linkedWith"]]["entity_id"],
1568
                        attributes[attribute["linkedWith"]]["label"]
1569
                    ))
1570
                    if any([filter['filterSign'] == "=" and not filter['filterValue'] for filter in attribute.get('linkedFilters', [])]):
×
1571
                        var_to_replace.append((obj, var_2))
×
1572
                    else:
1573
                        for filter in attribute.get('linkedFilters', []):
×
1574
                            modifier_string = ""
×
1575
                            if filter['filterValue']:
×
1576
                                modifier_string = " {} {}".format(filter['filterModifier'], filter['filterValue'])
×
1577
                            filter_string = "FILTER ( {} {} {} {} ) .".format(obj, filter['filterSign'], var_2, modifier_string)
×
1578
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1579

1580
            if attribute["type"] == "date":
1✔
1581
                if attribute["visible"] or Utils.check_key_in_list_of_dict(attribute["filters"], "filterValue") or attribute["id"] in linked_attributes:
×
1582
                    subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
×
1583
                    predicate = "<{}>".format(attribute["uri"])
×
1584
                    obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["nodeId"], attribute["label"]))
×
1585
                    self.store_triple({
×
1586
                        "subject": subject,
1587
                        "predicate": predicate,
1588
                        "object": obj,
1589
                        "optional": True if attribute["optional"] else False
1590
                    }, block_id, sblock_id, pblock_ids, depth)
1591
                    if attribute["visible"]:
×
1592
                        self.selects.append(obj)
×
1593
                # filters
1594
                for filtr in attribute["filters"]:
×
1595
                    if filtr["filterValue"] and not attribute["optional"] and not attribute["linked"]:
×
1596
                        # COnvert datetime to date
1597
                        val = filtr["filterValue"].split("T")[0]
×
1598
                        if filtr['filterSign'] == "=":
×
1599
                            self.store_value("VALUES {} {{ '{}'^^xsd:date }} .".format(obj, val), block_id, sblock_id, pblock_ids, depth)
×
1600
                        else:
1601
                            filter_string = "FILTER ( {} {} '{}'^^xsd:date ) .".format(obj, filtr["filterSign"], val)
×
1602
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1603
                if attribute["linked"] and attribute["linkedWith"]:
×
1604
                    var_2 = self.format_sparql_variable("{}{}_{}".format(
×
1605
                        attributes[attribute["linkedWith"]]["entity_label"],
1606
                        attributes[attribute["linkedWith"]]["entity_id"],
1607
                        attributes[attribute["linkedWith"]]["label"]
1608
                    ))
1609
                    if any([filter['filterSign'] == "=" and not filter['filterValue'] for filter in attribute.get('linkedFilters', [])]):
×
1610
                        var_to_replace.append((obj, var_2))
×
1611
                    else:
1612
                        for filter in attribute.get('linkedFilters', []):
×
1613
                            modifier_string = ""
×
1614
                            if filter['filterValue']:
×
1615
                                # Issue with virtuoso: engine-specific syntax for now (convert days to seconds)
1616
                                if self.settings.get('triplestore', 'triplestore') == "virtuoso":
×
1617
                                    modifier_string = " {} {}".format(filter['filterModifier'], 24 * 3600 * int(filter['filterValue']))
×
1618
                                else:
1619
                                    modifier_string = ' {} "P{}D"xsd:duration'.format(filter['filterModifier'], filter['filterValue'])
×
1620
                            filter_string = "FILTER ( {} {} {} {} ) .".format(obj, filter['filterSign'], var_2, modifier_string)
×
1621
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1622
            # Category
1623
            if attribute["type"] == "category":
1✔
1624
                if attribute["visible"] or attribute["filterSelectedValues"] != [] or attribute["id"] in strands or attribute["id"] in linked_attributes:
1✔
1625
                    node_uri = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
×
1626
                    category_value_uri = self.format_sparql_variable("{}{}_{}Category".format(attribute["entityLabel"], attribute["nodeId"], attribute["label"]))
×
1627
                    category_label = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["humanNodeId"], attribute["label"]))
×
1628
                    if attribute["faldo"] and attribute["faldo"].endswith("faldoReference"):
×
1629
                        category_name = 'faldo:location/faldo:begin/faldo:reference'
×
1630
                        self.store_triple({
×
1631
                            "subject": node_uri,
1632
                            "predicate": category_name,
1633
                            "object": category_value_uri,
1634
                            "optional": True if attribute["optional"] else False,
1635
                            "nested_start": True if (attribute["optional"] and attribute["visible"]) else False
1636
                        }, block_id, sblock_id, pblock_ids, depth)
1637
                        if attribute["visible"]:
×
1638
                            self.store_triple({
×
1639
                                "subject": category_value_uri,
1640
                                "predicate": "rdfs:label",
1641
                                "object": category_label,
1642
                                "optional": True if attribute["optional"] else False,
1643
                                "nested_end": True if attribute["optional"] else False
1644
                            }, block_id, sblock_id, pblock_ids, depth)
1645
                    elif attribute["faldo"] and attribute["faldo"].endswith("faldoStrand"):
×
1646
                        category_name = 'faldo:location/faldo:begin/rdf:type'
×
1647
                        self.store_triple({
×
1648
                            "subject": node_uri,
1649
                            "predicate": category_name,
1650
                            "object": category_value_uri,
1651
                            "optional": True if attribute["optional"] else False,
1652
                            "nested_start": True if (attribute["optional"] and attribute["visible"]) else False
1653
                        }, block_id, sblock_id, pblock_ids, depth)
1654
                        if attribute["visible"]:
×
1655
                            self.store_triple({
×
1656
                                "subject": category_value_uri,
1657
                                "predicate": "rdfs:label",
1658
                                "object": category_label,
1659
                                "optional": True if attribute["optional"] else False,
1660
                                "nested_end": True if attribute["optional"] else False
1661
                            }, block_id, sblock_id, pblock_ids, depth)
1662
                    else:
1663
                        category_name = "<{}>".format(attribute["uri"])
×
1664
                        self.store_triple({
×
1665
                            "subject": node_uri,
1666
                            "predicate": category_name,
1667
                            "object": category_value_uri,
1668
                            "optional": True if attribute["optional"] else False,
1669
                            "nested_start": True if (attribute["optional"] and attribute["visible"]) else False
1670
                        }, block_id, sblock_id, pblock_ids, depth)
1671
                        if attribute["visible"]:
×
1672
                            self.store_triple({
×
1673
                                "subject": category_value_uri,
1674
                                "predicate": "rdfs:label",
1675
                                "object": category_label,
1676
                                "optional": True if attribute["optional"] else False,
1677
                                "nested_end": True if attribute["optional"] else False
1678
                            }, block_id, sblock_id, pblock_ids, depth)
1679

1680
                    if attribute["visible"]:
×
1681
                        self.selects.append(category_label)
×
1682
                # values
1683
                if attribute["filterSelectedValues"] != [] and not attribute["optional"] and not attribute["linked"]:
1✔
1684
                    uri_val_list = []
×
1685
                    value_var = category_value_uri
×
1686
                    uri_val_list = ["<{}>".format(value) for value in attribute["filterSelectedValues"]]
×
1687
                    if uri_val_list:
×
1688
                        if attribute["exclude"]:
×
1689
                            filter_string = "FILTER ( {} NOT IN ( {} ) ) .".format(value_var, " ,".join(uri_val_list))
×
1690
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1691
                        else:
1692
                            self.store_value("VALUES {} {{ {} }}".format(value_var, ' '.join(uri_val_list)), block_id, sblock_id, pblock_ids, depth)
×
1693

1694
                if attribute["linked"] and attribute["linkedWith"]:
1✔
1695
                    var_2 = self.format_sparql_variable("{}{}_{}Category".format(
×
1696
                        attributes[attribute["linkedWith"]]["entity_label"],
1697
                        attributes[attribute["linkedWith"]]["entity_id"],
1698
                        attributes[attribute["linkedWith"]]["label"]
1699
                    ))
1700

1701
                    if not attribute.get('linkedNegative', False):
×
1702
                        var_to_replace.append((category_value_uri, var_2))
×
1703
                    else:
1704
                        filter_string = "FILTER ( {} {} {} ) .".format(category_value_uri, "!=", var_2)
×
1705
                        self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1706

1707
        from_string = "" if self.settings.getboolean("askomics", "single_tenant", fallback=False) else self.get_froms_from_graphs(self.graphs)
1✔
1708
        federated_from_string = self.get_federated_froms_from_graphs(self.graphs)
1✔
1709
        endpoints_string = self.get_endpoints_string()
1✔
1710
        federated_graphs_string = self.get_federated_remote_from_graphs()
1✔
1711

1712
        # Linked attributes: replace SPARQL variable target by source
1713
        self.replace_variables_in_blocks(var_to_replace)
1✔
1714
        self.replace_variables_in_triples(var_to_replace)
1✔
1715
        self.replace_variables_in_blocks_dict(var_to_replace)
1✔
1716

1717
        # Write the query
1718
        # query is for editor (no froms, no federated)
1719
        if for_editor:
1✔
1720
            query = """
1✔
1721
SELECT DISTINCT {selects}
1722
WHERE {{
1723
    {triples}
1724
    {blocks}
1725
    {filters}
1726
    {values}
1727
}}
1728
            """.format(
1729
                selects=' '.join(self.selects),
1730
                triples='\n    '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]),
1731
                blocks=self.blocks_to_string(),
1732
                filters='\n    '.join(self.filters),
1733
                values='\n    '.join(self.values))
1734

1735
        # Query is federated, add federated lines @federate & @from)
1736
        elif self.federated:
1✔
1737
            query = """
×
1738
{endpoints}
1739
{federated}
1740
{remote_graphs}
1741

1742
SELECT DISTINCT {selects}
1743
WHERE {{
1744
    {triples}
1745
    {blocks}
1746
    {filters}
1747
    {values}
1748
}}
1749
            """.format(
1750
                endpoints=endpoints_string,
1751
                federated=federated_from_string,
1752
                remote_graphs=federated_graphs_string,
1753
                selects=' '.join(self.selects),
1754
                triples='\n    '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]),
1755
                blocks=self.blocks_to_string(),
1756
                filters='\n    '.join(self.filters),
1757
                values='\n    '.join(self.values)
1758
            )
1759

1760
        # Query on the local endpoint (add froms)
1761
        elif self.endpoints == [self.local_endpoint_f]:
1✔
1762
            query = """
1✔
1763
SELECT DISTINCT {selects}
1764
{froms}
1765
WHERE {{
1766
    {triples}
1767
    {blocks}
1768
    {filters}
1769
    {values}
1770
}}
1771
            """.format(
1772
                selects=' '.join(self.selects),
1773
                froms=from_string,
1774
                triples='\n    '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]),
1775
                blocks=self.blocks_to_string(),
1776
                filters='\n    '.join(self.filters),
1777
                values='\n    '.join(self.values))
1778

1779
        # Query an external endpoint (no froms)
1780
        else:
1781
            query = """
1✔
1782
SELECT DISTINCT {selects}
1783
WHERE {{
1784
    {triples}
1785
    {blocks}
1786
    {filters}
1787
    {values}
1788
}}
1789
            """.format(
1790
                selects=' '.join(self.selects),
1791
                triples='\n    '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]),
1792
                blocks=self.blocks_to_string(),
1793
                filters='\n    '.join(self.filters),
1794
                values='\n    '.join(self.values))
1795

1796
        if preview:
1✔
1797
            query += "\nLIMIT {}".format(self.settings.getint('triplestore', 'preview_limit'))
1✔
1798

1799
        self.sparql = self.prefix_query(textwrap.dedent(query))
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc