Skip to content

Commit

Permalink
Merge pull request #385 from mpsonntag/cleanUpdate
Browse files Browse the repository at this point in the history
General updates and PEP8 code cleanup
  • Loading branch information
jgrewe authored Apr 22, 2020
2 parents dd29249 + 76b3f8d commit 206cbba
Show file tree
Hide file tree
Showing 29 changed files with 1,119 additions and 920 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
![Travis build](https://travis-ci.org/G-Node/python-odml.svg?branch=master)
[![Travis build](https://travis-ci.org/G-Node/python-odml.svg?branch=master)](https://travis-ci.org/G-Node/python-odml)
[![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)
![Test coverage](https://coveralls.io/repos/github/G-Node/python-odml/badge.svg?branch=master)
[![Test coverage](https://coveralls.io/repos/github/G-Node/python-odml/badge.svg?branch=master)](https://coveralls.io/github/G-Node/python-odml)
[![PyPI version](https://img.shields.io/pypi/v/odml.svg)](https://pypi.org/project/odML/)
[![Read the Docs](https://img.shields.io/readthedocs/python-odml)](https://python-odml.readthedocs.io/en/latest/)

Expand Down
60 changes: 57 additions & 3 deletions doc/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ experimental project and complements the special needs of your laboratory.
The code for the example odML files, which we use within this tutorial is part
of the documentation package (see doc/example_odMLs/).

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

-------------------------------------------------------------------------------

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


The Sections
------------

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

Printing overviews to navigate the contents of an odML document
---------------------------------------------------------------

The odML entities ``Property``, ``Section`` and ``Document`` feature
a method that allows to print a tree-like representation of
all child entities to get an overview of the file structure.

>>> MYodML.pprint()
>>> sec = MYodML['TheCrew']
>>> sec.pprint()
>>> prop = odmlEX['TheCrew'].properties['NameCrewMembers']
>>> prop.pprint()

-------------------------------------------------------------------------------

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


Defining and working with feature cardinality
---------------------------------------------

The odML format allows users to define a cardinality for
the number of subsections and properties of Sections and
the number of values a Property might have.

A cardinality is checked when it is set, when its target is
set and when a document is saved or loaded. If a specific
cardinality is violated, a corresponding warning will be printed.

Setting a cardinality
*********************

A cardinality can be set for sections or properties of sections
or for values of properties. By default every cardinality is None,
but it can be set to a defined minimal and/or a maximal number of
an element.

A cardinality is set via its convenience method:

>>> # Set the cardinality of the properties of a Section 'sec' to
>>> # a maximum of 5 elements.
>>> sec = odml.Section(name="cardinality", type="test")
>>> sec.set_properties_cardinality(max_val=5)

>>> # Set the cardinality of the subsections of Section 'sec' to
>>> # a minimum of one and a maximum of 2 elements.
>>> sec.set_sections_cardinality(min_val=1, max_val=2)

>>> # Set the cardinality of the values of a Property 'prop' to
>>> # a minimum of 1 element.
>>> prop = odml.Property(name="cardinality")
>>> prop.set_values_cardinality(min_val=1)

>>> # Re-set the cardinality of the values of a Property 'prop' to not set.
>>> prop.set_values_cardinality()
>>> # or
>>> prop.val_cardinality = None


Advanced knowledge on Values
----------------------------

Expand Down
24 changes: 14 additions & 10 deletions odml/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""
This module provides base classes for functionality common to odML objects.
"""
import copy
import posixpath

try:
Expand Down Expand Up @@ -36,9 +37,10 @@ def __eq__(self, obj):
return False

for key in self._format:
if key == "id" or key == "oid":
if key in ["id", "oid"]:
continue
elif getattr(self, key) != getattr(obj, key):

if getattr(self, key) != getattr(obj, key):
return False

return True
Expand Down Expand Up @@ -87,7 +89,6 @@ def clone(self, children=True):
from this class.
"""
# TODO don't we need some recursion / deepcopy here?
import copy
obj = copy.copy(self)
return obj

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

return False

def __eq__(self, obj):
"""
SmartList attributes of 'sections' and 'properties' are
Expand Down Expand Up @@ -292,7 +295,7 @@ def extend(self, sec_list):
if not isinstance(sec, BaseSection):
raise ValueError("Can only extend objects of type Section.")

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

for sec in sec_list:
Expand Down Expand Up @@ -356,9 +359,9 @@ def iterproperties(self, max_depth=None, filter_func=lambda x: True):
iterable. Yields iterable if function returns True
:type filter_func: function
"""
for sec in [s for s in self.itersections(max_depth=max_depth,
yield_self=True)]:
if hasattr(sec, "properties"): # not to fail if odml.Document
for sec in list(self.itersections(max_depth=max_depth, yield_self=True)):
# Avoid fail with an odml.Document
if hasattr(sec, "properties"):
for i in sec.properties:
if filter_func(i):
yield i
Expand All @@ -380,7 +383,7 @@ def itervalues(self, max_depth=None, filter_func=lambda x: True):
iterable. Yields iterable if function returns True
:type filter_func: function
"""
for prop in [p for p in self.iterproperties(max_depth=max_depth)]:
for prop in list(self.iterproperties(max_depth=max_depth)):
if filter_func(prop.values):
yield prop.values

Expand Down Expand Up @@ -478,9 +481,10 @@ def _get_section_by_path(self, path):

if found:
return found._get_section_by_path("/".join(pathlist[1:]))

raise ValueError("Section named '%s' does not exist" % pathlist[0])
else:
return self._match_iterable(self.sections, pathlist[0])

return self._match_iterable(self.sections, pathlist[0])

def find(self, key=None, type=None, findAll=False, include_subtype=False):
"""
Expand Down
14 changes: 14 additions & 0 deletions odml/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,20 @@ def parent(self):
""" The parent of a document is always None. """
return None

@property
def origin_file_name(self):
"""
If available, the file name from where the document has been loaded.
Will not be serialized to file when saving the document.
"""
return self._origin_file_name

@origin_file_name.setter
def origin_file_name(self, new_value):
if not new_value:
new_value = None
self._origin_file_name = new_value

def finalize(self):
"""
This needs to be called after the document is set up from parsing
Expand Down
56 changes: 23 additions & 33 deletions odml/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ def parent(self):
def parent(self, new_parent):
if new_parent is None and self._parent is None:
return
elif new_parent is None and self._parent is not None:

if new_parent is None and self._parent is not None:
self._parent.remove(self)
self._parent = None
elif self._validate_parent(new_parent):
Expand Down Expand Up @@ -319,7 +320,8 @@ def _validate_values(self, values):
return False
return True

def _convert_value_input(self, new_value):
@staticmethod
def _convert_value_input(new_value):
"""
This method ensures, that the passed new value is a list.
If new_value is a string, it will convert it to a list of
Expand Down Expand Up @@ -403,14 +405,12 @@ def values(self, new_value):
new_value = odml_tuple_import(t_count, new_value)

if not self._validate_values(new_value):
msg = "odml.Property.values: passed values are not of consistent type"
if self._dtype in ("date", "time", "datetime"):
req_format = dtypes.default_values(self._dtype)
msg = "odml.Property.values: passed values are not of consistent type "
msg += "\'%s\'! Format should be \'%s\'." % (self._dtype, req_format)
raise ValueError(msg)
else:
msg = "odml.Property.values: passed values are not of consistent type!"
raise ValueError(msg)
msg += " \'%s\'! Format should be \'%s\'." % (self._dtype, req_format)
raise ValueError(msg)

self._values = [dtypes.get(v, self.dtype) for v in new_value]

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

return self.parent._merged.contains(self)
return self.parent.get_merged_equivalent().contains(self)

@inherit_docstring
def get_terminology_equivalent(self):
Expand Down Expand Up @@ -841,26 +841,16 @@ def pprint(self, indent=2, max_length=80, current_depth=-1):

def export_leaf(self):
"""
Export only the path from this property to the root.
Include all properties of parent sections.
:returns: cloned odml tree to the root of the current document.
"""
curr = self.parent
par = self.parent
child = self.parent

while curr is not None:
par = curr.clone(children=False, keep_id=True)
if curr != self.parent:
par.append(child)
if hasattr(curr, 'properties'):
if curr == self.parent:
par.append(self.clone(keep_id=True))
else:
for prop in curr.properties:
par.append(prop.clone(keep_id=True))
child = par
curr = curr.parent

return par
Export the path including all direct parents from this Property
to the root of the document. Section properties are included,
Subsections are not included.
:returns: Cloned odml tree to the root of the current document.
"""
export = self
if export.parent:
# Section.export_leaf will take care of the full export and
# include the current Property.
export = export.parent.export_leaf()

return export
40 changes: 21 additions & 19 deletions odml/section.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ class BaseSection(base.Sectionable):
"""

type = None
reference = None # the *import* property
_link = None
_include = None
_merged = None
Expand Down Expand Up @@ -328,7 +327,8 @@ def parent(self):
def parent(self, new_parent):
if new_parent is None and self._parent is None:
return
elif new_parent is None and self._parent is not None:

if new_parent is None and self._parent is not None:
self._parent.remove(self)
self._parent = None
elif self._validate_parent(new_parent):
Expand All @@ -341,7 +341,8 @@ def parent(self, new_parent):
"odml.Section.parent: passed value is not of consistent type!"
"\nodml.Document or odml.Section expected")

def _validate_parent(self, new_parent):
@staticmethod
def _validate_parent(new_parent):
"""
Checks whether a provided object is a valid odml.Section or odml.Document..
Expand Down Expand Up @@ -402,9 +403,9 @@ def set_sections_cardinality(self, min_val=None, max_val=None):
Sets the Sections cardinality of a Section.
:param min_val: Required minimal number of values elements. None denotes
no restrictions on values elements minimum. Default is None.
no restrictions on sections elements minimum. Default is None.
:param max_val: Allowed maximal number of values elements. None denotes
no restrictions on values elements maximum. Default is None.
no restrictions on sections elements maximum. Default is None.
"""
self.sec_cardinality = (min_val, max_val)

Expand Down Expand Up @@ -455,9 +456,9 @@ def set_properties_cardinality(self, min_val=None, max_val=None):
Sets the Properties cardinality of a Section.
:param min_val: Required minimal number of values elements. None denotes
no restrictions on values elements minimum. Default is None.
no restrictions on properties elements minimum. Default is None.
:param max_val: Allowed maximal number of values elements. None denotes
no restrictions on values elements maximum. Default is None.
no restrictions on properties elements maximum. Default is None.
"""
self.prop_cardinality = (min_val, max_val)

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

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

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

for obj in obj_list:
self.append(obj)
Expand Down Expand Up @@ -613,13 +614,14 @@ def contains(self, obj):
if isinstance(obj, BaseSection):
return super(BaseSection, self).contains(obj)

elif isinstance(obj, BaseProperty):
if isinstance(obj, BaseProperty):
for i in self._props:
if obj.name == i.name:
return i
else:
raise ValueError("odml.Section.contains:"
"Section or Property object expected.")

return None

raise ValueError("odml.Section.contains: Section or Property object expected.")

def merge_check(self, source_section, strict=True):
"""
Expand Down
2 changes: 1 addition & 1 deletion odml/tools/converters/format_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def convert(cls, args=None):
parser.add_argument("-r", "--recursive", action="store_true",
help="Enable converting files from subdirectories")
args = parser.parse_args(args)
recursive = True if args.recursive else False
recursive = bool(args.recursive)
cls.convert_dir(args.input_dir, args.output_dir, recursive, args.result_format)

@classmethod
Expand Down
Loading

0 comments on commit 206cbba

Please sign in to comment.