Skip to content

Commit 4ce2e1b

Browse files
authored
Merge pull request #304 from mpsonntag/keepId
2 parents 040fe23 + f99896f commit 4ce2e1b

9 files changed

Lines changed: 98 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@ XML files via lxml. See #301.
2424
The `odmlconversion` convenience console script has been added to convert multiple
2525
previous odML version files to the latest odML version.
2626

27+
## Changes in cloning behaviour
28+
29+
When cloning a `Section` or a `Property` by default the id of any object is changed
30+
to a new UUID. The cloning methods now feature a new `keep_id` attribute. If set to
31+
`True`, the cloned object and any cloned children retain their original id. This
32+
is meant to create exact copies of Section-Property trees in different documents.
33+
34+
## Additional validation
35+
36+
When a document is saved, a new validation check makes sure, that a document
37+
contains only unique UUIDs this is required due to the introduction of creating
38+
clones with identical ids.
39+
2740

2841
# Version 1.4.1
2942

odml/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ def clean(self):
581581
for i in self:
582582
i.clean()
583583

584-
def clone(self, children=True):
584+
def clone(self, children=True, keep_id=False):
585585
"""
586586
Clone this object recursively allowing to copy it independently
587587
to another document
@@ -592,7 +592,7 @@ def clone(self, children=True):
592592
obj._sections = SmartList(BaseSection)
593593
if children:
594594
for s in self._sections:
595-
obj.append(s.clone())
595+
obj.append(s.clone(keep_id=keep_id))
596596

597597
return obj
598598

odml/property.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,15 +407,20 @@ def get_path(self):
407407

408408
return self.parent.get_path() + ":" + self.name
409409

410-
def clone(self):
410+
def clone(self, keep_id=False):
411411
"""
412-
Clone this object to copy it independently to another document.
413-
The id of the cloned object will be set to a different uuid.
412+
Clone this property to copy it independently to another document.
413+
By default the id of the cloned object will be set to a different uuid.
414+
415+
:param keep_id: If this attribute is set to True, the uuid of the
416+
object will remain unchanged.
417+
:return: The cloned property
414418
"""
415419
obj = super(BaseProperty, self).clone()
416420
obj._parent = None
417421
obj.value = self._value
418-
obj._id = str(uuid.uuid4())
422+
if not keep_id:
423+
obj.new_id()
419424

420425
return obj
421426

odml/section.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -390,18 +390,26 @@ def remove(self, obj):
390390
else:
391391
raise ValueError("Can only remove sections and properties")
392392

393-
def clone(self, children=True):
393+
def clone(self, children=True, keep_id=False):
394394
"""
395-
Clone this object recursively allowing to copy it independently
396-
to another document
395+
Clone this Section allowing to copy it independently
396+
to another document. By default the id of any cloned
397+
object will be set to a new uuid.
398+
399+
:param children: If True, also clone child sections and properties
400+
recursively.
401+
:param keep_id: If this attribute is set to True, the uuids of the
402+
Section and all child objects will remain unchanged.
403+
:return: The cloned Section.
397404
"""
398-
obj = super(BaseSection, self).clone(children)
399-
obj._id = str(uuid.uuid4())
405+
obj = super(BaseSection, self).clone(children, keep_id)
406+
if not keep_id:
407+
obj.new_id()
400408

401409
obj._props = base.SmartList(BaseProperty)
402410
if children:
403411
for p in self._props:
404-
obj.append(p.clone())
412+
obj.append(p.clone(keep_id))
405413

406414
return obj
407415

odml/tools/odmlparser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def write_file(self, odml_document, filename):
5050
msg = ""
5151
for err in validation.errors:
5252
if err.is_error:
53-
msg += "\n\t- %s %s: %s" % (err.obj, err.type, err.msg)
53+
msg += "\n\t- %s %s: %s" % (err.obj, err.rank, err.msg)
5454
if msg != "":
5555
msg = "Resolve document validation errors before saving %s" % msg
5656
raise ParserException(msg)

odml/validation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def section_unique_ids(parent, id_map=None):
174174
yield i
175175

176176
if sec.id in id_map:
177-
yield ValidationError(sec, "Duplicate id in Section '%s' and '%s'" %
177+
yield ValidationError(sec, "Duplicate id in Section '%s' and %s" %
178178
(sec.get_path(), id_map[sec.id]))
179179
else:
180180
id_map[sec.id] = "Section '%s'" % sec.get_path()
@@ -203,7 +203,7 @@ def property_unique_ids(section, id_map=None):
203203

204204
for prop in section.properties:
205205
if prop.id in id_map:
206-
yield ValidationError(prop, "Duplicate id in Property '%s' and '%s'" %
206+
yield ValidationError(prop, "Duplicate id in Property '%s' and %s" %
207207
(prop.get_path(), id_map[prop.id]))
208208
else:
209209
id_map[prop.id] = "Property '%s'" % prop.get_path()

test/test_property.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,11 @@ def test_clone(self):
623623
self.assertIsNotNone(prop.parent)
624624
self.assertIsNone(clone_prop.parent)
625625

626+
# Check keep_id
627+
prop = Property(name="keepid")
628+
clone_prop = prop.clone(True)
629+
self.assertEqual(prop.id, clone_prop.id)
630+
626631
def test_get_merged_equivalent(self):
627632
sec = Section(name="parent")
628633
mersec = Section(name="merged_section")

test/test_section.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,30 @@ def test_clone(self):
274274
self.assertListEqual(clone_sec.sections, [])
275275
self.assertListEqual(clone_sec.properties, [])
276276

277+
def test_clone_keep_id(self):
278+
# Check id kept in clone.
279+
sec = Section(name="original")
280+
clone_sec = sec.clone(keep_id=True)
281+
self.assertEqual(sec, clone_sec)
282+
self.assertEqual(sec.id, clone_sec.id)
283+
284+
# Check cloned child Sections keep their ids.
285+
Section(name="sec_one", parent=sec)
286+
Section(name="sec_two", parent=sec)
287+
clone_sec = sec.clone(keep_id=True)
288+
self.assertListEqual(sec.sections, clone_sec.sections)
289+
self.assertEqual(sec.sections["sec_one"], clone_sec.sections["sec_one"])
290+
self.assertEqual(sec.sections["sec_one"].id, clone_sec.sections["sec_one"].id)
291+
292+
# Check cloned child Properties keep their ids.
293+
Property(name="prop_one", parent=sec)
294+
Property(name="prop_two", parent=sec)
295+
clone_sec = sec.clone(keep_id=True)
296+
self.assertListEqual(sec.properties, clone_sec.properties)
297+
self.assertEqual(sec.properties["prop_one"], clone_sec.properties["prop_one"])
298+
self.assertEqual(sec.properties["prop_one"].id,
299+
clone_sec.properties["prop_one"].id)
300+
277301
def test_reorder(self):
278302
# Test reorder of document sections
279303
doc = Document()

test/test_validation.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,31 @@ def test_property_values(self):
9898
p.dependency = "p2"
9999
res = validate(doc)
100100
self.assertError(res, "non-existent dependency object")
101+
102+
def test_property_unique_ids(self):
103+
"""
104+
Test if identical ids in properties raise a validation error
105+
"""
106+
doc = odml.Document()
107+
sec_one = odml.Section("sec1", parent=doc)
108+
sec_two = odml.Section("sec2", parent=doc)
109+
prop = odml.Property("prop", parent=sec_one)
110+
111+
cprop = prop.clone(keep_id=True)
112+
sec_two.append(cprop)
113+
114+
res = validate(doc)
115+
self.assertError(res, "Duplicate id in Property")
116+
117+
def test_section_unique_ids(self):
118+
"""
119+
Test if identical ids in sections raise a validation error.
120+
"""
121+
doc = odml.Document()
122+
sec = odml.Section("sec", parent=doc)
123+
124+
csec = sec.clone(keep_id=True)
125+
sec.append(csec)
126+
127+
res = validate(doc)
128+
self.assertError(res, "Duplicate id in Section")

0 commit comments

Comments
 (0)