Skip to content

Commit

Permalink
Merge pull request #248 from jonls/entry-id-override
Browse files Browse the repository at this point in the history
Expand ModelEntry with id override and setters
  • Loading branch information
jonls committed May 30, 2017
2 parents 196c95b + 800aca6 commit 997d5f0
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 14 deletions.
84 changes: 70 additions & 14 deletions psamm/datasource/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,20 @@

@add_metaclass(abc.ABCMeta)
class ModelEntry(object):
"""Abstract model entry."""
"""Abstract model entry.
Provdides a base class for model entries which are representations of
any entity (such as compound, reaction or compartment) in a model. An
entity has an ID, and may have a name and filemark. The ID is a unique
string identified within a model. The name is a string identifier for
human consumption. The filemark indicates where the entry originates from
(e.g. file name and line number). Any additional properties for an
entity exist in ``properties`` which is any dict-like object mapping
from string keys to any value type. The ``name`` entry in the dictionary
corresponds to the name. Entries can be mutable, where the
properties can be modified, or immutable, where the properties cannot be
modified or where modifications are ignored. The ID is always immutable.
"""
@abc.abstractproperty
def id(self):
"""Identifier of entry."""
Expand All @@ -41,7 +54,10 @@ def name(self):
def properties(self):
"""Properties of entry as a :class:`Mapping` subclass (e.g. dict).
Note that the properties are not generally mutable.
Note that the properties are not generally mutable but may be mutable
for specific subclasses. If the ``id`` exists in this dictionary, it
must never change the actual entry ID as obtained from the ``id``
property, even if other properties are mutable.
"""

@abc.abstractproperty
Expand All @@ -53,7 +69,11 @@ def __repr__(self):


class CompoundEntry(ModelEntry):
"""Abstract compound entry."""
"""Abstract compound entry.
Entry subclass for representing compounds. This standardizes the properties
``formula`` and ``charge``.
"""
@property
def formula(self):
"""Chemical formula of compound."""
Expand All @@ -66,7 +86,11 @@ def charge(self):


class ReactionEntry(ModelEntry):
"""Abstract reaction entry."""
"""Abstract reaction entry.
Entry subclass for representing compounds. This standardizes the properties
``equation`` and ``genes``.
"""
@property
def equation(self):
"""Reaction equation."""
Expand All @@ -79,21 +103,33 @@ def genes(self):


class CompartmentEntry(ModelEntry):
"""Abstract compartment entry."""
"""Abstract compartment entry.
Entry subclass for representing compartments.
"""


class _BaseDictEntry(ModelEntry):
"""Base class for concrete entries based on dictionary."""
def __init__(self, abstract_type, properties={}, filemark=None):
"""Base class for concrete entries based on dictionary.
The properties are mutable for this subclass. If ``id`` is None, the
value corresponding to the ``id`` key in the dictionary is used. If this is
not defined, a :class:`ValueError` is raised.
"""
def __init__(self, abstract_type, properties={}, filemark=None, id=None):
if isinstance(properties, abstract_type):
self._id = properties.id
self._id = id
if self._id is None:
self._id = properties.id
self._properties = dict(properties.properties)
if filemark is None:
filemark = properties.filemark
elif isinstance(properties, Mapping):
if 'id' not in properties:
raise ValueError('id not defined in properties')
self._id = properties['id']
self._id = id
if self._id is None:
if 'id' not in properties:
raise ValueError('id not defined in properties')
self._id = properties['id']
self._properties = dict(properties)
else:
raise ValueError('Invalid type of properties object')
Expand All @@ -104,6 +140,10 @@ def __init__(self, abstract_type, properties={}, filemark=None):
def id(self):
return self._id

@ModelEntry.name.setter
def name(self, value):
self._properties['name'] = value

@property
def properties(self):
return self._properties
Expand All @@ -116,7 +156,7 @@ def filemark(self):
class DictCompoundEntry(CompoundEntry, _BaseDictEntry):
"""Compound entry backed by dictionary.
The given properties dictionary must contain a key 'id' with the
The given properties dictionary must contain a key ``id`` with the
identifier.
Args:
Expand All @@ -126,11 +166,19 @@ class DictCompoundEntry(CompoundEntry, _BaseDictEntry):
def __init__(self, *args, **kwargs):
super(DictCompoundEntry, self).__init__(CompoundEntry, *args, **kwargs)

@CompoundEntry.formula.setter
def formula(self, value):
self._properties['formula'] = value

@CompoundEntry.charge.setter
def charge(self, value):
self._properties['charge'] = value


class DictReactionEntry(ReactionEntry, _BaseDictEntry):
"""Reaction entry backed by dictionary.
The given properties dictionary must contain a key 'id' with the
The given properties dictionary must contain a key ``id`` with the
identifier.
Args:
Expand All @@ -140,11 +188,19 @@ class DictReactionEntry(ReactionEntry, _BaseDictEntry):
def __init__(self, *args, **kwargs):
super(DictReactionEntry, self).__init__(ReactionEntry, *args, **kwargs)

@ReactionEntry.equation.setter
def equation(self, value):
self._properties['equation'] = value

@ReactionEntry.genes.setter
def genes(self, value):
self._properties['genes'] = value


class DictCompartmentEntry(CompartmentEntry, _BaseDictEntry):
"""Compartment entry backed by dictionary.
The given properties dictionary must contain a key 'id' with the
The given properties dictionary must contain a key ``id`` with the
identifier.
Args:
Expand Down
27 changes: 27 additions & 0 deletions psamm/tests/test_datasource_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ def test_create_compound_dict_entry_without_id(self):
with self.assertRaises(ValueError):
e = entry.DictCompoundEntry(props)

def test_create_compound_dict_entry_with_id_override(self):
props = {
'name': 'Compound 1',
'formula': 'CO2'
}
e = entry.DictCompoundEntry(props, id='new_id')

def test_use_compound_dict_entry_setters(self):
e = entry.DictCompoundEntry({}, id='new_id')
e.formula = 'CO2'
e.name = 'Compound 1'
e.charge = 5
self.assertEqual(e.formula, 'CO2')
self.assertEqual(e.properties['formula'], 'CO2')
self.assertEqual(e.name, 'Compound 1')
self.assertEqual(e.charge, 5)

def test_create_reaction_entry(self):
props = {
'id': 'reaction_1',
Expand All @@ -74,6 +91,16 @@ def test_create_reaction_entry_from_compound_entry(self):
with self.assertRaises(ValueError):
e2 = entry.DictReactionEntry(e)

def test_use_reaction_dict_entry_setters(self):
e = entry.DictReactionEntry({}, id='reaction_1')
e.name = 'Reaction 1'
e.equation = 'A => B'
e.genes = 'gene_1 and gene_2'
self.assertEqual(e.name, 'Reaction 1')
self.assertEqual(e.equation, 'A => B')
self.assertEqual(e.genes, 'gene_1 and gene_2')
self.assertEqual(e.properties['genes'], 'gene_1 and gene_2')

def test_create_compartment_entry(self):
props = {
'id': 'c',
Expand Down

0 comments on commit 997d5f0

Please sign in to comment.