Skip to content

Commit 6daf752

Browse files
committed
Set parent relationship on adding object to list
Note that this removes the need for `create_relationship()` except with deepcopy.
1 parent 8baaeb3 commit 6daf752

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+110
-166
lines changed

doc/source/grouping.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ Using :class:`ChannelIndex`::
3434

3535
block = Block()
3636
segment = Segment()
37-
segment.block = block
3837
block.segments.append(segment)
3938

4039
for i in (0, 1):
@@ -55,7 +54,6 @@ Using array annotations, we annotate the channels of the :class:`AnalogSignal` d
5554

5655
block = Block()
5756
segment = Segment()
58-
segment.block = block
5957
block.segments.append(segment)
6058

6159
for i in (0, 1):
@@ -77,7 +75,6 @@ Each :class:`ChannelIndex` also contains the list of channels on which that neur
7775

7876
block = Block(name="probe data")
7977
segment = Segment()
80-
segment.block = block
8178
block.segments.append(segment)
8279

8380
# create 4-channel AnalogSignal with dummy data
@@ -119,7 +116,6 @@ Using :class:`ChannelView` and :class:`Group`::
119116

120117
block = Block(name="probe data")
121118
segment = Segment()
122-
segment.block = block
123119
block.segments.append(segment)
124120

125121
# create 4-channel AnalogSignal with dummy data

doc/source/io_developers_guide.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ Miscellaneous
3838
=============
3939

4040
* If your IO supports several versions of a format (like ABF1, ABF2), upload to the gin.g-node.org test file repository all file versions possible. (for test coverage).
41-
* :py:func:`neo.core.Block.create_relationship` offers a utility to complete the hierarchy when all one-to-many relationships have been created.
4241
* In the docstring, explain where you obtained the file format specification if it is a closed one.
4342
* If your IO is based on a database mapper, keep in mind that the returned object MUST be detached,
4443
because this object can be written to another url for copying.

doc/source/scripts/multi_tetrode_example.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535

3636
# Create dummy data, one segment at a time
3737
for segment in block.segments:
38-
segment.block = block
3938

4039
# create two 4-channel AnalogSignals with dummy data
4140
signals = {
@@ -46,8 +45,6 @@
4645
}
4746
if store_signals:
4847
segment.analogsignals.extend(signals.values())
49-
for signal in signals:
50-
signal.segment = segment
5148

5249
# create spike trains with dummy data
5350
# we will pretend the spikes have been extracted from the dummy signal
@@ -56,7 +53,6 @@
5653
spiketrain = SpikeTrain(np.random.uniform(0, 100, size=30) * ms, t_stop=100 * ms)
5754
# assign each spiketrain to the appropriate segment
5855
segment.spiketrains.append(spiketrain)
59-
spiketrain.segment = segment
6056
# assign each spiketrain to a given neuron
6157
current_group = next(iter_group)
6258
current_group.add(spiketrain)

examples/generated_data.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ def generate_block(n_segments=3, n_channels=4, n_units=3,
5656
seg.spiketrains.append(train)
5757
u.spiketrains.append(train)
5858

59-
block.create_relationship()
6059
return block
6160

6261

neo/core/block.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ def __init__(self, name=None, description=None, file_origin=None,
8989
self.file_datetime = file_datetime
9090
self.rec_datetime = rec_datetime
9191
self.index = index
92-
self._segments = ObjectList(Segment)
93-
self._groups = ObjectList(Group)
94-
self._regionsofinterest = ObjectList(RegionOfInterest)
92+
self._segments = ObjectList(Segment, parent=self)
93+
self._groups = ObjectList(Group, parent=self)
94+
self._regionsofinterest = ObjectList(RegionOfInterest, parent=self)
9595

9696
segments = property(
9797
fget=lambda self: self._get_object_list("_segments"),

neo/core/container.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ class Container(BaseNeo):
103103
Each class can define one or more of the following class attributes
104104
(in addition to those of BaseNeo):
105105
:_container_child_objects: Neo container objects that can be children
106-
of this object. This attribute is used in
107-
cases where the child can only have one
108-
parent of this type. An instance attribute
106+
of this object. An instance attribute
109107
named class.__name__.lower()+'s' will be
110108
automatically defined to hold this child and
111109
will be initialized to an empty list.
@@ -119,8 +117,7 @@ class Container(BaseNeo):
119117
120118
The following helper properties are available
121119
(in addition to those of BaseNeo):
122-
:_child_objects: All neo container objects that can be children
123-
of this object.
120+
:_child_objects: All neo objects that can be children of this object.
124121
:_container_child_objects: +
125122
:_data_child_objects:
126123
:_container_child_containers: The names of the container attributes
@@ -183,9 +180,9 @@ class Container(BaseNeo):
183180
2) process its non-universal recommended arguments (in its __new__ or
184181
__init__ method
185182
"""
186-
# Child objects that are a container and have a single parent
183+
# Child objects that are a container
187184
_container_child_objects = ()
188-
# Child objects that have data and have a single parent
185+
# Child objects that have data
189186
_data_child_objects = ()
190187
# Containers that are listed when pretty-printing
191188
_repr_pretty_containers = ()
@@ -200,13 +197,13 @@ def __init__(self, name=None, description=None, file_origin=None,
200197

201198
def _get_object_list(self, name):
202199
"""
203-
200+
todo
204201
"""
205202
return getattr(self, name)
206203

207204
def _set_object_list(self, name, value):
208205
"""
209-
206+
todo
210207
"""
211208
assert isinstance(value, list)
212209
object_list = getattr(self, name)
@@ -397,6 +394,17 @@ def list_children_by_class(self, cls):
397394
objs.extend(getattr(child, container_name, []))
398395
return objs
399396

397+
def check_relationships(self, recursive=True):
398+
"""
399+
Check that the expected child-parent relationships exist.
400+
"""
401+
parent_name = _reference_name(self.__class__.__name__)
402+
for child in self._single_children:
403+
assert getattr(child, parent_name, None) is self
404+
if recursive:
405+
for child in self.container_children:
406+
child.check_relationships(recursive=True)
407+
400408
def create_relationship(self, force=False, recursive=True):
401409
"""
402410
For each child of the current object that can only have a single

neo/core/group.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,18 @@ def __init__(self, objects=None, name=None, description=None, file_origin=None,
5959
super().__init__(name=name, description=description,
6060
file_origin=file_origin, **annotations)
6161

62+
# note that we create the ObjectLists here _without_ a parent argument
63+
# since objects do not have a reference to the group(s)
64+
# they are contained in.
6265
self._analogsignals = ObjectList(AnalogSignal)
6366
self._irregularlysampledsignals = ObjectList(IrregularlySampledSignal)
64-
self.spiketrains = SpikeTrainList(segment=self)
67+
self._spiketrains = SpikeTrainList(parent=self)
6568
self._events = ObjectList(Event)
6669
self._epochs = ObjectList(Epoch)
6770
self._channelviews = ObjectList(ChannelView)
6871
self._imagesequences = ObjectList(ImageSequence)
69-
self.segments = ObjectList(Segment) # to remove?
70-
self.groups = ObjectList(Group)
72+
self._segments = ObjectList(Segment) # to remove?
73+
self._groups = ObjectList(Group)
7174

7275
if allowed_types is None:
7376
self.allowed_types = None
@@ -113,6 +116,23 @@ def __init__(self, objects=None, name=None, description=None, file_origin=None,
113116
doc="todo"
114117
)
115118

119+
spiketrains = property(
120+
fget=lambda self: self._get_object_list("_spiketrains"),
121+
fset=lambda self, value: self._set_object_list("_spiketrains", value),
122+
doc="todo"
123+
124+
segments = property(
125+
fget=lambda self: self._get_object_list("_segments"),
126+
fset=lambda self, value: self._set_object_list("_segments", value),
127+
doc="list of Segments contained in this group"
128+
)
129+
130+
groups = property(
131+
fget=lambda self: self._get_object_list("_groups"),
132+
fset=lambda self, value: self._set_object_list("_groups", value),
133+
doc="list of Groups contained in this group"
134+
)
135+
116136
@property
117137
def _container_lookup(self):
118138
return {

neo/core/objectlist.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class ObjectList:
1111
handle relationships within Neo hierarchy
1212
"""
1313

14-
def __init__(self, allowed_contents):
14+
def __init__(self, allowed_contents, parent=None):
1515
# validate allowed_contents and normalize it to a tuple
1616
if isinstance(allowed_contents, type) and issubclass(allowed_contents, BaseNeo):
1717
self.allowed_contents = (allowed_contents,)
@@ -20,6 +20,7 @@ def __init__(self, allowed_contents):
2020
assert issubclass(item, BaseNeo)
2121
self.allowed_contents = tuple(allowed_contents)
2222
self.contents = []
23+
self.parent = parent
2324

2425
def _handle_append(self, obj):
2526
if not (
@@ -29,6 +30,12 @@ def _handle_append(self, obj):
2930
)
3031
):
3132
raise TypeError(f"Object is a {type(obj)}. It should be one of {self.allowed_contents}.")
33+
# set the child-parent relationship
34+
if self.parent:
35+
relationship_name = self.parent.__class__.__name__.lower()
36+
current_parent = getattr(obj, relationship_name)
37+
if current_parent != self.parent:
38+
setattr(obj, relationship_name, self.parent)
3239

3340
def __str__(self):
3441
return str(self.contents)

neo/core/segment.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,13 @@ def __init__(self, name=None, description=None, file_origin=None,
9999
super().__init__(name=name, description=description,
100100
file_origin=file_origin, **annotations)
101101

102-
self._analogsignals = ObjectList(AnalogSignal)
103-
self._irregularlysampledsignals = ObjectList(IrregularlySampledSignal)
104-
self.spiketrains = SpikeTrainList(segment=self)
105-
self._events = ObjectList(Event)
106-
self._epochs = ObjectList(Epoch)
107-
self._channelviews = ObjectList(ChannelView)
108-
self._imagesequences = ObjectList(ImageSequence)
102+
self._analogsignals = ObjectList(AnalogSignal, parent=self)
103+
self._irregularlysampledsignals = ObjectList(IrregularlySampledSignal, parent=self)
104+
self._spiketrains = SpikeTrainList(parent=self)
105+
self._events = ObjectList(Event, parent=self)
106+
self._epochs = ObjectList(Epoch, parent=self)
107+
self._channelviews = ObjectList(ChannelView, parent=self)
108+
self._imagesequences = ObjectList(ImageSequence, parent=self)
109109
self.block = None
110110

111111
self.file_datetime = file_datetime
@@ -148,6 +148,12 @@ def __init__(self, name=None, description=None, file_origin=None,
148148
doc="todo"
149149
)
150150

151+
spiketrains = property(
152+
fget=lambda self: self._get_object_list("_spiketrains"),
153+
fset=lambda self, value: self._set_object_list("_spiketrains", value),
154+
doc="todo"
155+
)
156+
151157
# t_start attribute is handled as a property so type checking can be done
152158
@property
153159
def t_start(self):
@@ -286,6 +292,6 @@ def time_slice(self, t_start=None, t_stop=None, reset_time=False, **kwargs):
286292
if len(ep_time_slice):
287293
subseg.epochs.append(ep_time_slice)
288294

289-
subseg.create_relationship()
295+
subseg.check_relationships()
290296

291297
return subseg

neo/core/spiketrainlist.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class SpikeTrainList(ObjectList):
7575
7676
"""
7777

78-
def __init__(self, items=None, segment=None):
78+
def __init__(self, items=None, parent=None):
7979
"""Initialize self"""
8080
if items is None:
8181
self._items = items
@@ -89,7 +89,7 @@ def __init__(self, items=None, segment=None):
8989
self._channel_id_array = None
9090
self._all_channel_ids = None
9191
self._spiketrain_metadata = {}
92-
self.segment = segment
92+
self.segment = parent
9393

9494
def __iter__(self):
9595
"""Implement iter(self)"""
@@ -183,7 +183,7 @@ def __add__(self, other):
183183
return self._add_spiketrainlists(other)
184184
elif other and is_spiketrain_or_proxy(other[0]):
185185
return self._add_spiketrainlists(
186-
self.__class__(items=other, segment=self.segment)
186+
self.__class__(items=other, parent=self.segment)
187187
)
188188
else:
189189
if self._items is None:

0 commit comments

Comments
 (0)