goa/dbmyams.py
changeset 0 b97547f5f1fa
child 935 ec229cc67334
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """extends yams to be able to load google appengine's schemas
       
     2 
       
     3 MISSING FEATURES:
       
     4  - ListProperty, StringList, EmailProperty, etc. (XXX)
       
     5  - ReferenceProperty.verbose_name, collection_name, etc. (XXX)
       
     6 
       
     7 XXX proprify this knowing we'll use goa.db
       
     8 """
       
     9 
       
    10 from os.path import join
       
    11 from datetime import datetime, date, time
       
    12 
       
    13 from google.appengine.ext import db
       
    14 from google.appengine.api import datastore_types
       
    15 
       
    16 from yams.schema2sql import eschema_attrs
       
    17 from yams.constraints import SizeConstraint
       
    18 from yams.reader import PyFileReader
       
    19 from yams.buildobjs import (String, Int, Float, Boolean, Date, Time, Datetime,
       
    20                             Interval, Password, Bytes, ObjectRelation,
       
    21                             SubjectRelation, RestrictedEntityType)
       
    22 from yams.buildobjs import metadefinition, EntityType
       
    23 
       
    24 from cubicweb.schema import CubicWebSchemaLoader
       
    25 from cubicweb.goa import db as goadb
       
    26 
       
    27 # db.Model -> yams ############################################################
       
    28 
       
    29 DBM2Y_TYPESMAP = {
       
    30     basestring: String,
       
    31     datastore_types.Text: String,
       
    32     int: Int,
       
    33     float: Float,
       
    34     bool: Boolean,
       
    35     time: Time,
       
    36     date: Date,
       
    37     datetime: Datetime,
       
    38     datastore_types.Blob: Bytes,
       
    39     }
       
    40 
       
    41 
       
    42 def dbm2y_default_factory(prop, **kwargs):
       
    43     """just wraps the default types map to set
       
    44     basic constraints like `required`, `default`, etc.
       
    45     """
       
    46     yamstype = DBM2Y_TYPESMAP[prop.data_type]
       
    47     if 'default' not in kwargs:
       
    48         default = prop.default_value()
       
    49         if default is not None:
       
    50             kwargs['default'] = default
       
    51     if prop.required:
       
    52         kwargs['required'] = True
       
    53     return yamstype(**kwargs)
       
    54 
       
    55 def dbm2y_string_factory(prop):
       
    56     """like dbm2y_default_factory but also deals with `maxsize` and `vocabulary`"""
       
    57     kwargs = {}
       
    58     if prop.data_type is basestring:
       
    59         kwargs['maxsize'] = 500
       
    60     if prop.choices is not None:
       
    61         kwargs['vocabulary'] = prop.choices
       
    62     return dbm2y_default_factory(prop, **kwargs)
       
    63 
       
    64 def dbm2y_date_factory(prop):
       
    65     """like dbm2y_default_factory but also deals with today / now definition"""
       
    66     kwargs = {}
       
    67     if prop.auto_now_add:
       
    68         if prop.data_type is datetime:
       
    69             kwargs['default'] = 'now'
       
    70         else:
       
    71             kwargs['default'] = 'today'
       
    72     # XXX no equivalent to Django's `auto_now`
       
    73     return dbm2y_default_factory(prop, **kwargs)
       
    74 
       
    75     
       
    76 def dbm2y_relation_factory(etype, prop, multiple=False):
       
    77     """called if `prop` is a `db.ReferenceProperty`"""
       
    78     if multiple:
       
    79         cardinality = '**'
       
    80     elif prop.required:
       
    81         cardinality = '1*'
       
    82     else:
       
    83         cardinality = '?*'
       
    84     # XXX deal with potential kwargs of ReferenceProperty.__init__()
       
    85     try:
       
    86         return SubjectRelation(prop.data_type.kind(), cardinality=cardinality)
       
    87     except AttributeError, ex:
       
    88         # hack, data_type is still _SELF_REFERENCE_MARKER
       
    89         return SubjectRelation(etype, cardinality=cardinality)
       
    90     
       
    91     
       
    92 DBM2Y_FACTORY = {
       
    93     basestring: dbm2y_string_factory,
       
    94     datastore_types.Text: dbm2y_string_factory,
       
    95     int: dbm2y_default_factory,
       
    96     float: dbm2y_default_factory,
       
    97     bool: dbm2y_default_factory,
       
    98     time: dbm2y_date_factory,
       
    99     date: dbm2y_date_factory,
       
   100     datetime: dbm2y_date_factory,
       
   101     datastore_types.Blob: dbm2y_default_factory,
       
   102     }
       
   103 
       
   104 
       
   105 class GaeSchemaLoader(CubicWebSchemaLoader):
       
   106     """Google appengine schema loader class"""
       
   107     def __init__(self, *args, **kwargs):
       
   108         self.use_gauthservice = kwargs.pop('use_gauthservice', False)
       
   109         super(GaeSchemaLoader, self).__init__(*args, **kwargs)
       
   110         self.defined = {}
       
   111         self.created = []
       
   112         self._instantiate_handlers()
       
   113         
       
   114     def finalize(self, register_base_types=False):
       
   115         return self._build_schema('google-appengine', register_base_types)
       
   116 
       
   117     def load_dbmodel(self, name, props):
       
   118         clsdict = {}
       
   119         ordered_props = sorted(props.items(),
       
   120                                key=lambda x: x[1].creation_counter)
       
   121         for pname, prop in ordered_props:
       
   122             if isinstance(prop, db.ListProperty):
       
   123                 if not issubclass(prop.item_type, db.Model):
       
   124                     self.error('ignoring list property with %s item type'
       
   125                                % prop.item_type)
       
   126                     continue
       
   127                 rdef = dbm2y_relation_factory(name, prop, multiple=True)
       
   128             else:
       
   129                 try:
       
   130                     if isinstance(prop, (db.ReferenceProperty,
       
   131                                          goadb.ReferencePropertyStub)):
       
   132                         rdef = dbm2y_relation_factory(name, prop)
       
   133                     else:
       
   134                         rdef = DBM2Y_FACTORY[prop.data_type](prop)
       
   135                 except KeyError, ex:
       
   136                     import traceback
       
   137                     traceback.print_exc()
       
   138                     self.error('ignoring property %s (keyerror on %s)' % (pname, ex))
       
   139                     continue
       
   140             rdef.creation_rank = prop.creation_counter
       
   141             clsdict[pname] = rdef
       
   142         edef = metadefinition(name, (EntityType,), clsdict)
       
   143         self.add_definition(self, edef())
       
   144 
       
   145     def error(self, msg):
       
   146         print 'ERROR:', msg
       
   147 
       
   148     def import_yams_schema(self, ertype, schemamod):
       
   149         erdef = self.pyreader.import_erschema(ertype, schemamod)
       
   150 
       
   151     def import_yams_cube_schema(self, templpath):
       
   152         for filepath in self.get_schema_files(templpath):
       
   153             self.handle_file(filepath)
       
   154         
       
   155     @property
       
   156     def pyreader(self):
       
   157         return self._live_handlers['.py']
       
   158         
       
   159 import os
       
   160 from cubicweb import CW_SOFTWARE_ROOT
       
   161 
       
   162 if os.environ.get('APYCOT_ROOT'):
       
   163     SCHEMAS_LIB_DIRECTORY = join(os.environ['APYCOT_ROOT'],
       
   164                                  'local', 'share', 'cubicweb', 'schemas')
       
   165 else:
       
   166     SCHEMAS_LIB_DIRECTORY = join(CW_SOFTWARE_ROOT, 'schemas')
       
   167 
       
   168 def load_schema(config, schemaclasses=None, extrahook=None):
       
   169     """high level method to load all the schema for a lax application"""
       
   170     # IMPORTANT NOTE: dbmodel schemas must be imported **BEFORE**
       
   171     # the loader is instantiated because this is where the dbmodels
       
   172     # are registered in the yams schema
       
   173     for compname in config['included-cubes']:
       
   174         comp = __import__('%s.schema' % compname)
       
   175     loader = GaeSchemaLoader(use_gauthservice=config['use-google-auth'], db=db)
       
   176     loader.lib_directory = SCHEMAS_LIB_DIRECTORY
       
   177     if schemaclasses is not None:
       
   178         for cls in schemaclasses:
       
   179             loader.load_dbmodel(cls.__name__, goadb.extract_dbmodel(cls))
       
   180     elif config['schema-type'] == 'dbmodel':
       
   181         import schema as appschema
       
   182         for objname, obj in vars(appschema).items():
       
   183             if isinstance(obj, type) and issubclass(obj, goadb.Model) and obj.__module__ == appschema.__name__:
       
   184                 loader.load_dbmodel(obj.__name__, goadb.extract_dbmodel(obj))
       
   185     for erschema in ('EGroup', 'EEType', 'ERType', 'RQLExpression',
       
   186                      'is_', 'is_instance_of',
       
   187                      'read_permission', 'add_permission',
       
   188                      'delete_permission', 'update_permission'):
       
   189         loader.import_yams_schema(erschema, 'bootstrap')  
       
   190     loader.handle_file(join(SCHEMAS_LIB_DIRECTORY, 'base.py'))
       
   191     cubes = config['included-yams-cubes']
       
   192     for cube in reversed(config.expand_cubes(cubes)):
       
   193         config.info('loading cube %s', cube)
       
   194         loader.import_yams_cube_schema(config.cube_dir(cube))
       
   195     if config['schema-type'] == 'yams':
       
   196         loader.import_yams_cube_schema('.')
       
   197     if extrahook is not None:
       
   198         extrahook(loader)
       
   199     if config['use-google-auth']:
       
   200         loader.defined['EUser'].remove_relation('upassword')
       
   201         loader.defined['EUser'].permissions['add'] = ()
       
   202         loader.defined['EUser'].permissions['delete'] = ()
       
   203     for etype in ('EGroup', 'RQLExpression'):
       
   204         read_perm_rel = loader.defined[etype].get_relations('read_permission').next()
       
   205         read_perm_rel.cardinality = '**'
       
   206     # XXX not yet ready for EUser workflow
       
   207     loader.defined['EUser'].remove_relation('in_state')
       
   208     loader.defined['EUser'].remove_relation('wf_info_for')
       
   209     # remove RQLConstraint('NOT O name "owners"') on EUser in_group EGroup
       
   210     # since "owners" group is not persistent with gae
       
   211     loader.defined['EUser'].get_relations('in_group').next().constraints = []
       
   212     # return the full schema including the cubes' schema
       
   213     for ertype in loader.defined.values():
       
   214         if getattr(ertype, 'inlined', False):
       
   215             ertype.inlined = False
       
   216     return loader.finalize()
       
   217