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.""" |
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')) |
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) |