Skip to content

Commit 206cbba

Browse files
authored
Merge pull request #385 from mpsonntag/cleanUpdate
General updates and PEP8 code cleanup
2 parents dd29249 + 76b3f8d commit 206cbba

29 files changed

+1119
-920
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
![Travis build](https://travis-ci.org/G-Node/python-odml.svg?branch=master)
1+
[![Travis build](https://travis-ci.org/G-Node/python-odml.svg?branch=master)](https://travis-ci.org/G-Node/python-odml)
22
[![Build status](https://ci.appveyor.com/api/projects/status/br7pe6atlwdg5618/branch/master?svg=true)](https://ci.appveyor.com/project/G-Node/python-odml/branch/master)
3-
![Test coverage](https://coveralls.io/repos/github/G-Node/python-odml/badge.svg?branch=master)
3+
[![Test coverage](https://coveralls.io/repos/github/G-Node/python-odml/badge.svg?branch=master)](https://coveralls.io/github/G-Node/python-odml)
44
[![PyPI version](https://img.shields.io/pypi/v/odml.svg)](https://pypi.org/project/odML/)
55
[![Read the Docs](https://img.shields.io/readthedocs/python-odml)](https://python-odml.readthedocs.io/en/latest/)
66

doc/tutorial.rst

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@ experimental project and complements the special needs of your laboratory.
7070
The code for the example odML files, which we use within this tutorial is part
7171
of the documentation package (see doc/example_odMLs/).
7272

73-
A summary of available odML terminologies and templates can be found `here
74-
<https://terminologies.g-node.org/v1.1/terminologies.xml>`_.
73+
A summary of available odML terminologies and templates can be found at the G-Node `odML terminology
74+
<https://terminologies.g-node.org/v1.1/terminologies.xml>`_ and `odML template
75+
<https://templates.g-node.org/>`_ pages.
7576

7677
-------------------------------------------------------------------------------
7778

@@ -311,7 +312,6 @@ following command::
311312
As expected from the Document printout our example contains two Sections. The
312313
printout and attributes of a Section are explained in the next chapter.
313314

314-
315315
The Sections
316316
------------
317317

@@ -553,6 +553,18 @@ returned list will have no affect on the actual Property values. If you want to
553553
make changes to a Property value, either use the ``append``, ``extend`` and ``remove``
554554
methods or assign a new value list to the property.
555555

556+
Printing overviews to navigate the contents of an odML document
557+
---------------------------------------------------------------
558+
559+
The odML entities ``Property``, ``Section`` and ``Document`` feature
560+
a method that allows to print a tree-like representation of
561+
all child entities to get an overview of the file structure.
562+
563+
>>> MYodML.pprint()
564+
>>> sec = MYodML['TheCrew']
565+
>>> sec.pprint()
566+
>>> prop = odmlEX['TheCrew'].properties['NameCrewMembers']
567+
>>> prop.pprint()
556568

557569
-------------------------------------------------------------------------------
558570

@@ -930,6 +942,48 @@ Also note that any style that is saved with an odML document will be lost, when
930942
document is loaded again and changes to the content are added. In this case the required
931943
style needs to be specified again when saving the changed file as described above.
932944

945+
946+
Defining and working with feature cardinality
947+
---------------------------------------------
948+
949+
The odML format allows users to define a cardinality for
950+
the number of subsections and properties of Sections and
951+
the number of values a Property might have.
952+
953+
A cardinality is checked when it is set, when its target is
954+
set and when a document is saved or loaded. If a specific
955+
cardinality is violated, a corresponding warning will be printed.
956+
957+
Setting a cardinality
958+
*********************
959+
960+
A cardinality can be set for sections or properties of sections
961+
or for values of properties. By default every cardinality is None,
962+
but it can be set to a defined minimal and/or a maximal number of
963+
an element.
964+
965+
A cardinality is set via its convenience method:
966+
967+
>>> # Set the cardinality of the properties of a Section 'sec' to
968+
>>> # a maximum of 5 elements.
969+
>>> sec = odml.Section(name="cardinality", type="test")
970+
>>> sec.set_properties_cardinality(max_val=5)
971+
972+
>>> # Set the cardinality of the subsections of Section 'sec' to
973+
>>> # a minimum of one and a maximum of 2 elements.
974+
>>> sec.set_sections_cardinality(min_val=1, max_val=2)
975+
976+
>>> # Set the cardinality of the values of a Property 'prop' to
977+
>>> # a minimum of 1 element.
978+
>>> prop = odml.Property(name="cardinality")
979+
>>> prop.set_values_cardinality(min_val=1)
980+
981+
>>> # Re-set the cardinality of the values of a Property 'prop' to not set.
982+
>>> prop.set_values_cardinality()
983+
>>> # or
984+
>>> prop.val_cardinality = None
985+
986+
933987
Advanced knowledge on Values
934988
----------------------------
935989

odml/base.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""
33
This module provides base classes for functionality common to odML objects.
44
"""
5+
import copy
56
import posixpath
67

78
try:
@@ -36,9 +37,10 @@ def __eq__(self, obj):
3637
return False
3738

3839
for key in self._format:
39-
if key == "id" or key == "oid":
40+
if key in ["id", "oid"]:
4041
continue
41-
elif getattr(self, key) != getattr(obj, key):
42+
43+
if getattr(self, key) != getattr(obj, key):
4244
return False
4345

4446
return True
@@ -87,7 +89,6 @@ def clone(self, children=True):
8789
from this class.
8890
"""
8991
# TODO don't we need some recursion / deepcopy here?
90-
import copy
9192
obj = copy.copy(self)
9293
return obj
9394

@@ -149,6 +150,8 @@ def __contains__(self, key):
149150
if (hasattr(obj, "name") and obj.name == key) or key == obj:
150151
return True
151152

153+
return False
154+
152155
def __eq__(self, obj):
153156
"""
154157
SmartList attributes of 'sections' and 'properties' are
@@ -292,7 +295,7 @@ def extend(self, sec_list):
292295
if not isinstance(sec, BaseSection):
293296
raise ValueError("Can only extend objects of type Section.")
294297

295-
elif isinstance(sec, BaseSection) and sec.name in self._sections:
298+
if isinstance(sec, BaseSection) and sec.name in self._sections:
296299
raise KeyError("Section with name '%s' already exists." % sec.name)
297300

298301
for sec in sec_list:
@@ -356,9 +359,9 @@ def iterproperties(self, max_depth=None, filter_func=lambda x: True):
356359
iterable. Yields iterable if function returns True
357360
:type filter_func: function
358361
"""
359-
for sec in [s for s in self.itersections(max_depth=max_depth,
360-
yield_self=True)]:
361-
if hasattr(sec, "properties"): # not to fail if odml.Document
362+
for sec in list(self.itersections(max_depth=max_depth, yield_self=True)):
363+
# Avoid fail with an odml.Document
364+
if hasattr(sec, "properties"):
362365
for i in sec.properties:
363366
if filter_func(i):
364367
yield i
@@ -380,7 +383,7 @@ def itervalues(self, max_depth=None, filter_func=lambda x: True):
380383
iterable. Yields iterable if function returns True
381384
:type filter_func: function
382385
"""
383-
for prop in [p for p in self.iterproperties(max_depth=max_depth)]:
386+
for prop in list(self.iterproperties(max_depth=max_depth)):
384387
if filter_func(prop.values):
385388
yield prop.values
386389

@@ -478,9 +481,10 @@ def _get_section_by_path(self, path):
478481

479482
if found:
480483
return found._get_section_by_path("/".join(pathlist[1:]))
484+
481485
raise ValueError("Section named '%s' does not exist" % pathlist[0])
482-
else:
483-
return self._match_iterable(self.sections, pathlist[0])
486+
487+
return self._match_iterable(self.sections, pathlist[0])
484488

485489
def find(self, key=None, type=None, findAll=False, include_subtype=False):
486490
"""

odml/doc.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,20 @@ def parent(self):
122122
""" The parent of a document is always None. """
123123
return None
124124

125+
@property
126+
def origin_file_name(self):
127+
"""
128+
If available, the file name from where the document has been loaded.
129+
Will not be serialized to file when saving the document.
130+
"""
131+
return self._origin_file_name
132+
133+
@origin_file_name.setter
134+
def origin_file_name(self, new_value):
135+
if not new_value:
136+
new_value = None
137+
self._origin_file_name = new_value
138+
125139
def finalize(self):
126140
"""
127141
This needs to be called after the document is set up from parsing

odml/property.py

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ def parent(self):
253253
def parent(self, new_parent):
254254
if new_parent is None and self._parent is None:
255255
return
256-
elif new_parent is None and self._parent is not None:
256+
257+
if new_parent is None and self._parent is not None:
257258
self._parent.remove(self)
258259
self._parent = None
259260
elif self._validate_parent(new_parent):
@@ -319,7 +320,8 @@ def _validate_values(self, values):
319320
return False
320321
return True
321322

322-
def _convert_value_input(self, new_value):
323+
@staticmethod
324+
def _convert_value_input(new_value):
323325
"""
324326
This method ensures, that the passed new value is a list.
325327
If new_value is a string, it will convert it to a list of
@@ -403,14 +405,12 @@ def values(self, new_value):
403405
new_value = odml_tuple_import(t_count, new_value)
404406

405407
if not self._validate_values(new_value):
408+
msg = "odml.Property.values: passed values are not of consistent type"
406409
if self._dtype in ("date", "time", "datetime"):
407410
req_format = dtypes.default_values(self._dtype)
408-
msg = "odml.Property.values: passed values are not of consistent type "
409-
msg += "\'%s\'! Format should be \'%s\'." % (self._dtype, req_format)
410-
raise ValueError(msg)
411-
else:
412-
msg = "odml.Property.values: passed values are not of consistent type!"
413-
raise ValueError(msg)
411+
msg += " \'%s\'! Format should be \'%s\'." % (self._dtype, req_format)
412+
raise ValueError(msg)
413+
414414
self._values = [dtypes.get(v, self.dtype) for v in new_value]
415415

416416
# Validate and inform user if the current values cardinality is violated
@@ -714,10 +714,10 @@ def get_merged_equivalent(self):
714714
Return the merged object (i.e. if the parent section is linked to another one,
715715
return the corresponding property of the linked section) or None.
716716
"""
717-
if self.parent is None or self.parent._merged is None:
717+
if self.parent is None or not self.parent.is_merged:
718718
return None
719719

720-
return self.parent._merged.contains(self)
720+
return self.parent.get_merged_equivalent().contains(self)
721721

722722
@inherit_docstring
723723
def get_terminology_equivalent(self):
@@ -841,26 +841,16 @@ def pprint(self, indent=2, max_length=80, current_depth=-1):
841841

842842
def export_leaf(self):
843843
"""
844-
Export only the path from this property to the root.
845-
Include all properties of parent sections.
846-
847-
:returns: cloned odml tree to the root of the current document.
848-
"""
849-
curr = self.parent
850-
par = self.parent
851-
child = self.parent
852-
853-
while curr is not None:
854-
par = curr.clone(children=False, keep_id=True)
855-
if curr != self.parent:
856-
par.append(child)
857-
if hasattr(curr, 'properties'):
858-
if curr == self.parent:
859-
par.append(self.clone(keep_id=True))
860-
else:
861-
for prop in curr.properties:
862-
par.append(prop.clone(keep_id=True))
863-
child = par
864-
curr = curr.parent
865-
866-
return par
844+
Export the path including all direct parents from this Property
845+
to the root of the document. Section properties are included,
846+
Subsections are not included.
847+
848+
:returns: Cloned odml tree to the root of the current document.
849+
"""
850+
export = self
851+
if export.parent:
852+
# Section.export_leaf will take care of the full export and
853+
# include the current Property.
854+
export = export.parent.export_leaf()
855+
856+
return export

odml/section.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ class BaseSection(base.Sectionable):
5454
"""
5555

5656
type = None
57-
reference = None # the *import* property
5857
_link = None
5958
_include = None
6059
_merged = None
@@ -328,7 +327,8 @@ def parent(self):
328327
def parent(self, new_parent):
329328
if new_parent is None and self._parent is None:
330329
return
331-
elif new_parent is None and self._parent is not None:
330+
331+
if new_parent is None and self._parent is not None:
332332
self._parent.remove(self)
333333
self._parent = None
334334
elif self._validate_parent(new_parent):
@@ -341,7 +341,8 @@ def parent(self, new_parent):
341341
"odml.Section.parent: passed value is not of consistent type!"
342342
"\nodml.Document or odml.Section expected")
343343

344-
def _validate_parent(self, new_parent):
344+
@staticmethod
345+
def _validate_parent(new_parent):
345346
"""
346347
Checks whether a provided object is a valid odml.Section or odml.Document..
347348
@@ -402,9 +403,9 @@ def set_sections_cardinality(self, min_val=None, max_val=None):
402403
Sets the Sections cardinality of a Section.
403404
404405
:param min_val: Required minimal number of values elements. None denotes
405-
no restrictions on values elements minimum. Default is None.
406+
no restrictions on sections elements minimum. Default is None.
406407
:param max_val: Allowed maximal number of values elements. None denotes
407-
no restrictions on values elements maximum. Default is None.
408+
no restrictions on sections elements maximum. Default is None.
408409
"""
409410
self.sec_cardinality = (min_val, max_val)
410411

@@ -455,9 +456,9 @@ def set_properties_cardinality(self, min_val=None, max_val=None):
455456
Sets the Properties cardinality of a Section.
456457
457458
:param min_val: Required minimal number of values elements. None denotes
458-
no restrictions on values elements minimum. Default is None.
459+
no restrictions on properties elements minimum. Default is None.
459460
:param max_val: Allowed maximal number of values elements. None denotes
460-
no restrictions on values elements maximum. Default is None.
461+
no restrictions on properties elements maximum. Default is None.
461462
"""
462463
self.prop_cardinality = (min_val, max_val)
463464

@@ -521,16 +522,16 @@ def extend(self, obj_list):
521522
# Make sure only Sections and Properties with unique names will be added.
522523
for obj in obj_list:
523524
if not isinstance(obj, BaseSection) and not isinstance(obj, BaseProperty):
524-
raise ValueError("odml.Section.extend: "
525-
"Can only extend sections and properties.")
525+
msg = "odml.Section.extend: Can only extend sections and properties."
526+
raise ValueError(msg)
526527

527-
elif isinstance(obj, BaseSection) and obj.name in self.sections:
528-
raise KeyError("odml.Section.extend: "
529-
"Section with name '%s' already exists." % obj.name)
528+
if isinstance(obj, BaseSection) and obj.name in self.sections:
529+
msg = "odml.Section.extend: Section with name '%s' already exists." % obj.name
530+
raise KeyError(msg)
530531

531-
elif isinstance(obj, BaseProperty) and obj.name in self.properties:
532-
raise KeyError("odml.Section.extend: "
533-
"Property with name '%s' already exists." % obj.name)
532+
if isinstance(obj, BaseProperty) and obj.name in self.properties:
533+
msg = "odml.Section.extend: Property with name '%s' already exists." % obj.name
534+
raise KeyError(msg)
534535

535536
for obj in obj_list:
536537
self.append(obj)
@@ -613,13 +614,14 @@ def contains(self, obj):
613614
if isinstance(obj, BaseSection):
614615
return super(BaseSection, self).contains(obj)
615616

616-
elif isinstance(obj, BaseProperty):
617+
if isinstance(obj, BaseProperty):
617618
for i in self._props:
618619
if obj.name == i.name:
619620
return i
620-
else:
621-
raise ValueError("odml.Section.contains:"
622-
"Section or Property object expected.")
621+
622+
return None
623+
624+
raise ValueError("odml.Section.contains: Section or Property object expected.")
623625

624626
def merge_check(self, source_section, strict=True):
625627
"""

odml/tools/converters/format_converter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def convert(cls, args=None):
7979
parser.add_argument("-r", "--recursive", action="store_true",
8080
help="Enable converting files from subdirectories")
8181
args = parser.parse_args(args)
82-
recursive = True if args.recursive else False
82+
recursive = bool(args.recursive)
8383
cls.convert_dir(args.input_dir, args.output_dir, recursive, args.result_format)
8484

8585
@classmethod

0 commit comments

Comments
 (0)