add a generic mechanisme to specify metadata (closes #83813)

RichString is migrated to this new mechanism

authorPierre-Yves David <pierre-yves.david@logilab.fr>
changeset4d25fa7c13d9
branchdefault
phasepublic
hiddenno
parent revision#297b430fad6c Added tag yams-debian-version-0.34.0-1 for changeset a1b026734347
child revision#08eea2b038b3 [fix] add missing metadata initialization (stolen by a documentation patch)
files modified by this revision
__init__.py
buildobjs.py
schema.py
test/unittest_reader.py
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@logilab.fr>
# Date 1323091078 -3600
# Mon Dec 05 14:17:58 2011 +0100
# Node ID 4d25fa7c13d9b3ecebb67bd45c189ab0f6365c5a
# Parent 297b430fad6c6513b8e8f40b0052f564d63b389f
add a generic mechanisme to specify metadata (closes #83813)

RichString is migrated to this new mechanism

diff --git a/__init__.py b/__init__.py
@@ -50,10 +50,12 @@
1      'Datetime' : lambda x: ':' in x and strptime(x, '%Y/%m/%d %H:%M') or strptime(x, '%Y/%m/%d'),
2      'Date' : lambda x : strptime(x, '%Y/%m/%d'),
3      'Time' : strptime_time
4      }
5 
6 +KNOWN_METAATTRIBUTES = set(('format', 'encoding', 'name'))
7 +
8  # work in progress ###
9 
10  class _RelationRole(int):
11      def __eq__(self, other):
12          if isinstance(other, _RelationRole):
diff --git a/buildobjs.py b/buildobjs.py
@@ -22,11 +22,11 @@
13  from warnings import warn
14 
15  from logilab.common import attrdict
16  from logilab.common.decorators import iclassmethod
17 
18 -from yams import BASE_TYPES, MARKER, BadSchemaDefinition
19 +from yams import BASE_TYPES, MARKER, BadSchemaDefinition, KNOWN_METAATTRIBUTES
20  from yams.constraints import (SizeConstraint, UniqueConstraint,
21                                StaticVocabularyConstraint, format_constraint)
22 
23  __all__ = ('EntityType', 'RelationType', 'RelationDefinition',
24             'SubjectRelation', 'ObjectRelation', 'BothWayRelation',
@@ -54,26 +54,25 @@
25              return
26      constraints.append(constraint)
27 
28  def _add_relation(relations, rdef, name=None, insertidx=None):
29      """Add relation (param rdef) to list of relations (param relations)."""
30 -    if isinstance(rdef, RichString):
31 -        format_attrdef = String(internationalizable=True,
32 -                                default=rdef.default_format, maxsize=50,
33 -                                constraints=rdef.format_constraints)
34 -        _add_relation(relations, format_attrdef,
35 -                      (name or rdef.name) + '_format', insertidx)
36      if isinstance(rdef, BothWayRelation):
37          _add_relation(relations, rdef.subjectrel, name, insertidx)
38          _add_relation(relations, rdef.objectrel, name, insertidx)
39      else:
40          if name is not None:
41              rdef.name = name
42          if insertidx is None:
43 -            relations.append(rdef)
44 -        else:
45 -            relations.insert(insertidx, rdef)
46 +            insertidx = len(relations)
47 +        relations.insert(insertidx, rdef)
48 +    if getattr(rdef, 'metadata', {}):
49 +        for meta_name, value in rdef.metadata.iteritems():
50 +            assert meta_name in KNOWN_METAATTRIBUTES
51 +            insertidx += 1 # insert meta after main
52 +            meta_rel_name = '_'.join(((name or rdef.name), meta_name))
53 +            _add_relation(relations, value, meta_rel_name, insertidx)
54 
55  def _check_kwargs(kwargs, attributes):
56      """Check that all keys of kwargs are actual attributes."""
57      for key in kwargs:
58          if not key in attributes:
@@ -339,14 +338,15 @@
59      def add_relation(cls, rdef, name=None):
60          if name:
61              rdef.name = name
62          if cls._ensure_relation_type(rdef):
63              _add_relation(cls.__relations__, rdef, name)
64 -            if isinstance(rdef, RichString) and not rdef in cls._defined:
65 -                format_attr_name = (name or rdef.name) + '_format'
66 -                rdef = cls.get_relations(format_attr_name).next()
67 -                cls._ensure_relation_type(rdef)
68 +            if getattr(rdef, 'metadata', {}) and not rdef in cls._defined:
69 +                for meta_name in rdef.metadata:
70 +                    meta_rel_name = '_'.join(((name or rdef.name), name_name))
71 +                    rdef = cls.get_relations(format_attr_name).next()
72 +                    cls._ensure_relation_type(rdef)
73          else:
74             _add_relation(cls.__relations__, rdef, name=name)
75 
76      @classmethod
77      def insert_relation_after(cls, afterrelname, name, rdef):
@@ -644,10 +644,24 @@
78          unique = kwargs.pop('unique', None)
79          if unique:
80              _add_constraint(kwargs, UniqueConstraint())
81          # use the etype attribute provided by subclasses
82          super(AbstractTypedAttribute, self).__init__(self.etype, **kwargs)
83 +        # reassign creation rank
84 +        #
85 +        # Main attribute are marked as created before it's metadata.
86 +        # order in meta data is preserved.
87 +        if self.metadata:
88 +            meta = sorted(metadata.values(), key= lambda x: x.creation_rank)
89 +            if meta[0].creation_rank < self.creation_rank:
90 +                m_iter = iter(meta)
91 +                previous = self
92 +                for next in meta:
93 +                    if previous.creation_rank < next.creation_rank:
94 +                        break
95 +                    previous.creation_rank, next.creation_rank = next.creation_rank, previous.creation_rank
96 +                    next = previous
97 
98      def set_vocabulary(self, vocabulary, kwargs=None):
99          if kwargs is None:
100              kwargs = self.__dict__
101          #constraints = kwargs.setdefault('constraints', [])
@@ -662,14 +676,14 @@
102  # build a specific class for each base type
103  for basetype in BASE_TYPES:
104      globals()[basetype] = type(basetype, (AbstractTypedAttribute,),
105                                 {'etype' : basetype})
106 
107 -# provides a RichString class for convenience
108 +# provides a RichString factory for convenience
109 
110 
111 -class RichString(String):
112 +def RichString(default_format='text/plain', format_constraints=None, **kwargs):
113      """RichString is a convenience attribute type for attribute containing text
114      in a format that should be specified in another attribute.
115 
116      The following declaration::
117 
@@ -681,14 +695,16 @@
118        class Card(EntityType):
119            content_format = String(internationalizable=True,
120                                    default='text/rest', constraints=[format_constraint])
121            content  = String(fulltextindexed=True)
122      """
123 -    def __init__(self, default_format='text/plain', format_constraints=None, **kwargs):
124 -        self.default_format = default_format
125 -        self.format_constraints = format_constraints or [format_constraint]
126 -        super(RichString, self).__init__(**kwargs)
127 +    format_args = {'default': default_format,
128 +                   'maxsize': 50}
129 +    if format_constraints is not None:
130 +        format_args['constraints'] = [format_constraints]
131 +    meta = {'format':String(internationalizable=True, **format_args)}
132 +    return String(metadata=meta, **kwargs)
133 
134 
135  # various derivated classes with some predefined values XXX deprecated
136 
137  class MetaEntityType(EntityType):
diff --git a/schema.py b/schema.py
@@ -27,11 +27,12 @@
138  from logilab.common.decorators import cached, clear_cache
139  from logilab.common.interface import implements
140  from logilab.common.deprecation import deprecated
141 
142  import yams
143 -from yams import BASE_TYPES, MARKER, ValidationError, BadSchemaDefinition
144 +from yams import (BASE_TYPES, MARKER, ValidationError, BadSchemaDefinition,
145 +                  KNOWN_METAATTRIBUTES)
146  from yams.interfaces import (ISchema, IRelationSchema, IEntitySchema,
147                               IVocabularyConstraint)
148  from yams.constraints import BASE_CHECKERS, BASE_CONVERTERS, UniqueConstraint
149 
150  def role_name(rtype, role):
@@ -153,11 +154,10 @@
151          check_permission_definitions(self)
152 
153 
154  # Schema objects definition ###################################################
155 
156 -KNOWN_METAATTRIBUTES = set(('format', 'encoding', 'name'))
157 
158  class EntitySchema(PermissionMixIn, ERSchema):
159      """An entity has a type, a set of subject and or object relations
160      the entity schema defines the possible relations for a given type and some
161      constraints on those relations.
diff --git a/test/unittest_reader.py b/test/unittest_reader.py
@@ -310,15 +310,16 @@
162 
163  class X(Student):
164      pass
165 
166  class Foo(B.EntityType):
167 -    i = B.Int(required=True)
168 +    i = B.Int(required=True, metadata={'name': B.String()})
169      f = B.Float()
170      d = B.Datetime()
171 
172 
173 +
174  class PySchemaTC(TestCase):
175 
176      def test_python_inheritance(self):
177          bp = BasePerson()
178          p = Person()
@@ -334,14 +335,14 @@
179          x = X()
180          self.assertEqual(x.specialized_type, None)
181 
182      def test_relationtype(self):
183          foo = Foo()
184 -        self.assertEqual([r.etype for r in foo.__relations__],
185 -                          ['Int', 'Float', 'Datetime'])
186 +        self.assertEqual(['Int', 'String', 'Float', 'Datetime'],
187 +                         [r.etype for r in foo.__relations__])
188          self.assertEqual(foo.__relations__[0].cardinality, '11')
189 -        self.assertEqual(foo.__relations__[1].cardinality, '?1')
190 +        self.assertEqual(foo.__relations__[2].cardinality, '?1')
191 
192      def test_maxsize(self):
193          bp = BasePerson()
194          def maxsize(e):
195              for e in e.constraints:
@@ -350,10 +351,15 @@
196          self.assertEqual(maxsize(bp.__relations__[0]), 7)
197          # self.assertEqual(maxsize(bp.__relations__[1]), 7)
198          emp = Employee()
199          self.assertEqual(maxsize(emp.__relations__[3]), 7)
200 
201 +    def test_metadata(self):
202 +        foo = Foo()
203 +        self.assertEqual('i_name', foo.__relations__[1].name)
204 +
205 +
206      def test_date_defaults(self):
207          _today = date.today()
208          _now = datetime.now()
209          datetest = schema.eschema('Datetest')
210          dt1 = datetest.default('dt1')