devtools/dataimport.py
branchstable
changeset 4818 9f9bfbcdecfd
parent 4734 4ae30c9ca11b
child 4847 9466604ef448
equal deleted inserted replaced
4816:c02583cb80a9 4818:9f9bfbcdecfd
    57 import sys
    57 import sys
    58 import csv
    58 import csv
    59 import traceback
    59 import traceback
    60 import os.path as osp
    60 import os.path as osp
    61 from StringIO import StringIO
    61 from StringIO import StringIO
       
    62 from copy import copy
    62 
    63 
    63 from logilab.common import shellutils
    64 from logilab.common import shellutils
       
    65 from logilab.common.date import strptime
       
    66 from logilab.common.decorators import cached
    64 from logilab.common.deprecation import deprecated
    67 from logilab.common.deprecation import deprecated
       
    68 
    65 
    69 
    66 def ucsvreader_pb(filepath, encoding='utf-8', separator=',', quote='"',
    70 def ucsvreader_pb(filepath, encoding='utf-8', separator=',', quote='"',
    67                   skipfirst=False, withpb=True):
    71                   skipfirst=False, withpb=True):
    68     """same as ucsvreader but a progress bar is displayed as we iter on rows"""
    72     """same as ucsvreader but a progress bar is displayed as we iter on rows"""
    69     if not osp.exists(filepath):
    73     if not osp.exists(filepath):
    88     if skipfirst:
    92     if skipfirst:
    89         it.next()
    93         it.next()
    90     for row in it:
    94     for row in it:
    91         yield [item.decode(encoding) for item in row]
    95         yield [item.decode(encoding) for item in row]
    92 
    96 
    93 utf8csvreader = deprecated('use ucsvreader instead')(ucsvreader)
       
    94 
       
    95 def commit_every(nbit, store, it):
    97 def commit_every(nbit, store, it):
    96     for i, x in enumerate(it):
    98     for i, x in enumerate(it):
    97         yield x
    99         yield x
    98         if nbit is not None and i % nbit:
   100         if nbit is not None and i % nbit:
    99             store.checkpoint()
   101             store.checkpoint()
   127     """
   129     """
   128     res = {}
   130     res = {}
   129     assert isinstance(row, dict)
   131     assert isinstance(row, dict)
   130     assert isinstance(map, list)
   132     assert isinstance(map, list)
   131     for src, dest, funcs in map:
   133     for src, dest, funcs in map:
   132         assert not (required in funcs and optional in funcs), "optional and required checks are exclusive"
   134         assert not (required in funcs and optional in funcs), \
       
   135                "optional and required checks are exclusive"
   133         res[dest] = row[src]
   136         res[dest] = row[src]
   134         try:
   137         try:
   135             for func in funcs:
   138             for func in funcs:
   136                 res[dest] = func(res[dest])
   139                 res[dest] = func(res[dest])
   137             if res[dest] is None:
   140                 if res[dest] is None:
   138                 raise AssertionError('undetermined value')
   141                     break
   139         except AssertionError, err:
   142         except ValueError, err:
   140             if optional in funcs:
   143             raise ValueError('error with %r field: %s' % (src, err))
   141                 # Forget this field if exception is coming from optional function
       
   142                del res[dest]
       
   143             else:
       
   144                raise AssertionError('error with "%s" field: %s' % (src, err))
       
   145     return res
   144     return res
   146 
   145 
   147 
   146 
   148 # user interactions ############################################################
   147 # user interactions ############################################################
   149 
   148 
   176             if self.ctl.catcherrors:
   175             if self.ctl.catcherrors:
   177                 self.ctl.record_error(self.key, None, type, value, traceback)
   176                 self.ctl.record_error(self.key, None, type, value, traceback)
   178                 return True # silent
   177                 return True # silent
   179 
   178 
   180 
   179 
   181 # base sanitizing functions ####################################################
   180 # base sanitizing/coercing functions ###########################################
   182 
       
   183 def capitalize_if_unicase(txt):
       
   184     if txt.isupper() or txt.islower():
       
   185         return txt.capitalize()
       
   186     return txt
       
   187 
       
   188 def uppercase(txt):
       
   189     return txt.upper()
       
   190 
       
   191 def lowercase(txt):
       
   192     return txt.lower()
       
   193 
       
   194 def no_space(txt):
       
   195     return txt.replace(' ','')
       
   196 
       
   197 def no_uspace(txt):
       
   198     return txt.replace(u'\xa0','')
       
   199 
       
   200 def no_dash(txt):
       
   201     return txt.replace('-','')
       
   202 
       
   203 def decimal(value):
       
   204     """cast to float but with comma replacement
       
   205 
       
   206     We take care of some locale format as replacing ',' by '.'"""
       
   207     value = value.replace(',', '.')
       
   208     try:
       
   209         return float(value)
       
   210     except Exception, err:
       
   211         raise AssertionError(err)
       
   212 
       
   213 def integer(value):
       
   214     try:
       
   215         return int(value)
       
   216     except Exception, err:
       
   217         raise AssertionError(err)
       
   218 
       
   219 def strip(txt):
       
   220     return txt.strip()
       
   221 
       
   222 def yesno(value):
       
   223     """simple heuristic that returns boolean value
       
   224 
       
   225     >>> yesno("Yes")
       
   226     True
       
   227     >>> yesno("oui")
       
   228     True
       
   229     >>> yesno("1")
       
   230     True
       
   231     >>> yesno("11")
       
   232     True
       
   233     >>> yesno("")
       
   234     False
       
   235     >>> yesno("Non")
       
   236     False
       
   237     >>> yesno("blablabla")
       
   238     False
       
   239     """
       
   240     if value:
       
   241         return value.lower()[0] in 'yo1'
       
   242     return False
       
   243 
       
   244 def isalpha(value):
       
   245     if value.isalpha():
       
   246         return value
       
   247     raise AssertionError("not all characters in the string alphabetic")
       
   248 
   181 
   249 def optional(value):
   182 def optional(value):
   250     """validation error will not been raised if you add this checker in chain"""
   183     """validation error will not been raised if you add this checker in chain"""
   251     return value
   184     if value:
       
   185         return value
       
   186     return None
   252 
   187 
   253 def required(value):
   188 def required(value):
   254     """raise AssertionError is value is empty
   189     """raise ValueError is value is empty
   255 
   190 
   256     This check should be often found in last position in the chain.
   191     This check should be often found in last position in the chain.
   257     """
   192     """
   258     if bool(value):
   193     if value:
   259         return value
   194         return value
   260     raise AssertionError("required")
   195     raise ValueError("required")
   261 
   196 
   262 @deprecated('use required(value)')
   197 def todatetime(format='%d/%m/%Y'):
   263 def nonempty(value):
   198     """return a transformation function to turn string input value into a
   264     return required(value)
   199     `datetime.datetime` instance, using given format.
   265 
   200 
   266 @deprecated('use integer(value)')
   201     Follow it by `todate` or `totime` functions from `logilab.common.date` if
   267 def alldigits(txt):
   202     you want a `date`/`time` instance instead of `datetime`.
   268     if txt.isdigit():
   203     """
   269         return txt
   204     def coerce(value):
   270     else:
   205         return strptime(value, format)
   271         return u''
   206     return coerce
   272 
   207 
       
   208 def call_transform_method(methodname, *args, **kwargs):
       
   209     """return value returned by calling the given method on input"""
       
   210     def coerce(value):
       
   211         return getattr(value, methodname)(*args, **kwargs)
       
   212     return coerce
       
   213 
       
   214 def call_check_method(methodname, *args, **kwargs):
       
   215     """check value returned by calling the given method on input is true,
       
   216     else raise ValueError
       
   217     """
       
   218     def check(value):
       
   219         if getattr(value, methodname)(*args, **kwargs):
       
   220             return value
       
   221         raise ValueError('%s not verified on %r' % (methodname, value))
       
   222     return check
   273 
   223 
   274 # base integrity checking functions ############################################
   224 # base integrity checking functions ############################################
   275 
   225 
   276 def check_doubles(buckets):
   226 def check_doubles(buckets):
   277     """Extract the keys that have more than one item in their bucket."""
   227     """Extract the keys that have more than one item in their bucket."""
   314         assert isinstance(item, dict), 'item is not a dict but a %s' % type(item)
   264         assert isinstance(item, dict), 'item is not a dict but a %s' % type(item)
   315         eid = item['eid'] = self._put(type, item)
   265         eid = item['eid'] = self._put(type, item)
   316         self.eids[eid] = item
   266         self.eids[eid] = item
   317         self.types.setdefault(type, []).append(eid)
   267         self.types.setdefault(type, []).append(eid)
   318 
   268 
   319     def relate(self, eid_from, rtype, eid_to):
   269     def relate(self, eid_from, rtype, eid_to, inlined=False):
   320         """Add new relation (reverse type support is available)
   270         """Add new relation (reverse type support is available)
   321 
   271 
   322         >>> 1,2 = eid_from, eid_to
   272         >>> 1,2 = eid_from, eid_to
   323         >>> self.relate(eid_from, 'in_group', eid_to)
   273         >>> self.relate(eid_from, 'in_group', eid_to)
   324         1, 'in_group', 2
   274         1, 'in_group', 2
   357 
   307 
   358         # Build index with specified key
   308         # Build index with specified key
   359         func = lambda x: x[key]
   309         func = lambda x: x[key]
   360         self.build_index(name, type, func)
   310         self.build_index(name, type, func)
   361 
   311 
   362     @deprecated('get_many() deprecated. Use fetch() instead')
       
   363     def get_many(self, name, key):
       
   364         return self.fetch(name, key, unique=False)
       
   365 
       
   366     @deprecated('get_one() deprecated. Use fetch(..., unique=True) instead')
       
   367     def get_one(self, name, key):
       
   368         return self.fetch(name, key, unique=True)
       
   369 
       
   370     def fetch(self, name, key, unique=False, decorator=None):
   312     def fetch(self, name, key, unique=False, decorator=None):
   371         """
   313         """
   372             decorator is a callable method or an iterator of callable methods (usually a lambda function)
   314             decorator is a callable method or an iterator of callable methods (usually a lambda function)
   373             decorator=lambda x: x[:1] (first value is returned)
   315             decorator=lambda x: x[:1] (first value is returned)
   374 
   316 
   396             return self._rql(*args)
   338             return self._rql(*args)
   397 
   339 
   398     def checkpoint(self):
   340     def checkpoint(self):
   399         pass
   341         pass
   400 
   342 
       
   343     @property
       
   344     def nb_inserted_entities(self):
       
   345         return len(self.eids)
       
   346     @property
       
   347     def nb_inserted_types(self):
       
   348         return len(self.types)
       
   349     @property
       
   350     def nb_inserted_relations(self):
       
   351         return len(self.relations)
       
   352 
       
   353     @deprecated('[3.6] get_many() deprecated. Use fetch() instead')
       
   354     def get_many(self, name, key):
       
   355         return self.fetch(name, key, unique=False)
       
   356 
       
   357     @deprecated('[3.6] get_one() deprecated. Use fetch(..., unique=True) instead')
       
   358     def get_one(self, name, key):
       
   359         return self.fetch(name, key, unique=True)
       
   360 
   401 
   361 
   402 class RQLObjectStore(ObjectStore):
   362 class RQLObjectStore(ObjectStore):
   403     """ObjectStore that works with an actual RQL repository (production mode)"""
   363     """ObjectStore that works with an actual RQL repository (production mode)"""
   404     _rql = None # bw compat
   364     _rql = None # bw compat
   405 
   365 
   410                 # connection
   370                 # connection
   411                 cnx = session
   371                 cnx = session
   412                 session = session.request()
   372                 session = session.request()
   413                 session.set_pool = lambda : None
   373                 session.set_pool = lambda : None
   414                 checkpoint = checkpoint or cnx.commit
   374                 checkpoint = checkpoint or cnx.commit
       
   375             else:
       
   376                 session.set_pool()
   415             self.session = session
   377             self.session = session
   416             self.checkpoint = checkpoint or session.commit
   378             self._checkpoint = checkpoint or session.commit
   417         elif checkpoint is not None:
   379         elif checkpoint is not None:
   418             self.checkpoint = checkpoint
   380             self._checkpoint = checkpoint
       
   381             # XXX .session
       
   382 
       
   383     def checkpoint(self):
       
   384         self._checkpoint()
       
   385         self.session.set_pool()
   419 
   386 
   420     def rql(self, *args):
   387     def rql(self, *args):
   421         if self._rql is not None:
   388         if self._rql is not None:
   422             return self._rql(*args)
   389             return self._rql(*args)
   423         self.session.set_pool()
       
   424         return self.session.execute(*args)
   390         return self.session.execute(*args)
   425 
   391 
   426     def create_entity(self, *args, **kwargs):
   392     def create_entity(self, *args, **kwargs):
   427         self.session.set_pool()
       
   428         entity = self.session.create_entity(*args, **kwargs)
   393         entity = self.session.create_entity(*args, **kwargs)
   429         self.eids[entity.eid] = entity
   394         self.eids[entity.eid] = entity
   430         self.types.setdefault(args[0], []).append(entity.eid)
   395         self.types.setdefault(args[0], []).append(entity.eid)
   431         return entity
   396         return entity
   432 
   397 
   433     def _put(self, type, item):
   398     def _put(self, type, item):
   434         query = ('INSERT %s X: ' % type) + ', '.join('X %s %%(%s)s' % (k, k)
   399         query = ('INSERT %s X: ' % type) + ', '.join('X %s %%(%s)s' % (k, k)
   435                                                      for k in item)
   400                                                      for k in item)
   436         return self.rql(query, item)[0][0]
   401         return self.rql(query, item)[0][0]
   437 
   402 
   438     def relate(self, eid_from, rtype, eid_to):
   403     def relate(self, eid_from, rtype, eid_to, inlined=False):
   439         # if reverse relation is found, eids are exchanged
   404         # if reverse relation is found, eids are exchanged
   440         eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(
   405         eid_from, rtype, eid_to = super(RQLObjectStore, self).relate(
   441             eid_from, rtype, eid_to)
   406             eid_from, rtype, eid_to)
   442         self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
   407         self.rql('SET X %s Y WHERE X eid %%(x)s, Y eid %%(y)s' % rtype,
   443                   {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y'))
   408                   {'x': int(eid_from), 'y': int(eid_to)}, ('x', 'y'))
   511                     if err:
   476                     if err:
   512                         self.errors[title] = (help, err)
   477                         self.errors[title] = (help, err)
   513         self.store.checkpoint()
   478         self.store.checkpoint()
   514         nberrors = sum(len(err[1]) for err in self.errors.values())
   479         nberrors = sum(len(err[1]) for err in self.errors.values())
   515         self.tell('\nImport completed: %i entities, %i types, %i relations and %i errors'
   480         self.tell('\nImport completed: %i entities, %i types, %i relations and %i errors'
   516                   % (len(self.store.eids), len(self.store.types),
   481                   % (self.store.nb_inserted_entities,
   517                      len(self.store.relations), nberrors))
   482                      self.store.nb_inserted_types,
       
   483                      self.store.nb_inserted_relations,
       
   484                      nberrors))
   518         if self.errors:
   485         if self.errors:
   519             if self.askerror == 2 or (self.askerror and confirm('Display errors ?')):
   486             if self.askerror == 2 or (self.askerror and confirm('Display errors ?')):
   520                 from pprint import pformat
   487                 from pprint import pformat
   521                 for errkey, error in self.errors.items():
   488                 for errkey, error in self.errors.items():
   522                     self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1])))
   489                     self.tell("\n%s (%s): %d\n" % (error[0], errkey, len(error[1])))
   543         self._tell(msg)
   510         self._tell(msg)
   544 
   511 
   545     def iter_and_commit(self, datakey):
   512     def iter_and_commit(self, datakey):
   546         """iter rows, triggering commit every self.commitevery iterations"""
   513         """iter rows, triggering commit every self.commitevery iterations"""
   547         return commit_every(self.commitevery, self.store, self.get_data(datakey))
   514         return commit_every(self.commitevery, self.store, self.get_data(datakey))
       
   515 
       
   516 
       
   517 
       
   518 from datetime import datetime
       
   519 from cubicweb.schema import META_RTYPES, VIRTUAL_RTYPES
       
   520 
       
   521 
       
   522 class NoHookRQLObjectStore(RQLObjectStore):
       
   523     """ObjectStore that works with an actual RQL repository (production mode)"""
       
   524     _rql = None # bw compat
       
   525 
       
   526     def __init__(self, session, metagen=None, baseurl=None):
       
   527         super(NoHookRQLObjectStore, self).__init__(session)
       
   528         self.source = session.repo.system_source
       
   529         self.rschema = session.repo.schema.rschema
       
   530         self.add_relation = self.source.add_relation
       
   531         if metagen is None:
       
   532             metagen = MetaGenerator(session, baseurl)
       
   533         self.metagen = metagen
       
   534         self._nb_inserted_entities = 0
       
   535         self._nb_inserted_types = 0
       
   536         self._nb_inserted_relations = 0
       
   537         self.rql = session.unsafe_execute
       
   538 
       
   539     def create_entity(self, etype, **kwargs):
       
   540         for k, v in kwargs.iteritems():
       
   541             kwargs[k] = getattr(v, 'eid', v)
       
   542         entity, rels = self.metagen.base_etype_dicts(etype)
       
   543         entity = copy(entity)
       
   544         entity._related_cache = {}
       
   545         self.metagen.init_entity(entity)
       
   546         entity.update(kwargs)
       
   547         session = self.session
       
   548         self.source.add_entity(session, entity)
       
   549         self.source.add_info(session, entity, self.source, complete=False)
       
   550         for rtype, targeteids in rels.iteritems():
       
   551             # targeteids may be a single eid or a list of eids
       
   552             inlined = self.rschema(rtype).inlined
       
   553             try:
       
   554                 for targeteid in targeteids:
       
   555                     self.add_relation(session, entity.eid, rtype, targeteid,
       
   556                                       inlined)
       
   557             except TypeError:
       
   558                 self.add_relation(session, entity.eid, rtype, targeteids,
       
   559                                   inlined)
       
   560         self._nb_inserted_entities += 1
       
   561         return entity
       
   562 
       
   563     def relate(self, eid_from, rtype, eid_to):
       
   564         assert not rtype.startswith('reverse_')
       
   565         self.add_relation(self.session, eid_from, rtype, eid_to,
       
   566                           self.rschema(rtype).inlined)
       
   567         self._nb_inserted_relations += 1
       
   568 
       
   569     @property
       
   570     def nb_inserted_entities(self):
       
   571         return self._nb_inserted_entities
       
   572     @property
       
   573     def nb_inserted_types(self):
       
   574         return self._nb_inserted_types
       
   575     @property
       
   576     def nb_inserted_relations(self):
       
   577         return self._nb_inserted_relations
       
   578 
       
   579     def _put(self, type, item):
       
   580         raise RuntimeError('use create entity')
       
   581 
       
   582 
       
   583 class MetaGenerator(object):
       
   584     def __init__(self, session, baseurl=None):
       
   585         self.session = session
       
   586         self.source = session.repo.system_source
       
   587         self.time = datetime.now()
       
   588         if baseurl is None:
       
   589             config = session.vreg.config
       
   590             baseurl = config['base-url'] or config.default_base_url()
       
   591         if not baseurl[-1] == '/':
       
   592             baseurl += '/'
       
   593         self.baseurl =  baseurl
       
   594         # attributes/relations shared by all entities of the same type
       
   595         self.etype_attrs = []
       
   596         self.etype_rels = []
       
   597         # attributes/relations specific to each entity
       
   598         self.entity_attrs = ['eid', 'cwuri']
       
   599         #self.entity_rels = [] XXX not handled (YAGNI?)
       
   600         schema = session.vreg.schema
       
   601         rschema = schema.rschema
       
   602         for rtype in META_RTYPES:
       
   603             if rtype in ('eid', 'cwuri') or rtype in VIRTUAL_RTYPES:
       
   604                 continue
       
   605             if rschema(rtype).final:
       
   606                 self.etype_attrs.append(rtype)
       
   607             else:
       
   608                 self.etype_rels.append(rtype)
       
   609         if not schema._eid_index:
       
   610             # test schema loaded from the fs
       
   611             self.gen_is = self.test_gen_is
       
   612             self.gen_is_instance_of = self.test_gen_is_instanceof
       
   613 
       
   614     @cached
       
   615     def base_etype_dicts(self, etype):
       
   616         entity = self.session.vreg['etypes'].etype_class(etype)(self.session)
       
   617         # entity are "surface" copied, avoid shared dict between copies
       
   618         del entity.cw_extra_kwargs
       
   619         for attr in self.etype_attrs:
       
   620             entity[attr] = self.generate(entity, attr)
       
   621         rels = {}
       
   622         for rel in self.etype_rels:
       
   623             rels[rel] = self.generate(entity, rel)
       
   624         return entity, rels
       
   625 
       
   626     def init_entity(self, entity):
       
   627         for attr in self.entity_attrs:
       
   628             entity[attr] = self.generate(entity, attr)
       
   629         entity.eid = entity['eid']
       
   630 
       
   631     def generate(self, entity, rtype):
       
   632         return getattr(self, 'gen_%s' % rtype)(entity)
       
   633 
       
   634     def gen_eid(self, entity):
       
   635         return self.source.create_eid(self.session)
       
   636 
       
   637     def gen_cwuri(self, entity):
       
   638         return u'%seid/%s' % (self.baseurl, entity['eid'])
       
   639 
       
   640     def gen_creation_date(self, entity):
       
   641         return self.time
       
   642     def gen_modification_date(self, entity):
       
   643         return self.time
       
   644 
       
   645     def gen_is(self, entity):
       
   646         return entity.e_schema.eid
       
   647     def gen_is_instance_of(self, entity):
       
   648         eids = []
       
   649         for etype in entity.e_schema.ancestors() + [entity.e_schema]:
       
   650             eids.append(entity.e_schema.eid)
       
   651         return eids
       
   652 
       
   653     def gen_created_by(self, entity):
       
   654         return self.session.user.eid
       
   655     def gen_owned_by(self, entity):
       
   656         return self.session.user.eid
       
   657 
       
   658     # implementations of gen_is / gen_is_instance_of to use during test where
       
   659     # schema has been loaded from the fs (hence entity type schema eids are not
       
   660     # known)
       
   661     def test_gen_is(self, entity):
       
   662         from cubicweb.hooks.metadata import eschema_eid
       
   663         return eschema_eid(self.session, entity.e_schema)
       
   664     def test_gen_is_instanceof(self, entity):
       
   665         from cubicweb.hooks.metadata import eschema_eid
       
   666         eids = []
       
   667         for eschema in entity.e_schema.ancestors() + [entity.e_schema]:
       
   668             eids.append(eschema_eid(self.session, eschema))
       
   669         return eids
       
   670 
       
   671 
       
   672 ################################################################################
       
   673 
       
   674 utf8csvreader = deprecated('[3.6] use ucsvreader instead')(ucsvreader)
       
   675 
       
   676 @deprecated('[3.6] use required')
       
   677 def nonempty(value):
       
   678     return required(value)
       
   679 
       
   680 @deprecated("[3.6] use call_check_method('isdigit')")
       
   681 def alldigits(txt):
       
   682     if txt.isdigit():
       
   683         return txt
       
   684     else:
       
   685         return u''
       
   686 
       
   687 @deprecated("[3.7] too specific, will move away, copy me")
       
   688 def capitalize_if_unicase(txt):
       
   689     if txt.isupper() or txt.islower():
       
   690         return txt.capitalize()
       
   691     return txt
       
   692 
       
   693 @deprecated("[3.7] too specific, will move away, copy me")
       
   694 def yesno(value):
       
   695     """simple heuristic that returns boolean value
       
   696 
       
   697     >>> yesno("Yes")
       
   698     True
       
   699     >>> yesno("oui")
       
   700     True
       
   701     >>> yesno("1")
       
   702     True
       
   703     >>> yesno("11")
       
   704     True
       
   705     >>> yesno("")
       
   706     False
       
   707     >>> yesno("Non")
       
   708     False
       
   709     >>> yesno("blablabla")
       
   710     False
       
   711     """
       
   712     if value:
       
   713         return value.lower()[0] in 'yo1'
       
   714     return False
       
   715 
       
   716 @deprecated("[3.7] use call_check_method('isalpha')")
       
   717 def isalpha(value):
       
   718     if value.isalpha():
       
   719         return value
       
   720     raise ValueError("not all characters in the string alphabetic")
       
   721 
       
   722 @deprecated("[3.7] use call_transform_method('upper')")
       
   723 def uppercase(txt):
       
   724     return txt.upper()
       
   725 
       
   726 @deprecated("[3.7] use call_transform_method('lower')")
       
   727 def lowercase(txt):
       
   728     return txt.lower()
       
   729 
       
   730 @deprecated("[3.7] use call_transform_method('replace', ' ', '')")
       
   731 def no_space(txt):
       
   732     return txt.replace(' ','')
       
   733 
       
   734 @deprecated("[3.7] use call_transform_method('replace', u'\xa0', '')")
       
   735 def no_uspace(txt):
       
   736     return txt.replace(u'\xa0','')
       
   737 
       
   738 @deprecated("[3.7] use call_transform_method('replace', '-', '')")
       
   739 def no_dash(txt):
       
   740     return txt.replace('-','')
       
   741 
       
   742 @deprecated("[3.7] use call_transform_method('strip')")
       
   743 def strip(txt):
       
   744     return txt.strip()
       
   745 
       
   746 @deprecated("[3.7] use call_transform_method('replace', ',', '.'), float")
       
   747 def decimal(value):
       
   748     return comma_float(value)
       
   749 
       
   750 @deprecated('[3.7] use int builtin')
       
   751 def integer(value):
       
   752     return int(value)