--- /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()
+