#!/usr/bin/env python # -*- coding: utf-8 -*- """ Copyright(C) 2007 INL Written by Damien Boucard <damien.boucard AT inl.fr> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 3 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. --- relation.py is a module which dynamically manages multi-lateral relations. """ class RelationManager(list): pass class Relation: def __init__(self, manager=None): self.manager = default_manager if manager is not None: if not isinstance(manager, RelationManager): raise ValueError("'manager' argument must be a %s instance; given: %s (%s)" %(default_manager.__class__.name, manager, type(manager))) self.manager = manager self.manager.append(self) class RelationSet: def __init__(self, relation_type=Relation, manager=None, **kw): """ Filtered list of relations. Filtering is made on type of relation (class given to isinstance) and/or the value of one or several relation instance attribute. Example of usage: >>> class Test(Relation): ... def __init__(self, a, b): ... Relation.__init__(self) ... self.a = a ... self.b = b ... def __str__(self): return "%s-%s" %(self.a, self.b) >>> a1 = "A" >>> b1 = "1" >>> a2 = "B" >>> b2 = "2" >>> Test(a1, b1) #doctest: +ELLIPSIS <__main__.Test instance at 0x...> >>> Test(a1, b2) #doctest: +ELLIPSIS <__main__.Test instance at 0x...> >>> Test(a2, b2) #doctest: +ELLIPSIS <__main__.Test instance at 0x...> >>> str(RelationSet(relation_type=Test, a=a1)) "['A-1', 'A-2']" >>> str(RelationSet(relation_type=Test, a=a2)) "['B-2']" >>> str(RelationSet(relation_type=Test, a=a1, b=b1)) "['A-1']" >>> str(RelationSet(relation_type=Test)) "['A-1', 'A-2', 'B-2']" Filter can be made on attributes of related objects, by passing keyword arguments containing double-underscore "__". Each "__" is interpreted as a "." in the normal Python syntax. Exemple of recursive lookup: >>> del default_manager[:] >>> class N: ... def __init__(self, n, salt): ... self.n = n ... self.salt = salt ... def __str__(self): return "N%s(%s)" %(self.n, self.salt) >>> Test(a1, N(1, "Abc")) <__main__.Test instance at 0x...> >>> Test(a1, N(2, "Def")) <__main__.Test instance at 0x...> >>> Test(a1, N(2, "Ghi")) <__main__.Test instance at 0x...> >>> str(RelationSet()) "['A-N1(Abc)', 'A-N2(Def)', 'A-N2(Ghi)']" >>> str(RelationSet(b__n=1)) "['A-N1(Abc)']" >>> str(RelationSet(b__n=2)) "['A-N2(Def)', 'A-N2(Ghi)']" """ self.attributes = kw if not issubclass(relation_type, Relation): raise ValueError("'relation_type' argument must be a subclass of relation.Relation; given: %s" %(relation_type.__name__)) self.relation_type = relation_type self.manager = default_manager if manager is not None: if not isinstance(manager, RelationManager): raise ValueError("'manager' argument must be a %s instance; given: %s (%s)" %(default_manager.__class__.name, manager, type(manager))) self.manager = manager def __iter__(self): for relation in self.manager: if not isinstance(relation, self.relation_type): continue has_attr = True for attr_str, expected_value in self.attributes.iteritems(): attr_list = attr_str.split("__") attr_value = relation for attr_name in attr_list: if not hasattr(attr_value, attr_name): has_attr = False break attr_value = getattr(attr_value, attr_name) if not has_attr or attr_value != expected_value: has_attr = False break if has_attr: yield relation def __len__(self): return len([str(relation) for relation in self]) def __str__(self): return str([str(relation) for relation in self]) default_manager = RelationManager() if __name__ == "__main__": from doctest import testmod, ELLIPSIS from sys import exit failure, nb_test = testmod(optionflags=ELLIPSIS) if failure: exit(1)