GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Orange.canvas.scheme.Scheme   F
last analyzed

Complexity

Total Complexity 90

Size/Duplication

Total Lines 631
Duplicated Lines 0 %
Metric Value
dl 0
loc 631
rs 1.4811
wmc 90

39 Methods

Rating   Name   Duplication   Size   Complexity  
A remove_link() 0 20 1
B __remove_node_links() 0 13 5
A runtime_env() 0 9 1
A parents() 0 5 2
A output_links() 0 7 1
B new_node() 0 35 2
A nodes() 0 6 1
A links() 0 6 1
A set_title() 0 7 2
B find_links() 0 13 7
A title() 0 5 1
A add_node() 0 18 1
A get_runtime_env() 0 5 1
A is_ancestor() 0 12 1
F propose_links() 0 43 14
A compatible_channels() 0 11 1
B weight() 0 13 6
A save_to() 0 16 2
A creates_cycle() 0 14 1
A input_links() 0 7 1
A remove_node() 0 20 1
A set_description() 0 7 2
B downstream_nodes() 0 21 5
B new_link() 0 28 1
B load_from() 0 11 5
A remove_annotation() 0 8 1
A description() 0 5 1
A annotations() 0 8 1
A add_link() 0 22 1
B upstream_nodes() 0 22 5
A children() 0 5 2
A __init__() 0 13 1
B check_connect() 0 46 6
A can_connect() 0 16 2
B clear() 0 16 6
A sync_node_properties() 0 8 1
A add_annotation() 0 12 1
A is_terminal() 0 2 1
A set_runtime_env() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Orange.canvas.scheme.Scheme often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
===============
3
Scheme Workflow
4
===============
5
6
The :class:`Scheme` class defines a DAG (Directed Acyclic Graph) workflow.
7
8
"""
9
import types
10
from operator import itemgetter
11
from collections import deque
12
13
import logging
14
15
from PyQt4.QtCore import QObject
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtCore could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
16
from PyQt4.QtCore import pyqtSignal as Signal
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtCore could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
17
from PyQt4.QtCore import pyqtProperty as Property
0 ignored issues
show
Configuration introduced by
The import PyQt4.QtCore could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
18
19
from .node import SchemeNode
20
from .link import SchemeLink, compatible_channels
21
from .annotations import BaseSchemeAnnotation
22
23
from ..utils import check_arg, check_type
24
25
from .errors import (
26
    SchemeCycleError, IncompatibleChannelTypeError, SinkChannelError,
27
    DuplicatedLinkError
28
)
29
30
from . import readwrite
31
32
from ..registry import WidgetDescription, InputSignal, OutputSignal
0 ignored issues
show
Unused Code introduced by
Unused OutputSignal imported from registry
Loading history...
Unused Code introduced by
Unused InputSignal imported from registry
Loading history...
33
34
log = logging.getLogger(__name__)
35
36
37
class Scheme(QObject):
38
    """
39
    An :class:`QObject` subclass representing the scheme widget workflow
40
    with annotations.
41
42
    Parameters
43
    ----------
44
    parent : :class:`QObject`
45
        A parent QObject item (default `None`).
46
    title : str
47
        The scheme title.
48
    description : str
49
        A longer description of the scheme.
50
51
52
    Attributes
53
    ----------
54
    nodes : list of :class:`.SchemeNode`
55
        A list of all the nodes in the scheme.
56
57
    links : list of :class:`.SchemeLink`
58
        A list of all links in the scheme.
59
60
    annotations : list of :class:`BaseSchemeAnnotation`
61
        A list of all the annotations in the scheme.
62
63
    """
64
65
    # Signal emitted when a `node` is added to the scheme.
66
    node_added = Signal(SchemeNode)
67
68
    # Signal emitted when a `node` is removed from the scheme.
69
    node_removed = Signal(SchemeNode)
70
71
    # Signal emitted when a `link` is added to the scheme.
72
    link_added = Signal(SchemeLink)
73
74
    # Signal emitted when a `link` is removed from the scheme.
75
    link_removed = Signal(SchemeLink)
76
77
    # Signal emitted when a `annotation` is added to the scheme.
78
    annotation_added = Signal(BaseSchemeAnnotation)
79
80
    # Signal emitted when a `annotation` is removed from the scheme.
81
    annotation_removed = Signal(BaseSchemeAnnotation)
82
83
    # Signal emitted when the title of scheme changes.
84
    title_changed = Signal(str)
85
86
    # Signal emitted when the description of scheme changes.
87
    description_changed = Signal(str)
88
89
    node_state_changed = Signal()
90
    channel_state_changed = Signal()
91
    topology_changed = Signal()
92
93
    #: Emitted when the associated runtime environment changes
94
    #: runtime_env_changed(key: str, newvalue: Option[str],
95
    #:                     oldvalue: Option[str])
96
    runtime_env_changed = Signal(str, object, object)
97
98
    def __init__(self, parent=None, title=None, description=None, env={}):
0 ignored issues
show
Bug Best Practice introduced by
The default value {} might cause unintended side-effects.

Objects as default values are only created once in Python and not on each invocation of the function. If the default object is modified, this modification is carried over to the next invocation of the method.

# Bad:
# If array_param is modified inside the function, the next invocation will
# receive the modified object.
def some_function(array_param=[]):
    # ...

# Better: Create an array on each invocation
def some_function(array_param=None):
    array_param = array_param or []
    # ...
Loading history...
99
        QObject.__init__(self, parent)
100
101
        self.__title = title or ""
102
        "Workflow title (empty string by default)."
103
104
        self.__description = description or ""
105
        "Workflow description (empty string by default)."
106
107
        self.__annotations = []
108
        self.__nodes = []
109
        self.__links = []
110
        self.__env = dict(env)
111
112
    @property
113
    def nodes(self):
114
        """
115
        A list of all nodes (:class:`.SchemeNode`) currently in the scheme.
116
        """
117
        return list(self.__nodes)
118
119
    @property
120
    def links(self):
121
        """
122
        A list of all links (:class:`.SchemeLink`) currently in the scheme.
123
        """
124
        return list(self.__links)
125
126
    @property
127
    def annotations(self):
128
        """
129
        A list of all annotations (:class:`.BaseSchemeAnnotation`) in the
130
        scheme.
131
132
        """
133
        return list(self.__annotations)
134
135
    def set_title(self, title):
136
        """
137
        Set the scheme title text.
138
        """
139
        if self.__title != title:
140
            self.__title = title
141
            self.title_changed.emit(title)
142
143
    def title(self):
144
        """
145
        The title (human readable string) of the scheme.
146
        """
147
        return self.__title
148
149
    title = Property(str, fget=title, fset=set_title)
150
151
    def set_description(self, description):
152
        """
153
        Set the scheme description text.
154
        """
155
        if self.__description != description:
156
            self.__description = description
157
            self.description_changed.emit(description)
158
159
    def description(self):
160
        """
161
        Scheme description text.
162
        """
163
        return self.__description
164
165
    description = Property(str, fget=description, fset=set_description)
166
167
    def add_node(self, node):
168
        """
169
        Add a node to the scheme. An error is raised if the node is
170
        already in the scheme.
171
172
        Parameters
173
        ----------
174
        node : :class:`.SchemeNode`
175
            Node instance to add to the scheme.
176
177
        """
178
        check_arg(node not in self.__nodes,
179
                  "Node already in scheme.")
180
        check_type(node, SchemeNode)
181
182
        self.__nodes.append(node)
183
        log.info("Added node %r to scheme %r." % (node.title, self.title))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
184
        self.node_added.emit(node)
185
186
    def new_node(self, description, title=None, position=None,
187
                 properties=None):
188
        """
189
        Create a new :class:`.SchemeNode` and add it to the scheme.
190
191
        Same as::
192
193
            scheme.add_node(SchemeNode(description, title, position,
194
                                       properties))
195
196
        Parameters
197
        ----------
198
        description : :class:`WidgetDescription`
199
            The new node's description.
200
        title : str, optional
201
            Optional new nodes title. By default `description.name` is used.
202
        position : `(x, y)` tuple of floats, optional
203
            Optional position in a 2D space.
204
        properties : dict, optional
205
            A dictionary of optional extra properties.
206
207
        See also
208
        --------
209
        .SchemeNode, Scheme.add_node
210
211
        """
212
        if isinstance(description, WidgetDescription):
213
            node = SchemeNode(description, title=title, position=position,
214
                              properties=properties)
215
        else:
216
            raise TypeError("Expected %r, got %r." % \
217
                            (WidgetDescription, type(description)))
218
219
        self.add_node(node)
220
        return node
221
222
    def remove_node(self, node):
223
        """
224
        Remove a `node` from the scheme. All links into and out of the
225
        `node` are also removed. If the node in not in the scheme an error
226
        is raised.
227
228
        Parameters
229
        ----------
230
        node : :class:`.SchemeNode`
231
            Node instance to remove.
232
233
        """
234
        check_arg(node in self.__nodes,
235
                  "Node is not in the scheme.")
236
237
        self.__remove_node_links(node)
238
        self.__nodes.remove(node)
239
        log.info("Removed node %r from scheme %r." % (node.title, self.title))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
240
        self.node_removed.emit(node)
241
        return node
242
243
    def __remove_node_links(self, node):
244
        """
245
        Remove all links for node.
246
        """
247
        links_in, links_out = [], []
248
        for link in self.__links:
249
            if link.source_node is node:
250
                links_out.append(link)
251
            elif link.sink_node is node:
252
                links_in.append(link)
253
254
        for link in links_out + links_in:
255
            self.remove_link(link)
256
257
    def add_link(self, link):
258
        """
259
        Add a `link` to the scheme.
260
261
        Parameters
262
        ----------
263
        link : :class:`.SchemeLink`
264
            An initialized link instance to add to the scheme.
265
266
        """
267
        check_type(link, SchemeLink)
268
269
        self.check_connect(link)
270
        self.__links.append(link)
271
272
        log.info("Added link %r (%r) -> %r (%r) to scheme %r." % \
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
273
                 (link.source_node.title, link.source_channel.name,
274
                  link.sink_node.title, link.sink_channel.name,
275
                  self.title)
276
                 )
277
278
        self.link_added.emit(link)
279
280
    def new_link(self, source_node, source_channel,
281
                 sink_node, sink_channel):
282
        """
283
        Create a new :class:`.SchemeLink` from arguments and add it to
284
        the scheme. The new link is returned.
285
286
        Parameters
287
        ----------
288
        source_node : :class:`.SchemeNode`
289
            Source node of the new link.
290
        source_channel : :class:`.OutputSignal`
291
            Source channel of the new node. The instance must be from
292
            ``source_node.output_channels()``
293
        sink_node : :class:`.SchemeNode`
294
            Sink node of the new link.
295
        sink_channel : :class:`.InputSignal`
296
            Sink channel of the new node. The instance must be from
297
            ``sink_node.input_channels()``
298
299
        See also
300
        --------
301
        .SchemeLink, Scheme.add_link
302
303
        """
304
        link = SchemeLink(source_node, source_channel,
305
                          sink_node, sink_channel)
306
        self.add_link(link)
307
        return link
308
309
    def remove_link(self, link):
310
        """
311
        Remove a link from the scheme.
312
313
        Parameters
314
        ----------
315
        link : :class:`.SchemeLink`
316
            Link instance to remove.
317
318
        """
319
        check_arg(link in self.__links,
320
                  "Link is not in the scheme.")
321
322
        self.__links.remove(link)
323
        log.info("Removed link %r (%r) -> %r (%r) from scheme %r." % \
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
324
                 (link.source_node.title, link.source_channel.name,
325
                  link.sink_node.title, link.sink_channel.name,
326
                  self.title)
327
                 )
328
        self.link_removed.emit(link)
329
330
    def check_connect(self, link):
331
        """
332
        Check if the `link` can be added to the scheme and raise an
333
        appropriate exception.
334
335
        Can raise:
336
            - :class:`TypeError` if `link` is not an instance of
337
              :class:`.SchemeLink`
338
            - :class:`.SchemeCycleError` if the `link` would introduce a cycle
339
            - :class:`.IncompatibleChannelTypeError` if the channel types are
340
              not compatible
341
            - :class:`.SinkChannelError` if a sink channel has a `Single` flag
342
              specification and the channel is already connected.
343
            - :class:`.DuplicatedLinkError` if a `link` duplicates an already
344
              present link.
345
346
        """
347
        check_type(link, SchemeLink)
348
349
        if self.creates_cycle(link):
350
            raise SchemeCycleError("Cannot create cycles in the scheme")
351
352
        if not self.compatible_channels(link):
353
            raise IncompatibleChannelTypeError(
354
                    "Cannot connect %r to %r." \
355
                    % (link.source_channel.type, link.sink_channel.type)
356
                )
357
358
        links = self.find_links(source_node=link.source_node,
359
                                source_channel=link.source_channel,
360
                                sink_node=link.sink_node,
361
                                sink_channel=link.sink_channel)
362
363
        if links:
364
            raise DuplicatedLinkError(
365
                    "A link from %r (%r) -> %r (%r) already exists" \
366
                    % (link.source_node.title, link.source_channel.name,
367
                       link.sink_node.title, link.sink_channel.name)
368
                )
369
370
        if link.sink_channel.single:
371
            links = self.find_links(sink_node=link.sink_node,
372
                                    sink_channel=link.sink_channel)
373
            if links:
374
                raise SinkChannelError(
375
                        "%r is already connected." % link.sink_channel.name
376
                    )
377
378
    def creates_cycle(self, link):
379
        """
380
        Return `True` if `link` would introduce a cycle in the scheme.
381
382
        Parameters
383
        ----------
384
        link : :class:`.SchemeLink`
385
386
        """
387
        check_type(link, SchemeLink)
388
        source_node, sink_node = link.source_node, link.sink_node
389
        upstream = self.upstream_nodes(source_node)
390
        upstream.add(source_node)
391
        return sink_node in upstream
392
393
    def compatible_channels(self, link):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
394
        """
395
        Return `True` if the channels in `link` have compatible types.
396
397
        Parameters
398
        ----------
399
        link : :class:`.SchemeLink`
400
401
        """
402
        check_type(link, SchemeLink)
403
        return compatible_channels(link.source_channel, link.sink_channel)
404
405
    def can_connect(self, link):
406
        """
407
        Return `True` if `link` can be added to the scheme.
408
409
        See also
410
        --------
411
        Scheme.check_connect
412
413
        """
414
        check_type(link, SchemeLink)
415
        try:
416
            self.check_connect(link)
417
            return True
418
        except (SchemeCycleError, IncompatibleChannelTypeError,
419
                SinkChannelError, DuplicatedLinkError):
420
            return False
421
422
    def upstream_nodes(self, start_node):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
423
        """
424
        Return a set of all nodes upstream from `start_node` (i.e.
425
        all ancestor nodes).
426
427
        Parameters
428
        ----------
429
        start_node : :class:`.SchemeNode`
430
431
        """
432
        visited = set()
433
        queue = deque([start_node])
434
        while queue:
435
            node = queue.popleft()
436
            snodes = [link.source_node for link in self.input_links(node)]
437
            for source_node in snodes:
438
                if source_node not in visited:
439
                    queue.append(source_node)
440
441
            visited.add(node)
442
        visited.remove(start_node)
443
        return visited
444
445
    def downstream_nodes(self, start_node):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
446
        """
447
        Return a set of all nodes downstream from `start_node`.
448
449
        Parameters
450
        ----------
451
        start_node : :class:`.SchemeNode`
452
453
        """
454
        visited = set()
455
        queue = deque([start_node])
456
        while queue:
457
            node = queue.popleft()
458
            snodes = [link.sink_node for link in self.output_links(node)]
459
            for source_node in snodes:
460
                if source_node not in visited:
461
                    queue.append(source_node)
462
463
            visited.add(node)
464
        visited.remove(start_node)
465
        return visited
466
467
    def is_ancestor(self, node, child):
468
        """
469
        Return True if `node` is an ancestor node of `child` (is upstream
470
        of the child in the workflow). Both nodes must be in the scheme.
471
472
        Parameters
473
        ----------
474
        node : :class:`.SchemeNode`
475
        child : :class:`.SchemeNode`
476
477
        """
478
        return child in self.downstream_nodes(node)
479
480
    def children(self, node):
481
        """
482
        Return a set of all children of `node`.
483
        """
484
        return set(link.sink_node for link in self.output_links(node))
485
486
    def parents(self, node):
487
        """
488
        Return a set of all parents of `node`.
489
        """
490
        return set(link.source_node for link in self.input_links(node))
491
492
    def input_links(self, node):
493
        """
494
        Return a list of all input links (:class:`.SchemeLink`) connected
495
        to the `node` instance.
496
497
        """
498
        return self.find_links(sink_node=node)
499
500
    def output_links(self, node):
501
        """
502
        Return a list of all output links (:class:`.SchemeLink`) connected
503
        to the `node` instance.
504
505
        """
506
        return self.find_links(source_node=node)
507
508
    def find_links(self, source_node=None, source_channel=None,
509
                   sink_node=None, sink_channel=None):
510
        # TODO: Speedup - keep index of links by nodes and channels
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
511
        result = []
512
        match = lambda query, value: (query is None or value == query)
513
        for link in self.__links:
514
            if match(source_node, link.source_node) and \
515
                    match(sink_node, link.sink_node) and \
516
                    match(source_channel, link.source_channel) and \
517
                    match(sink_channel, link.sink_channel):
518
                result.append(link)
519
520
        return result
521
522
    def propose_links(self, source_node, sink_node):
523
        """
524
        Return a list of ordered (:class:`OutputSignal`,
525
        :class:`InputSignal`, weight) tuples that could be added to
526
        the scheme between `source_node` and `sink_node`.
527
528
        .. note:: This can depend on the links already in the scheme.
529
530
        """
531
        if source_node is sink_node or \
532
                self.is_ancestor(sink_node, source_node):
533
            # Cyclic connections are not possible.
534
            return []
535
536
        outputs = source_node.output_channels()
537
        inputs = sink_node.input_channels()
538
539
        # Get existing links to sink channels that are Single.
540
        links = self.find_links(None, None, sink_node)
541
        already_connected_sinks = [link.sink_channel for link in links \
542
                                   if link.sink_channel.single]
543
544
        def weight(out_c, in_c):
545
            if out_c.explicit or in_c.explicit:
546
                # Zero weight for explicit links
547
                weight = 0
548
            else:
549
                check = [not out_c.dynamic,  # Dynamic signals are last
550
                         in_c not in already_connected_sinks,
551
                         bool(in_c.default),
552
                         bool(out_c.default)
553
                         ]
554
                weights = [2 ** i for i in range(len(check), 0, -1)]
555
                weight = sum([w for w, c in zip(weights, check) if c])
556
            return weight
557
558
        proposed_links = []
559
        for out_c in outputs:
560
            for in_c in inputs:
561
                if compatible_channels(out_c, in_c):
562
                    proposed_links.append((out_c, in_c, weight(out_c, in_c)))
563
564
        return sorted(proposed_links, key=itemgetter(-1), reverse=True)
565
566
    def add_annotation(self, annotation):
567
        """
568
        Add an annotation (:class:`BaseSchemeAnnotation` subclass) instance
569
        to the scheme.
570
571
        """
572
        check_arg(annotation not in self.__annotations,
573
                  "Cannot add the same annotation multiple times.")
574
        check_type(annotation, BaseSchemeAnnotation)
575
576
        self.__annotations.append(annotation)
577
        self.annotation_added.emit(annotation)
578
579
    def remove_annotation(self, annotation):
580
        """
581
        Remove the `annotation` instance from the scheme.
582
        """
583
        check_arg(annotation in self.__annotations,
584
                  "Annotation is not in the scheme.")
585
        self.__annotations.remove(annotation)
586
        self.annotation_removed.emit(annotation)
587
588
    def clear(self):
589
        """
590
        Remove all nodes, links, and annotation items from the scheme.
591
        """
592
        def is_terminal(node):
593
            return not bool(self.find_links(source_node=node))
594
595
        while self.nodes:
596
            terminal_nodes = list(filter(is_terminal, self.nodes))
597
            for node in terminal_nodes:
598
                self.remove_node(node)
599
600
        for annotation in self.annotations:
601
            self.remove_annotation(annotation)
602
603
        assert(not (self.nodes or self.links or self.annotations))
0 ignored issues
show
Unused Code Coding Style introduced by
There is an unnecessary parenthesis after assert.
Loading history...
604
605
    def sync_node_properties(self):
606
        """
607
        Called before saving, allowing a subclass to update/sync.
608
609
        The default implementation does nothing.
610
611
        """
612
        pass
613
614
    def save_to(self, stream, pretty=True, pickle_fallback=False):
615
        """
616
        Save the scheme as an xml formated file to `stream`
617
618
        See also
619
        --------
620
        .scheme_to_ows_stream
621
622
        """
623
        if isinstance(stream, str):
624
            stream = open(stream, "wb")
625
626
        self.sync_node_properties()
627
628
        readwrite.scheme_to_ows_stream(self, stream, pretty,
629
                                       pickle_fallback=pickle_fallback)
630
631
    def load_from(self, stream):
632
        """
633
        Load the scheme from xml formated stream.
634
        """
635
        if self.__nodes or self.__links or self.__annotations:
636
            # TODO: should we clear the scheme and load it.
0 ignored issues
show
Coding Style introduced by
TODO and FIXME comments should generally be avoided.
Loading history...
637
            raise ValueError("Scheme is not empty.")
638
639
        if isinstance(stream, str):
640
            stream = open(stream, "rb")
641
        readwrite.scheme_load(self, stream)
642
#         parse_scheme(self, stream)
643
644
    def set_runtime_env(self, key, value):
645
        """
646
        Set a runtime environment variable `key` to `value`
647
        """
648
        oldvalue = self.__env.get(key, None)
649
        if value != oldvalue:
650
            self.__env[key] = value
651
            self.runtime_env_changed.emit(key, value, oldvalue)
652
653
    def get_runtime_env(self, key, default=None):
654
        """
655
        Return a runtime environment variable for `key`.
656
        """
657
        return self.__env.get(key, default)
658
659
    def runtime_env(self):
660
        """
661
        Return (a view to) the full runtime environment.
662
663
        The return value is a types.MappingProxyType of the
664
        underlying environment dictionary. Changes to the env.
665
        will be reflected in it.
666
        """
667
        return types.MappingProxyType(self.__env)
668