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

askomics / flaskomics / 5785737010

pending completion
5785737010

push

github-actions

mboudet
Fix test & add indirect to link

6209 of 7404 relevant lines covered (83.86%)

0.84 hits per line

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

53.26
/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
                    self.store_triple({
×
1265
                        "subject": source,
1266
                        "predicate": "askomics:{}".format(block_uri),
1267
                        "object": common_block,
1268
                        "optional": False
1269

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

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

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

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

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

1291
                    elif link["uri"] == "overlap_with":
×
1292
                        self.store_filter("FILTER (({start2} >{equalsign} {start1} && {start2} <{equalsign} {end1}) || ({end2} >{equalsign} {start1} && {end2} <{equalsign} {end1}) || ({start1} >{equalsign} {start2} && {end1} <{equalsign} {end2}))".format(
×
1293
                            start1=start_1,
1294
                            start2=start_2,
1295
                            end1=end_1,
1296
                            end2=end_2,
1297
                            equalsign=equal_sign
1298
                        ), block_id, sblock_id, pblock_ids, depth)
1299
                    else:
1300
                        for filter in link.get('faldoFilters', []):
×
1301
                            modifier_string = ""
×
1302
                            if filter['filterValue']:
×
1303
                                modifier_string = " {} {}".format(filter['filterModifier'], filter['filterValue'])
×
1304

1305
                            start = start_1 if filter['filterStart'] == "start" else end_1
×
1306
                            end = start_2 if filter['filterEnd'] == "start" else end_2
×
1307
                            filter_string = "FILTER ( {} {} {} {} ) .".format(start, filter['filterSign'], end, modifier_string)
×
1308
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1309

1310
                # Classic relation
1311
                else:
1312
                    # Manage ontology stuff
1313
                    inverse = ""
1✔
1314
                    recurrence = ""
1✔
1315
                    relation = link["uri"]
1✔
1316

1317
                    if relation.startswith("^"):
1✔
1318
                        inverse = "^"
×
1319
                        relation = relation.lstrip("^")
×
1320

1321
                    if relation.endswith("*"):
1✔
1322
                        recurrence = "*"
×
1323
                        relation = relation.rstrip("*")
×
1324

1325
                    relation = inverse + "<{}>".format(relation) + recurrence
1✔
1326
                    triple = {
1✔
1327
                        "subject": source,
1328
                        "predicate": relation,
1329
                        "object": target,
1330
                        "optional": False
1331
                    }
1332

1333
                    if not link.get('indirect', False):
1✔
1334
                        self.store_triple(triple, block_id, sblock_id, pblock_ids, depth)
1✔
1335

1336
        # Store linked attributes
1337
        for attribute in self.json["attr"]:
1✔
1338
            attributes[attribute["id"]] = {
1✔
1339
                "label": attribute["label"],
1340
                "entity_label": attribute["entityLabel"],
1341
                "entity_id": attribute["nodeId"]
1342
            }
1343
            if attribute["linked"] and attribute["linkedWith"]:
1✔
1344
                linked_attributes.extend((attribute["id"], attribute["linkedWith"]))
×
1345

1346
        # Browse attributes
1347
        for attribute in self.json["attr"]:
1✔
1348
            # Get blockid and sblockid of the attribute node
1349
            block_id, sblock_id, pblock_ids, depth = self.get_block_ids(attribute["nodeId"])
1✔
1350

1351
            # URI ---
1352
            if attribute["type"] == "uri":
1✔
1353
                subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
1✔
1354
                predicate = attribute["uri"]
1✔
1355
                obj = "<{}>".format(attribute["entityUris"][0])
1✔
1356
                if not (self.is_bnode(attribute["entityUris"][0], self.json["nodes"]) or attribute.get("ontology", False) is True):
1✔
1357
                    self.store_triple({
1✔
1358
                        "subject": subject,
1359
                        "predicate": predicate,
1360
                        "object": obj,
1361
                        "optional": False
1362
                    }, block_id, sblock_id, pblock_ids, depth)
1363
                if attribute.get("ontology", False) is True:
1✔
1364
                    self.store_triple({
×
1365
                        "subject": subject,
1366
                        "predicate": predicate,
1367
                        "object": "owl:Class",
1368
                        "optional": False
1369
                    }, block_id, sblock_id, pblock_ids, depth)
1370

1371
                if attribute["visible"]:
1✔
1372
                    self.selects.append(subject)
1✔
1373
                # filters/values
1374
                if attribute["filterValue"] != "" and not attribute["linked"]:
1✔
1375
                    filter_value = self.get_uri_filter_value(attribute["filterValue"])
1✔
1376
                    if attribute["filterType"] == "regexp":
1✔
1377
                        negative_sign = ""
×
1378
                        if attribute["negative"]:
×
1379
                            negative_sign = "!"
×
1380
                            self.store_filter("FILTER ({}regex({}, {}, 'i'))".format(negative_sign, subject, filter_value), block_id, sblock_id, pblock_ids, depth)
×
1381
                    elif attribute["filterType"] == "exact":
1✔
1382
                        if attribute["negative"]:
1✔
1383
                            self.store_filter("FILTER (str({}) != {}) .".format(subject, filter_value), block_id, sblock_id, pblock_ids, depth)
×
1384
                        else:
1385
                            self.store_value("VALUES {} {{ {} }} .".format(subject, filter_value), block_id, sblock_id, pblock_ids, depth)
1✔
1386

1387
                if attribute["linked"] and attribute["linkedWith"]:
1✔
1388
                    var_2 = self.format_sparql_variable("{}{}_uri".format(
×
1389
                        attributes[attribute["linkedWith"]]["entity_label"],
1390
                        attributes[attribute["linkedWith"]]["entity_id"]
1391
                    ))
1392
                    var_to_replace.append((subject, var_2))
×
1393

1394
            if attribute["type"] == "boolean":
1✔
1395
                if attribute["visible"] or attribute["filterSelectedValues"] != [] or attribute["id"] in linked_attributes:
×
1396
                    subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
×
1397
                    predicate = "<{}>".format(attribute["uri"])
×
1398
                    obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["humanNodeId"], attribute["label"]))
×
1399

1400
                    self.store_triple({
×
1401
                        "subject": subject,
1402
                        "predicate": predicate,
1403
                        "object": obj,
1404
                        "optional": True if attribute["optional"] else False
1405
                    }, block_id, sblock_id, pblock_ids, depth)
1406

1407
                    if attribute["visible"]:
×
1408
                        self.selects.append(obj)
×
1409

1410
                # values
1411
                if attribute["filterSelectedValues"] != [] and not attribute["optional"] and not attribute["linked"]:
×
1412
                    uri_val_list = []
×
1413
                    for value in attribute["filterSelectedValues"]:
×
1414
                        if value == "true":
×
1415
                            bool_value = "'true'^^xsd:boolean"
×
1416
                        else:
1417
                            bool_value = "'false'^^xsd:boolean"
×
1418
                        value_var = obj
×
1419
                        uri_val_list.append(bool_value)
×
1420

1421
                    if uri_val_list:
×
1422
                        self.store_value("VALUES {} {{ {} }}".format(value_var, ' '.join(uri_val_list)), block_id, sblock_id, pblock_ids, depth)
×
1423
                if attribute["linked"] and attribute["linkedWith"]:
×
1424
                    var_2 = self.format_sparql_variable("{}{}_{}".format(
×
1425
                        attributes[attribute["linkedWith"]]["entity_label"],
1426
                        attributes[attribute["linkedWith"]]["entity_id"],
1427
                        attributes[attribute["linkedWith"]]["label"]
1428
                    ))
1429
                    if not attribute.get('linkedNegative', False):
×
1430
                        var_to_replace.append((obj, var_2))
×
1431
                    else:
1432
                        filter_string = "FILTER ( {} {} {} ) .".format(obj, "!=", var_2)
×
1433
                        self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1434

1435
            # Text
1436
            if attribute["type"] == "text":
1✔
1437
                if attribute["visible"] or attribute["filterValue"] != "" or attribute["id"] in linked_attributes:
1✔
1438
                    subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
1✔
1439
                    if attribute["uri"] == "rdfs:label":
1✔
1440
                        predicate = attribute["uri"]
1✔
1441
                        if ontologies.get(attribute["entityUris"][0]):
1✔
1442
                            predicate = ontologies[attribute["entityUris"][0]]["label_uri"]
×
1443
                    else:
1444
                        predicate = "<{}>".format(attribute["uri"])
×
1445

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

1448
                    self.store_triple({
1✔
1449
                        "subject": subject,
1450
                        "predicate": predicate,
1451
                        "object": obj,
1452
                        "optional": True if attribute["optional"] else False
1453
                    }, block_id, sblock_id, pblock_ids, depth)
1454
                    if attribute["visible"]:
1✔
1455
                        self.selects.append(obj)
1✔
1456
                # filters/values
1457
                if attribute["filterValue"] != "" and not attribute["optional"] and not attribute["linked"]:
1✔
1458
                    if attribute["filterType"] == "regexp":
×
1459
                        negative_sign = ""
×
1460
                        if attribute["negative"]:
×
1461
                            negative_sign = "!"
×
1462
                        self.store_filter("FILTER ({}regex({}, '{}', 'i'))".format(negative_sign, obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids, depth)
×
1463
                    elif attribute["filterType"] == "exact":
×
1464
                        if attribute["negative"]:
×
1465
                            self.store_filter("FILTER (str({}) != '{}') .".format(obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids, depth)
×
1466
                        else:
1467
                            self.store_value("VALUES {} {{ '{}' }} .".format(obj, attribute["filterValue"]), block_id, sblock_id, pblock_ids, depth)
×
1468
                if attribute["linked"] and attribute["linkedWith"]:
1✔
1469
                    var_2 = self.format_sparql_variable("{}{}_{}".format(
×
1470
                        attributes[attribute["linkedWith"]]["entity_label"],
1471
                        attributes[attribute["linkedWith"]]["entity_id"],
1472
                        attributes[attribute["linkedWith"]]["label"]
1473
                    ))
1474
                    if not (attribute.get('linkedNegative', False) or attribute.get('linkedFilterValue')):
×
1475
                        var_to_replace.append((obj, var_2))
×
1476
                    else:
1477
                        filter = "!" if attribute.get('linkedNegative', False) else ""
×
1478
                        regex_clause = "{} = {}".format(obj, var_2)
×
1479
                        if attribute.get('linkedFilterValue'):
×
1480
                            regex_clause = r"REGEX({}, REPLACE('{}', '\\$1', {}), 'i')".format(obj, attribute.get('linkedFilterValue', "$1"), var_2)
×
1481
                        filter_string = "FILTER ( {} {} ) .".format(filter, regex_clause)
×
1482
                        self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1483

1484
            # Numeric
1485
            if attribute["type"] == "decimal":
1✔
1486
                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✔
1487
                    subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
×
1488
                    if attribute["faldo"]:
×
1489
                        predicate = "faldo:location/faldo:{}/faldo:position".format("begin" if attribute["faldo"].endswith("faldoStart") else "end")
×
1490
                    else:
1491
                        predicate = "<{}>".format(attribute["uri"])
×
1492
                    obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["nodeId"], attribute["label"]))
×
1493
                    self.store_triple({
×
1494
                        "subject": subject,
1495
                        "predicate": predicate,
1496
                        "object": obj,
1497
                        "optional": True if attribute["optional"] else False
1498
                    }, block_id, sblock_id, pblock_ids, depth)
1499
                    if attribute["visible"]:
×
1500
                        self.selects.append(obj)
×
1501
                # filters
1502
                for filtr in attribute["filters"]:
1✔
1503
                    if filtr["filterValue"] != "" and not attribute["optional"] and not attribute["linked"]:
1✔
1504
                        if filtr['filterSign'] == "=":
×
1505
                            self.store_value("VALUES {} {{ {} }} .".format(obj, filtr["filterValue"]), block_id, sblock_id, pblock_ids, depth)
×
1506
                        else:
1507
                            filter_string = "FILTER ( {} {} {} ) .".format(obj, filtr["filterSign"], filtr["filterValue"])
×
1508
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1509
                if attribute["linked"] and attribute["linkedWith"]:
1✔
1510
                    var_2 = self.format_sparql_variable("{}{}_{}".format(
×
1511
                        attributes[attribute["linkedWith"]]["entity_label"],
1512
                        attributes[attribute["linkedWith"]]["entity_id"],
1513
                        attributes[attribute["linkedWith"]]["label"]
1514
                    ))
1515
                    if any([filter['filterSign'] == "=" and not filter['filterValue'] for filter in attribute.get('linkedFilters', [])]):
×
1516
                        var_to_replace.append((obj, var_2))
×
1517
                    else:
1518
                        for filter in attribute.get('linkedFilters', []):
×
1519
                            modifier_string = ""
×
1520
                            if filter['filterValue']:
×
1521
                                modifier_string = " {} {}".format(filter['filterModifier'], filter['filterValue'])
×
1522
                            filter_string = "FILTER ( {} {} {} {} ) .".format(obj, filter['filterSign'], var_2, modifier_string)
×
1523
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1524

1525
            if attribute["type"] == "date":
1✔
1526
                if attribute["visible"] or Utils.check_key_in_list_of_dict(attribute["filters"], "filterValue") or attribute["id"] in linked_attributes:
×
1527
                    subject = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
×
1528
                    predicate = "<{}>".format(attribute["uri"])
×
1529
                    obj = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["nodeId"], attribute["label"]))
×
1530
                    self.store_triple({
×
1531
                        "subject": subject,
1532
                        "predicate": predicate,
1533
                        "object": obj,
1534
                        "optional": True if attribute["optional"] else False
1535
                    }, block_id, sblock_id, pblock_ids, depth)
1536
                    if attribute["visible"]:
×
1537
                        self.selects.append(obj)
×
1538
                # filters
1539
                for filtr in attribute["filters"]:
×
1540
                    if filtr["filterValue"] and not attribute["optional"] and not attribute["linked"]:
×
1541
                        # COnvert datetime to date
1542
                        val = filtr["filterValue"].split("T")[0]
×
1543
                        if filtr['filterSign'] == "=":
×
1544
                            self.store_value("VALUES {} {{ '{}'^^xsd:date }} .".format(obj, val), block_id, sblock_id, pblock_ids, depth)
×
1545
                        else:
1546
                            filter_string = "FILTER ( {} {} '{}'^^xsd:date ) .".format(obj, filtr["filterSign"], val)
×
1547
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1548
                if attribute["linked"] and attribute["linkedWith"]:
×
1549
                    var_2 = self.format_sparql_variable("{}{}_{}".format(
×
1550
                        attributes[attribute["linkedWith"]]["entity_label"],
1551
                        attributes[attribute["linkedWith"]]["entity_id"],
1552
                        attributes[attribute["linkedWith"]]["label"]
1553
                    ))
1554
                    if any([filter['filterSign'] == "=" and not filter['filterValue'] for filter in attribute.get('linkedFilters', [])]):
×
1555
                        var_to_replace.append((obj, var_2))
×
1556
                    else:
1557
                        for filter in attribute.get('linkedFilters', []):
×
1558
                            modifier_string = ""
×
1559
                            if filter['filterValue']:
×
1560
                                # Issue with virtuoso: engine-specific syntax for now (convert days to seconds)
1561
                                if self.settings.get('triplestore', 'triplestore') == "virtuoso":
×
1562
                                    modifier_string = " {} {}".format(filter['filterModifier'], 24 * 3600 * int(filter['filterValue']))
×
1563
                                else:
1564
                                    modifier_string = ' {} "P{}D"xsd:duration'.format(filter['filterModifier'], filter['filterValue'])
×
1565
                            filter_string = "FILTER ( {} {} {} {} ) .".format(obj, filter['filterSign'], var_2, modifier_string)
×
1566
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1567
            # Category
1568
            if attribute["type"] == "category":
1✔
1569
                if attribute["visible"] or attribute["filterSelectedValues"] != [] or attribute["id"] in strands or attribute["id"] in linked_attributes:
1✔
1570
                    node_uri = self.format_sparql_variable("{}{}_uri".format(attribute["entityLabel"], attribute["nodeId"]))
×
1571
                    category_value_uri = self.format_sparql_variable("{}{}_{}Category".format(attribute["entityLabel"], attribute["nodeId"], attribute["label"]))
×
1572
                    category_label = self.format_sparql_variable("{}{}_{}".format(attribute["entityLabel"], attribute["humanNodeId"], attribute["label"]))
×
1573
                    if attribute["faldo"] and attribute["faldo"].endswith("faldoReference"):
×
1574
                        category_name = 'faldo:location/faldo:begin/faldo:reference'
×
1575
                        self.store_triple({
×
1576
                            "subject": node_uri,
1577
                            "predicate": category_name,
1578
                            "object": category_value_uri,
1579
                            "optional": True if attribute["optional"] else False,
1580
                            "nested_start": True if (attribute["optional"] and attribute["visible"]) else False
1581
                        }, block_id, sblock_id, pblock_ids, depth)
1582
                        if attribute["visible"]:
×
1583
                            self.store_triple({
×
1584
                                "subject": category_value_uri,
1585
                                "predicate": "rdfs:label",
1586
                                "object": category_label,
1587
                                "optional": True if attribute["optional"] else False,
1588
                                "nested_end": True if attribute["optional"] else False
1589
                            }, block_id, sblock_id, pblock_ids, depth)
1590
                    elif attribute["faldo"] and attribute["faldo"].endswith("faldoStrand"):
×
1591
                        category_name = 'faldo:location/faldo:begin/rdf:type'
×
1592
                        self.store_triple({
×
1593
                            "subject": node_uri,
1594
                            "predicate": category_name,
1595
                            "object": category_value_uri,
1596
                            "optional": True if attribute["optional"] else False,
1597
                            "nested_start": True if (attribute["optional"] and attribute["visible"]) else False
1598
                        }, block_id, sblock_id, pblock_ids, depth)
1599
                        if attribute["visible"]:
×
1600
                            self.store_triple({
×
1601
                                "subject": category_value_uri,
1602
                                "predicate": "rdfs:label",
1603
                                "object": category_label,
1604
                                "optional": True if attribute["optional"] else False,
1605
                                "nested_end": True if attribute["optional"] else False
1606
                            }, block_id, sblock_id, pblock_ids, depth)
1607
                    else:
1608
                        category_name = "<{}>".format(attribute["uri"])
×
1609
                        self.store_triple({
×
1610
                            "subject": node_uri,
1611
                            "predicate": category_name,
1612
                            "object": category_value_uri,
1613
                            "optional": True if attribute["optional"] else False,
1614
                            "nested_start": True if (attribute["optional"] and attribute["visible"]) else False
1615
                        }, block_id, sblock_id, pblock_ids, depth)
1616
                        if attribute["visible"]:
×
1617
                            self.store_triple({
×
1618
                                "subject": category_value_uri,
1619
                                "predicate": "rdfs:label",
1620
                                "object": category_label,
1621
                                "optional": True if attribute["optional"] else False,
1622
                                "nested_end": True if attribute["optional"] else False
1623
                            }, block_id, sblock_id, pblock_ids, depth)
1624

1625
                    if attribute["visible"]:
×
1626
                        self.selects.append(category_label)
×
1627
                # values
1628
                if attribute["filterSelectedValues"] != [] and not attribute["optional"] and not attribute["linked"]:
1✔
1629
                    uri_val_list = []
×
1630
                    value_var = category_value_uri
×
1631
                    uri_val_list = ["<{}>".format(value) for value in attribute["filterSelectedValues"]]
×
1632
                    if uri_val_list:
×
1633
                        if attribute["exclude"]:
×
1634
                            filter_string = "FILTER ( {} NOT IN ( {} ) ) .".format(value_var, " ,".join(uri_val_list))
×
1635
                            self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1636
                        else:
1637
                            self.store_value("VALUES {} {{ {} }}".format(value_var, ' '.join(uri_val_list)), block_id, sblock_id, pblock_ids, depth)
×
1638

1639
                if attribute["linked"] and attribute["linkedWith"]:
1✔
1640
                    var_2 = self.format_sparql_variable("{}{}_{}Category".format(
×
1641
                        attributes[attribute["linkedWith"]]["entity_label"],
1642
                        attributes[attribute["linkedWith"]]["entity_id"],
1643
                        attributes[attribute["linkedWith"]]["label"]
1644
                    ))
1645

1646
                    if not attribute.get('linkedNegative', False):
×
1647
                        var_to_replace.append((category_value_uri, var_2))
×
1648
                    else:
1649
                        filter_string = "FILTER ( {} {} {} ) .".format(category_value_uri, "!=", var_2)
×
1650
                        self.store_filter(filter_string, block_id, sblock_id, pblock_ids, depth)
×
1651

1652
        from_string = "" if self.settings.getboolean("askomics", "single_tenant", fallback=False) else self.get_froms_from_graphs(self.graphs)
1✔
1653
        federated_from_string = self.get_federated_froms_from_graphs(self.graphs)
1✔
1654
        endpoints_string = self.get_endpoints_string()
1✔
1655
        federated_graphs_string = self.get_federated_remote_from_graphs()
1✔
1656

1657
        # Linked attributes: replace SPARQL variable target by source
1658
        self.replace_variables_in_blocks(var_to_replace)
1✔
1659
        self.replace_variables_in_triples(var_to_replace)
1✔
1660
        self.replace_variables_in_blocks_dict(var_to_replace)
1✔
1661

1662
        # Write the query
1663
        # query is for editor (no froms, no federated)
1664
        if for_editor:
1✔
1665
            query = """
1✔
1666
SELECT DISTINCT {selects}
1667
WHERE {{
1668
    {triples}
1669
    {blocks}
1670
    {filters}
1671
    {values}
1672
}}
1673
            """.format(
1674
                selects=' '.join(self.selects),
1675
                triples='\n    '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]),
1676
                blocks=self.blocks_to_string(),
1677
                filters='\n    '.join(self.filters),
1678
                values='\n    '.join(self.values))
1679

1680
        # Query is federated, add federated lines @federate & @from)
1681
        elif self.federated:
1✔
1682
            query = """
×
1683
{endpoints}
1684
{federated}
1685
{remote_graphs}
1686

1687
SELECT DISTINCT {selects}
1688
WHERE {{
1689
    {triples}
1690
    {blocks}
1691
    {filters}
1692
    {values}
1693
}}
1694
            """.format(
1695
                endpoints=endpoints_string,
1696
                federated=federated_from_string,
1697
                remote_graphs=federated_graphs_string,
1698
                selects=' '.join(self.selects),
1699
                triples='\n    '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]),
1700
                blocks=self.blocks_to_string(),
1701
                filters='\n    '.join(self.filters),
1702
                values='\n    '.join(self.values)
1703
            )
1704

1705
        # Query on the local endpoint (add froms)
1706
        elif self.endpoints == [self.local_endpoint_f]:
1✔
1707
            query = """
1✔
1708
SELECT DISTINCT {selects}
1709
{froms}
1710
WHERE {{
1711
    {triples}
1712
    {blocks}
1713
    {filters}
1714
    {values}
1715
}}
1716
            """.format(
1717
                selects=' '.join(self.selects),
1718
                froms=from_string,
1719
                triples='\n    '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]),
1720
                blocks=self.blocks_to_string(),
1721
                filters='\n    '.join(self.filters),
1722
                values='\n    '.join(self.values))
1723

1724
        # Query an external endpoint (no froms)
1725
        else:
1726
            query = """
1✔
1727
SELECT DISTINCT {selects}
1728
WHERE {{
1729
    {triples}
1730
    {blocks}
1731
    {filters}
1732
    {values}
1733
}}
1734
            """.format(
1735
                selects=' '.join(self.selects),
1736
                triples='\n    '.join([self.triple_dict_to_string(triple_dict) for triple_dict in self.triples]),
1737
                blocks=self.blocks_to_string(),
1738
                filters='\n    '.join(self.filters),
1739
                values='\n    '.join(self.values))
1740

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

1744
        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

© 2026 Coveralls, Inc