[schema] cleanup default values handling in entity schema (closes #109207)

  • KEYWORD_MAP becomes an extensible two-level map
  • date/time/... string literals are deprecated
  • all other base types are now left untouched
authorAurelien Campeas <aurelien.campeas@logilab.fr>
changeset762472143df0
branchdefault
phasedraft
hiddenyes
parent revision#153169d841bd Added tag for version 0.37.0 on changeset 25d0bff4eb9d
child revision#4ffc56103a11 [schema] add a warning to avoid a potential silent bug (follows #109207), #176fd3a44354 [schema] add an assertion to avoid a silent bug (follows #109207)
files modified by this revision
__init__.py
constraints.py
schema.py
# HG changeset patch
# User Aurelien Campeas <aurelien.campeas@logilab.fr>
# Date 1367586577 -7200
# Fri May 03 15:09:37 2013 +0200
# Node ID 762472143df0eff098c48b6fa24acb0edf9fd709
# Parent 153169d841bdeb1e091e58c909c938a93aa5a0e6
[schema] cleanup default values handling in entity schema (closes #109207)

* KEYWORD_MAP becomes an extensible two-level map

* date/time/... string literals are deprecated

* all other base types are now left untouched

diff --git a/__init__.py b/__init__.py
@@ -17,10 +17,11 @@
1  # with yams. If not, see <http://www.gnu.org/licenses/>.
2  """Object model and utilities to define generic Entities/Relations schemas.
3  """
4  __docformat__ = "restructuredtext en"
5 
6 +import warnings
7  from datetime import datetime, date, time
8 
9  # XXX set _ builtin to unicode by default, should be overriden if necessary
10  import __builtin__
11  __builtin__._ = unicode
@@ -39,21 +40,59 @@
12                    ))
13 
14  # base groups used in permissions
15  BASE_GROUPS = set((_('managers'), _('users'), _('guests'), _('owners')))
16 
17 -KEYWORD_MAP = {'Datetime.NOW' : datetime.now,
18 -               'Datetime.TODAY': datetime.today,
19 -               'TZDatetime.NOW' : datetime.utcnow,
20 -               'TZDatetime.TODAY': datetime.today,
21 -               'Date.TODAY': date.today}
22 +# This provides a way to specify callable objects as default values
23 +# First level is the final type, second is the keyword to callable map
24 +KEYWORD_MAP = {
25 +    'Datetime':{'NOW' : datetime.now,
26 +                'TODAY': datetime.today},
27 +    'TZDatetime': {'NOW' : datetime.utcnow,
28 +                   'TODAY': datetime.today},
29 +    'Date': {'TODAY': date.today}
30 +}
31 +
32 +
33 +# bw compat for literal date/time values stored as strings in schemas
34  DATE_FACTORY_MAP = {
35      'Datetime' : lambda x: ':' in x and strptime(x, '%Y/%m/%d %H:%M') or strptime(x, '%Y/%m/%d'),
36      'Date' : lambda x : strptime(x, '%Y/%m/%d'),
37      'Time' : strptime_time
38      }
39 
40 +
41 +def convert_default_value(rdef, default):
42 +    # rdef can be either a yams.schema.RelationDefinitionSchema or a yams.buildobjs.RelationDefinition
43 +    rtype = getattr(rdef, 'name', None) or rdef.rtype.type
44 +    if isinstance(default, basestring) and rdef.object != 'String':
45 +        # real Strings can be anything, including things that look like keywords
46 +        # for other base types
47 +        if rdef.object in KEYWORD_MAP:
48 +            try:
49 +                return KEYWORD_MAP[rdef.object][default.upper()]()
50 +            except KeyError:
51 +                # the default was likely not a special constant like TODAY but some litteral
52 +                pass
53 +        # bw compat for old schemas
54 +        if rdef.object in DATE_FACTORY_MAP:
55 +                warnings.warn('using strings as default values for attribute %s of type %s '
56 +                              'is deprecated; you should use the plain python objects instead'
57 +                              % (rtype, rdef.object),
58 +                              DeprecationWarning)
59 +                try:
60 +                    return DATE_FACTORY_MAP[rdef.object](default)
61 +                except ValueError, verr:
62 +                    raise ValueError('creating a default value for attribute %s of type %s '
63 +                                     'from the string %r is not supported (cause %s)'
64 +                                     % (rtype, rdef.object, default, verr))
65 +    if rdef.object == 'String':
66 +        default = unicode(default)
67 +    return default # general case: untouched default
68 +
69 +
70 +
71  KNOWN_METAATTRIBUTES = set(('format', 'encoding', 'name'))
72 
73  # work in progress ###
74 
75  class _RelationRole(int):
diff --git a/constraints.py b/constraints.py
@@ -435,11 +435,11 @@
76 
77      def __str__(self):
78          return '%s(%r)' % (self.__class__.__name__, self.offset)
79 
80      def value(self, entity):
81 -        now = yams.KEYWORD_MAP['Datetime.NOW']()
82 +        now = yams.KEYWORD_MAP['Datetime']['NOW']()
83          if self.offset:
84              now += self.offset
85          return now
86 
87 
@@ -450,11 +450,11 @@
88 
89      def __str__(self):
90          return '%s(%r, %r)' % (self.__class__.__name__, self.offset, self.type)
91 
92      def value(self, entity):
93 -        now = yams.KEYWORD_MAP['%s.TODAY' % self.type]()
94 +        now = yams.KEYWORD_MAP[self.type]['TODAY']()
95          if self.offset:
96              now += self.offset
97          return now
98 
99 
diff --git a/schema.py b/schema.py
@@ -30,11 +30,11 @@
100  from logilab.common.interface import implements
101  from logilab.common.deprecation import deprecated
102 
103  import yams
104  from yams import (BASE_TYPES, MARKER, ValidationError, BadSchemaDefinition,
105 -                  KNOWN_METAATTRIBUTES)
106 +                  KNOWN_METAATTRIBUTES, convert_default_value)
107  from yams.interfaces import (ISchema, IRelationSchema, IEntitySchema,
108                               IVocabularyConstraint)
109  from yams.constraints import BASE_CHECKERS, BASE_CONVERTERS, UniqueConstraint
110 
111  def role_name(rtype, role):
@@ -97,11 +97,10 @@
112 
113  def _format_properties(props):
114      res = [('%s=%s' % item) for item in props.items() if item[1]]
115      return ','.join(res)
116 
117 -
118  class ERSchema(object):
119      """Base class shared by entity and relation schema."""
120 
121 
122      def __init__(self, schema=None, erdef=None):
@@ -381,31 +380,11 @@
123          if callable(default):
124              default = default()
125          if default is MARKER:
126              default = None
127          elif default is not None:
128 -            # rdef.object is the attribute type
129 -            if rdef.object == 'Boolean':
130 -                if not isinstance(default, bool):
131 -                    default = default == 'True' # XXX duh?
132 -            elif rdef.object in ('Int', 'BigInt'):
133 -                if not isinstance(default, (int, long)):
134 -                    default = int(default)
135 -            elif rdef.object == 'Float':
136 -                if not isinstance(default, float):
137 -                    default = float(default)
138 -            elif rdef.object == 'Decimal':
139 -                if not isinstance(default, Decimal):
140 -                    default = Decimal(default)
141 -            elif rdef.object in ('Date', 'Datetime', 'Time'):
142 -                key = '%s.%s' % (rdef.object, default.upper())
143 -                try:
144 -                    default = yams.KEYWORD_MAP[key]()
145 -                except KeyError:
146 -                    default = yams.DATE_FACTORY_MAP[rdef.object](default)
147 -            else:
148 -                default = unicode(default)
149 +            return convert_default_value(rdef, default)
150          return default
151 
152      def has_unique_values(self, rtype):
153          """convenience method to check presence of the UniqueConstraint on a
154          relation