diff -r 000000000000 -r b97547f5f1fa goa/dbmyams.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/goa/dbmyams.py Wed Nov 05 15:52:50 2008 +0100 @@ -0,0 +1,217 @@ +"""extends yams to be able to load google appengine's schemas + +MISSING FEATURES: + - ListProperty, StringList, EmailProperty, etc. (XXX) + - ReferenceProperty.verbose_name, collection_name, etc. (XXX) + +XXX proprify this knowing we'll use goa.db +""" + +from os.path import join +from datetime import datetime, date, time + +from google.appengine.ext import db +from google.appengine.api import datastore_types + +from yams.schema2sql import eschema_attrs +from yams.constraints import SizeConstraint +from yams.reader import PyFileReader +from yams.buildobjs import (String, Int, Float, Boolean, Date, Time, Datetime, + Interval, Password, Bytes, ObjectRelation, + SubjectRelation, RestrictedEntityType) +from yams.buildobjs import metadefinition, EntityType + +from cubicweb.schema import CubicWebSchemaLoader +from cubicweb.goa import db as goadb + +# db.Model -> yams ############################################################ + +DBM2Y_TYPESMAP = { + basestring: String, + datastore_types.Text: String, + int: Int, + float: Float, + bool: Boolean, + time: Time, + date: Date, + datetime: Datetime, + datastore_types.Blob: Bytes, + } + + +def dbm2y_default_factory(prop, **kwargs): + """just wraps the default types map to set + basic constraints like `required`, `default`, etc. + """ + yamstype = DBM2Y_TYPESMAP[prop.data_type] + if 'default' not in kwargs: + default = prop.default_value() + if default is not None: + kwargs['default'] = default + if prop.required: + kwargs['required'] = True + return yamstype(**kwargs) + +def dbm2y_string_factory(prop): + """like dbm2y_default_factory but also deals with `maxsize` and `vocabulary`""" + kwargs = {} + if prop.data_type is basestring: + kwargs['maxsize'] = 500 + if prop.choices is not None: + kwargs['vocabulary'] = prop.choices + return dbm2y_default_factory(prop, **kwargs) + +def dbm2y_date_factory(prop): + """like dbm2y_default_factory but also deals with today / now definition""" + kwargs = {} + if prop.auto_now_add: + if prop.data_type is datetime: + kwargs['default'] = 'now' + else: + kwargs['default'] = 'today' + # XXX no equivalent to Django's `auto_now` + return dbm2y_default_factory(prop, **kwargs) + + +def dbm2y_relation_factory(etype, prop, multiple=False): + """called if `prop` is a `db.ReferenceProperty`""" + if multiple: + cardinality = '**' + elif prop.required: + cardinality = '1*' + else: + cardinality = '?*' + # XXX deal with potential kwargs of ReferenceProperty.__init__() + try: + return SubjectRelation(prop.data_type.kind(), cardinality=cardinality) + except AttributeError, ex: + # hack, data_type is still _SELF_REFERENCE_MARKER + return SubjectRelation(etype, cardinality=cardinality) + + +DBM2Y_FACTORY = { + basestring: dbm2y_string_factory, + datastore_types.Text: dbm2y_string_factory, + int: dbm2y_default_factory, + float: dbm2y_default_factory, + bool: dbm2y_default_factory, + time: dbm2y_date_factory, + date: dbm2y_date_factory, + datetime: dbm2y_date_factory, + datastore_types.Blob: dbm2y_default_factory, + } + + +class GaeSchemaLoader(CubicWebSchemaLoader): + """Google appengine schema loader class""" + def __init__(self, *args, **kwargs): + self.use_gauthservice = kwargs.pop('use_gauthservice', False) + super(GaeSchemaLoader, self).__init__(*args, **kwargs) + self.defined = {} + self.created = [] + self._instantiate_handlers() + + def finalize(self, register_base_types=False): + return self._build_schema('google-appengine', register_base_types) + + def load_dbmodel(self, name, props): + clsdict = {} + ordered_props = sorted(props.items(), + key=lambda x: x[1].creation_counter) + for pname, prop in ordered_props: + if isinstance(prop, db.ListProperty): + if not issubclass(prop.item_type, db.Model): + self.error('ignoring list property with %s item type' + % prop.item_type) + continue + rdef = dbm2y_relation_factory(name, prop, multiple=True) + else: + try: + if isinstance(prop, (db.ReferenceProperty, + goadb.ReferencePropertyStub)): + rdef = dbm2y_relation_factory(name, prop) + else: + rdef = DBM2Y_FACTORY[prop.data_type](prop) + except KeyError, ex: + import traceback + traceback.print_exc() + self.error('ignoring property %s (keyerror on %s)' % (pname, ex)) + continue + rdef.creation_rank = prop.creation_counter + clsdict[pname] = rdef + edef = metadefinition(name, (EntityType,), clsdict) + self.add_definition(self, edef()) + + def error(self, msg): + print 'ERROR:', msg + + def import_yams_schema(self, ertype, schemamod): + erdef = self.pyreader.import_erschema(ertype, schemamod) + + def import_yams_cube_schema(self, templpath): + for filepath in self.get_schema_files(templpath): + self.handle_file(filepath) + + @property + def pyreader(self): + return self._live_handlers['.py'] + +import os +from cubicweb import CW_SOFTWARE_ROOT + +if os.environ.get('APYCOT_ROOT'): + SCHEMAS_LIB_DIRECTORY = join(os.environ['APYCOT_ROOT'], + 'local', 'share', 'cubicweb', 'schemas') +else: + SCHEMAS_LIB_DIRECTORY = join(CW_SOFTWARE_ROOT, 'schemas') + +def load_schema(config, schemaclasses=None, extrahook=None): + """high level method to load all the schema for a lax application""" + # IMPORTANT NOTE: dbmodel schemas must be imported **BEFORE** + # the loader is instantiated because this is where the dbmodels + # are registered in the yams schema + for compname in config['included-cubes']: + comp = __import__('%s.schema' % compname) + loader = GaeSchemaLoader(use_gauthservice=config['use-google-auth'], db=db) + loader.lib_directory = SCHEMAS_LIB_DIRECTORY + if schemaclasses is not None: + for cls in schemaclasses: + loader.load_dbmodel(cls.__name__, goadb.extract_dbmodel(cls)) + elif config['schema-type'] == 'dbmodel': + import schema as appschema + for objname, obj in vars(appschema).items(): + if isinstance(obj, type) and issubclass(obj, goadb.Model) and obj.__module__ == appschema.__name__: + loader.load_dbmodel(obj.__name__, goadb.extract_dbmodel(obj)) + for erschema in ('EGroup', 'EEType', 'ERType', 'RQLExpression', + 'is_', 'is_instance_of', + 'read_permission', 'add_permission', + 'delete_permission', 'update_permission'): + loader.import_yams_schema(erschema, 'bootstrap') + loader.handle_file(join(SCHEMAS_LIB_DIRECTORY, 'base.py')) + cubes = config['included-yams-cubes'] + for cube in reversed(config.expand_cubes(cubes)): + config.info('loading cube %s', cube) + loader.import_yams_cube_schema(config.cube_dir(cube)) + if config['schema-type'] == 'yams': + loader.import_yams_cube_schema('.') + if extrahook is not None: + extrahook(loader) + if config['use-google-auth']: + loader.defined['EUser'].remove_relation('upassword') + loader.defined['EUser'].permissions['add'] = () + loader.defined['EUser'].permissions['delete'] = () + for etype in ('EGroup', 'RQLExpression'): + read_perm_rel = loader.defined[etype].get_relations('read_permission').next() + read_perm_rel.cardinality = '**' + # XXX not yet ready for EUser workflow + loader.defined['EUser'].remove_relation('in_state') + loader.defined['EUser'].remove_relation('wf_info_for') + # remove RQLConstraint('NOT O name "owners"') on EUser in_group EGroup + # since "owners" group is not persistent with gae + loader.defined['EUser'].get_relations('in_group').next().constraints = [] + # return the full schema including the cubes' schema + for ertype in loader.defined.values(): + if getattr(ertype, 'inlined', False): + ertype.inlined = False + return loader.finalize() +