Contact: fumanchu@aminus.org

Log in as guest/dejavu to create tickets

I think I've seen this ORM somewhere before...

Changeset 420

Show
Ignore:
Timestamp:
03/11/07 02:33:51
Author:
fumanchu
Message:

New 'repair' mode for map_all, map, sync. New 'automap' arg for arena.storage. Return of MappingError?.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/arenas.py

    r419 r420  
    102102            del self.stores[name] 
    103103     
    104     def map_all(self): 
     104    def map_all(self, conflict_mode='error'): 
    105105        """Map all registered classes to internal storage structures. 
    106106         
     
    112112        This method is idempotent, but that doesn't mean cheap. Try not 
    113113        to call it very often (once at app startup is usually enough). 
     114         
     115        conflict_mode: This argument determines what happens when there are 
     116        discrepancies between the Dejavu model and the actual database. 
     117             
     118            If 'error' (the default), MappingError is raised for the 
     119            first issue and the sync process is aborted. 
     120             
     121            If 'warn', then a warning is raised (instead of an error) 
     122            for each issue, and the sync process is not aborted. This 
     123            allows you to see all errors at once, without having to stop 
     124            and fix each one and then execute the process again. 
     125             
     126            If 'repair', then each issue will be resolved by changing 
     127            the database to match the model. 
    114128        """ 
    115129        storemap = {} 
     
    128142        storemap.sort() 
    129143        for order, store, classes in storemap: 
    130             store.map(classes
     144            store.map(classes, conflict_mode
    131145     
    132146    def shutdown(self): 
     
    174188        raise KeyError("No registered class found for '%s'." % classname) 
    175189     
    176     def storage(self, cls): 
     190    def storage(self, cls, automap=True): 
    177191        """Return the StorageManager which handles Units of the given class. 
    178192         
    179         The results of this call will be cached for performance. Also, this 
    180         will call store.map(cls) on first retrieval. 
     193        The results of this call will be cached for performance. 
     194         
     195        If automap is True (the default), this method will call 
     196        store.map(cls) if the class is not already cached. 
    181197        """ 
    182198        store = self._registered_classes.get(cls) 
     
    186202        store = self._find_storage(cls) 
    187203        self._registered_classes[cls] = store 
    188         store.map([cls]) 
     204        if automap: 
     205            store.map([cls]) 
    189206        return store 
    190207     
     
    213230    def create_storage(self, cls): 
    214231        """Create storage space for cls.""" 
    215         # Skip the map() call inside storage(). 
    216         store = self._registered_classes.get(cls) 
    217         if not store: 
    218             store = self._find_storage(cls) 
    219             self._registered_classes[cls] = store 
     232        store = self.storage(cls, automap=False) 
    220233        store.create_storage(cls) 
    221234     
    222235    def has_storage(self, cls): 
    223236        """If storage space for cls exists, return True (False otherwise).""" 
    224         import geniusql 
    225         try: 
    226             store = self.storage(cls) 
    227         except geniusql.errors.MappingError: 
    228             return False 
    229          
     237        store = self.storage(cls, automap=False) 
    230238        return store.has_storage(cls) 
    231239     
  • trunk/errors.py

    r419 r420  
    2020    """Warning about functionality which is not supported by all SM's.""" 
    2121    pass 
     22 
     23class MappingError(DejavuError): 
     24    """Exception raised when a Unit class cannot be mapped to storage. 
     25     
     26    This exception should be raised when a consumer attempts to build 
     27    a map between a Unit class and existing internal storage structures. 
     28    Other exceptions may be raised when trying to find such a map after 
     29    it has already (supposedly) been created. That is, the questions 
     30    "do we have a map?" and "can we create a map?" are distinct. 
     31    The latter should raise this exception whenever possible. 
     32    The behavior of the former is not specified. 
     33    """ 
     34    pass 
  • trunk/schemas.py

    r419 r420  
    140140        easily if you run assert_storage before the upgrade methods. 
    141141        """ 
    142          
    143         classes = self.arena._registered_classes.keys() 
    144         if DeployedVersion in classes: 
    145             classes.remove(DeployedVersion) 
    146          
    147         for cls in classes: 
    148             if not self.arena.has_storage(cls): 
    149                 self.arena.create_storage(cls) 
     142        self.arena.map_all(conflict_mode='repair') 
    150143     
    151144    def assert_version(self): 
  • trunk/storage/__init__.py

    r419 r420  
    11"""Storage Managers for Dejavu.""" 
    22 
    3 import re 
    43import datetime 
    5 import thread 
    64try: 
    75    import cPickle as pickle 
    86except ImportError: 
    97    import pickle 
    10  
    11 from dejavu import logic, recur 
     8import re 
     9import thread 
     10import warnings 
     11 
     12from dejavu import errors, logic, recur 
    1213 
    1314 
     
    6061    #                               Schemas                               # 
    6162     
    62     def map(self, classes): 
    63         """Map classes to internal storage structures.""" 
    64         pass 
     63    def map(self, classes, conflict_mode='error'): 
     64        """Map classes to internal storage. 
     65         
     66        conflict_mode: This argument determines what happens when there are 
     67        discrepancies between the Dejavu model and the actual storage. 
     68             
     69            If 'error' (the default), MappingError is raised for the 
     70            first issue and the mapping process is aborted. 
     71             
     72            If 'warn', then a warning is raised (instead of an error) 
     73            for each issue, and the mapping process is not aborted. 
     74            This allows you to see all errors at once, without having 
     75            to stop and fix each one and then execute the process again. 
     76             
     77            If 'repair', then each issue will be resolved by changing 
     78            storage to match the model. 
     79        """ 
     80        for cls in classes: 
     81            if not self.has_storage(cls): 
     82                if conflict_mode == 'repair': 
     83                    self.create_storage(cls) 
     84                else: 
     85                    msg = "%s: no storage found." % cls.__name__ 
     86                    if conflict_mode == 'warn': 
     87                        warnings.warn(msg) 
     88                    else: 
     89                        raise errors.MappingError(msg) 
    6590     
    6691    def create_database(self): 
     
    160185    #                               Schemas                               # 
    161186     
    162     def map(self, classes, warn=False): 
    163         """Map classes to internal storage structures. 
    164          
    165         If 'warn' is True, then any mapping errors are replaced by warnings. 
    166         This allows you to see all errors at once, without having to stop 
    167         and fix each one and then re-execute the process. 
     187    def map(self, classes, conflict_mode='error'): 
     188        """Map classes to internal storage. 
     189         
     190        conflict_mode: This argument determines what happens when there are 
     191        discrepancies between the Dejavu model and the actual database. 
     192             
     193            If 'error' (the default), MappingError is raised for the 
     194            first issue and the sync process is aborted. 
     195             
     196            If 'warn', then a warning is raised (instead of an error) 
     197            for each issue, and the sync process is not aborted. This 
     198            allows you to see all errors at once, without having to stop 
     199            and fix each one and then execute the process again. 
     200             
     201            If 'repair', then each issue will be resolved by changing 
     202            the database to match the model. 
    168203        """ 
    169         self.nextstore.map(classes, warn
     204        self.nextstore.map(classes, conflict_mode
    170205     
    171206    def create_database(self): 
     
    487522managers = { 
    488523    "cache": CachingProxy, 
     524    "caching": CachingProxy, 
    489525    "burned": BurnedProxy, 
    490526    "proxy": ProxyStorage, 
  • trunk/storage/db.py

    r419 r420  
    3737import dejavu 
    3838from dejavu import logic, storage, logflags, xray 
    39 from dejavu.errors import StorageWarning 
     39from dejavu.errors import StorageWarning, MappingError 
    4040 
    4141 
     
    6868        self.db = self.databaseclass(**allOptions) 
    6969        self.schema = self.db.schema(name) 
     70         
     71        if 'Prefix' in allOptions: 
     72            self.schema.prefix = allOptions['Prefix'] 
    7073         
    7174        def logger(msg): 
     
    418421    auto_discover = True 
    419422     
    420     def map(self, classes, warn=False): 
    421         """Map classes to Table objects (if not found)
     423    def map(self, classes, conflict_mode='error'): 
     424        """Map classes to internal storage
    422425         
    423426        If self.auto_discover is True (the default), then Table/Column/Index 
     
    430433        and changes to the database are not expected outside the model. 
    431434         
    432         If 'warn' is True, then any mapping errors are replaced by warnings. 
    433         This allows you to see all errors at once, without having to stop 
    434         and fix each one and then re-execute the process. 
     435        conflict_mode: This argument determines what happens when there are 
     436        discrepancies between the Dejavu model and the actual database. 
     437             
     438            If 'error' (the default), MappingError is raised for the 
     439            first issue and the sync process is aborted. 
     440             
     441            If 'warn', then a warning is raised (instead of an error) 
     442            for each issue, and the sync process is not aborted. This 
     443            allows you to see all errors at once, without having to stop 
     444            and fix each one and then execute the process again. 
     445             
     446            If 'repair', then each issue will be resolved by changing 
     447            the database to match the model. 
    435448        """ 
    436449        if self.auto_discover: 
    437             self.sync(classes, warn
     450            self.sync(classes, conflict_mode
    438451        else: 
    439452            for cls in classes: 
    440                 clsname = cls.__name__ 
    441                 if clsname in self.schema: 
     453                if self.has_storage(cls): 
    442454                    # If our consumer-side key is already present, skip this cls. 
    443455                    # This allows arena.storage() to auto-sync class by class 
     
    448460                 
    449461                # Use the superclass call to avoid DROP/CREATE TABLE 
    450                 dict.__setitem__(self.schema, clsname, t) 
    451      
    452     def sync(self, classes, warn=False): 
     462                dict.__setitem__(self.schema, cls.__name__, t) 
     463     
     464    def sync(self, classes, conflict_mode='error'): 
    453465        """Map classes to existing Table objects (found via discovery). 
    454466         
    455         If a matching Table/Column/Index cannot be found, KeyError is raised. 
    456          
    457         If 'warn' is True, then a warning is raised instead of an error. 
    458         This allows you to see all errors at once, without having to stop 
    459         and fix each one and then re-execute the process. 
     467        conflict_mode: This argument determines what happens when there are 
     468        discrepancies between the Dejavu model and the actual database. 
     469             
     470            If 'error' (the default), MappingError is raised for the 
     471            first issue and the sync process is aborted. 
     472             
     473            If 'warn', then a warning is raised (instead of an error) 
     474            for each issue, and the sync process is not aborted. This 
     475            allows you to see all errors at once, without having to stop 
     476            and fix each one and then execute the process again. 
     477             
     478            If 'repair', then each issue will be resolved by changing 
     479            the database to match the model. 
    460480        """ 
     481         
     482        def notify(msg): 
     483            if conflict_mode == 'warn': 
     484                warnings.warn(msg) 
     485            else: 
     486                raise MappingError(msg) 
     487         
    461488        for cls in classes: 
    462489            clsname = cls.__name__ 
     
    471498            tablename = self.schema.table_name(clsname) 
    472499            try: 
    473                 # Do we already have a map
     500                # Do we already have a map using the DB name
    474501                table = self.schema[tablename] 
     502                self.schema.alias(table.name, clsname) 
    475503            except KeyError: 
    476504                # Can we create a map? Discover the DB table and try again. 
    477505                try: 
    478506                    table = self.schema.discover(tablename) 
     507                    self.schema.alias(table.name, clsname) 
    479508                except geniusql.errors.MappingError: 
    480509                    msg = "%s: no such table %r." % (clsname, tablename) 
    481                     if warn: 
    482                         warnings.warn(msg) 
     510                    if conflict_mode == 'repair': 
     511                        self.create_storage(cls) 
     512                        table = self.schema[clsname] 
     513                    else: 
     514                        notify(msg) 
    483515                        continue 
    484                     else: 
    485                         raise geniusql.errors.MappingError(msg) 
    486              
    487             self.schema.alias(table.name, clsname) 
    488516             
    489517            # Match Column objects with class properties. 
     
    491519            indices = cls.indices() 
    492520            related = self.db.typeadapter.related 
    493             for ckey in cls.properties: 
    494                 colname = self.schema._column_name(table.name, ckey) 
     521            for pkey in cls.properties: 
     522                colname = self.schema._column_name(table.name, pkey) 
    495523                try: 
    496524                    col = dbcols[colname] 
     525                    table.alias(colname, pkey) 
    497526                except KeyError, x: 
    498                     msg = "%s: no matching column found for %r." % (clsname, ckey) 
    499                     if warn: 
    500                         warnings.warn(msg) 
     527                    msg = "%s: no column found for %r." % (clsname, pkey) 
     528                    if conflict_mode == 'repair': 
     529                        self.add_property(cls, pkey) 
     530                        if pkey in cls.indices() and pkey not in table.indices: 
     531                            self.add_index(cls, pkey) 
     532                        col = table[pkey] 
     533                    else: 
     534                        notify(msg) 
    501535                        continue 
     536                 
     537                # Check that the column.key matches our identifiers list; 
     538                # this is crucial for the proper operation of OLTP methods 
     539                # in geniusql.Table, which uses column.key to decide 
     540                # the unique identifiers for a given row of data. 
     541                if pkey in cls.identifiers and not col.key: 
     542                    msg = ("%s: %r is an identifier, but the " 
     543                           "column is not marked as a primary key." 
     544                           % (clsname, pkey)) 
     545                    if conflict_mode == 'repair': 
     546                        col.key = True 
     547                        table.add_primary() 
    502548                    else: 
    503                         raise geniusql.errors.MappingError(msg) 
     549                        notify(msg) 
     550                        continue 
     551                elif col.key and not pkey in cls.identifiers: 
     552                    msg = ("%s: %r is not an identifier, but the " 
     553                           "column is marked as a primary key." 
     554                           % (clsname, pkey)) 
     555                    if conflict_mode == 'repair': 
     556                        col.key = False 
     557                        table.drop_primary() 
     558                        # Just because the current pkey is not an identifier 
     559                        # doesn't mean we have *no* identifiers. 
     560                        if cls.identifiers: 
     561                            table.add_primary() 
     562                    else: 
     563                        notify(msg) 
     564                        continue 
    504565                 
    505566                # Set imperfect_type 
    506                 newtype = getattr(cls, ckey).type 
     567                newtype = getattr(cls, pkey).type 
    507568                if newtype != col.pytype: 
    508569                    col.imperfect_type = not related(newtype, col.pytype) 
    509570                    col.pytype = newtype 
    510571                 
    511                 table.alias(col.name, ckey) 
    512                  
    513572                # Try to find matching Index objects. Because index names are 
    514573                # so platform-specific, we match attributes rather than names. 
    515                 if ckey in indices: 
    516                     for ikey, idx in table.indices.iteritems(): 
     574                if pkey in indices: 
     575                    for ikey, idx in table.indices.items(): 
    517576                        if idx.colname == colname: 
    518                             a = self.schema.table_name("i" + clsname + ckey) 
     577                            a = self.schema.table_name("i" + clsname + pkey) 
    519578                            table.indices.alias(ikey, a) 
    520579                            break 
    521580                    else: 
    522                         msg = ("%s: no matching index found for %r." 
    523                                % (clsname, ckey)) 
    524                         if warn: 
    525                             warnings.warn(msg) 
     581                        msg = "%s: no index found for %r." % (clsname, pkey) 
     582                        if conflict_mode == 'repair': 
     583                            self.add_index(cls, pkey) 
    526584                        else: 
    527                             raise geniusql.errors.MappingError(msg) 
     585                            notify(msg) 
     586                            continue 
     587                else: 
     588                    if pkey in cls.identifiers and self.schema.db.pks_must_be_indexed: 
     589                        pass 
     590                    else: 
     591                        for ikey, idx in table.indices.items(): 
     592                            if idx.colname == colname: 
     593                                msg = ("%s: index found for non-indexed %r." 
     594                                       % (clsname, pkey)) 
     595                                if conflict_mode == 'repair': 
     596                                    self.drop_index(cls, ikey) 
     597                                else: 
     598                                    notify(msg) 
     599                                    continue 
    528600             
    529601            # Set Table.references