Changeset 517 for elixir

Show
Ignore:
Timestamp:
11/13/09 21:40:37 (3 years ago)
Author:
ged
Message:

merge all changes from trunk (up to r516)

Location:
elixir/branches/0.7.x
Files:
9 added
20 modified

Legend:

Unmodified
Added
Removed
  • elixir/branches/0.7.x/AUTHORS

    r409 r517  
    1616- Graham Higgins 
    1717- Jason R. Coombs 
     18- Johannes Janssen 
    1819- Neil Blakey-Milner 
    1920- Paul Johnston 
    2021- Remi Jolin 
    2122- Robin Munn 
     23- Stéphane Klein 
     24- Valentin Lab 
    2225- some anonymous contributions I couldn't trace to someone in particular 
  • elixir/branches/0.7.x/CHANGES

    r486 r517  
     10.7.1 - 2009-11-.. 
     2 
     3New features: 
     4- Entities can now be declared "abstract" so that they do not create a table, 
     5  etc... This allows, among others, an entity to inherit from multiple abstract 
     6  classes (patch from Stéphane Klein, closes #89). 
     7- Added a new collection which can resolve entities relative to the current 
     8  entity, for example "..other_module.Class" (based on patches from Johannes 
     9  Janssen, closes #93). 
     10- Added a new entity option "resolve_root", which allows one to specify the 
     11  root module where your entities are defined. The string will be prepended 
     12  to all "absolute" entity paths. It can also be used on a per-entity basis. 
     13  This feature is based on a patch from Johannes Janssen, see #93. 
     14 
     15Changes: 
     16- using_options_defaults and using_table_options statements can be used several 
     17  times within the same class (closes #70). 
     18 
     19Bug fixes: 
     20- Fixed custom base classes and versioned extension when used with zope 
     21  interfaces (closes #98, patch from Valentin Lab) 
     22- Fixed having relationships in custom base classes (based on patch 
     23  by Stéphane Klein) 
     24 
    1250.7.0 - 2009-10-01 
    226 
  • elixir/branches/0.7.x/elixir/__init__.py

    r492 r517  
    3636                              Synonym 
    3737from elixir.statements import Statement 
    38 from elixir.collection import EntityCollection 
     38from elixir.collection import EntityCollection, GlobalEntityCollection 
    3939 
    4040 
     
    6868 
    6969# default entity collection 
    70 entities = EntityCollection() 
     70entities = GlobalEntityCollection() 
    7171 
    7272 
  • elixir/branches/0.7.x/elixir/collection.py

    r439 r517  
    33''' 
    44import sys 
     5import re 
    56 
    67from elixir.py23compat import rsplit 
    78 
    8 # default entity collection 
    9 class EntityCollection(list): 
     9class BaseCollection(list): 
    1010    def __init__(self, entities=None): 
    11         # _entities is a dict of entities keyed on their name. 
    12         self._entities = {} 
    1311        list.__init__(self) 
    1412        if entities is not None: 
     
    1816        for e in entities: 
    1917            self.append(e) 
     18 
     19    def clear(self): 
     20        del self[:] 
     21 
     22    def resolve_absolute(self, key, full_path, entity=None, root=None): 
     23        if root is None: 
     24            root = entity._descriptor.resolve_root 
     25        if root: 
     26            full_path = '%s.%s' % (root, full_path) 
     27        module_path, classname = rsplit(full_path, '.', 1) 
     28        module = sys.modules[module_path] 
     29        res = getattr(module, classname, None) 
     30        if res is None: 
     31            if entity is not None: 
     32                raise Exception("Couldn't resolve target '%s' <%s> in '%s'!" 
     33                                % (key, full_path, entity.__name__)) 
     34            else: 
     35                raise Exception("Couldn't resolve target '%s' <%s>!" 
     36                                % (key, full_path)) 
     37        return res 
     38 
     39    def __getattr__(self, key): 
     40        return self.resolve(key) 
     41 
     42# default entity collection 
     43class GlobalEntityCollection(BaseCollection): 
     44    def __init__(self, entities=None): 
     45        # _entities is a dict of entities keyed on their name. 
     46        self._entities = {} 
     47        super(GlobalEntityCollection, self).__init__(entities) 
    2048 
    2149    def append(self, entity): 
     
    3361        "source" entity when resolving relationship targets. 
    3462        ''' 
    35         path = rsplit(key, '.', 1) 
    36         classname = path.pop() 
    37         if path: 
    38             # Do we have a fully qualified entity name? 
    39             module = sys.modules[path.pop()] 
    40             return getattr(module, classname, None) 
     63        # Do we have a fully qualified entity name? 
     64        if '.' in key: 
     65            return self.resolve_absolute(key, key, entity) 
    4166        else: 
    4267            # Otherwise we look in the entities of this collection 
     
    4469            if res is None: 
    4570                if entity: 
    46                     raise Exception("Couldn't resolve target '%s' in '%s'" \ 
     71                    raise Exception("Couldn't resolve target '%s' in '%s'" 
    4772                                    % (key, entity.__name__)) 
    4873                else: 
     
    5984    def clear(self): 
    6085        self._entities = {} 
    61         del self[:] 
     86        super(GlobalEntityCollection, self).clear() 
     87 
     88# backward compatible name 
     89EntityCollection = GlobalEntityCollection 
     90 
     91_leading_dots = re.compile('^([.]*).*$') 
     92 
     93class RelativeEntityCollection(BaseCollection): 
     94    # the entity=None does not make any sense with a relative entity collection 
     95    def resolve(self, key, entity): 
     96        ''' 
     97        Resolve a key to an Entity. The optional `entity` argument is the 
     98        "source" entity when resolving relationship targets. 
     99        ''' 
     100        full_path = key 
     101 
     102        if '.' not in key or key.startswith('.'): 
     103            # relative target 
     104 
     105            # any leading dot is stripped and with each dot removed, 
     106            # the entity_module is stripped of one more chunk (starting with 
     107            # the last one). 
     108            num_dots = _leading_dots.match(full_path).end(1) 
     109            full_path = full_path[num_dots:] 
     110            chunks = entity.__module__.split('.') 
     111            chunkstokeep = len(chunks) - num_dots 
     112            if chunkstokeep < 0: 
     113                raise Exception("Couldn't resolve relative target " 
     114                    "'%s' relative to '%s'" % (key, entity.__module__)) 
     115            entity_module = '.'.join(chunks[:chunkstokeep]) 
     116 
     117            if entity_module and entity_module is not '__main__': 
     118                full_path = '%s.%s' % (entity_module, full_path) 
     119 
     120            root = '' 
     121        else: 
     122            root = None 
     123        return self.resolve_absolute(key, full_path, entity, root=root) 
    62124 
    63125    def __getattr__(self, key): 
    64         return self.resolve(key) 
     126        raise NotImplementedError 
    65127 
    66  
  • elixir/branches/0.7.x/elixir/entity.py

    r480 r517  
    77 
    88import sys 
    9 import inspect 
    109import types 
    1110import warnings 
    1211 
    13 from copy import copy 
     12from copy import deepcopy 
    1413 
    1514import sqlalchemy 
     
    2221 
    2322import elixir 
    24 from elixir.statements import process_mutators 
     23from elixir.statements import process_mutators, MUTATORS 
    2524from elixir import options 
    2625from elixir.properties import Property 
     
    5554 
    5655    def __init__(self, entity): 
    57         entity.table = None 
    58         entity.mapper = None 
    59  
    6056        self.entity = entity 
    61         # entity.__module__ is not always reliable (eg in mod_python) 
    62         self.module = sys.modules.get(entity.__module__) 
    63  
    64         # used for multi-table inheritance 
    65         self.join_condition = None 
    66         self.has_pk = False 
    67         self._pk_col_done = False 
    68  
    69         self.builders = [] 
    70  
    7157        self.parent = None 
    72         #XXX: use entity.__subclasses__ ? 
    73         self.children = [] 
    74  
     58 
     59        bases = [] 
    7560        for base in entity.__bases__: 
    7661            if isinstance(base, EntityMeta): 
    77                 if is_entity(base): 
     62                if is_entity(base) and not is_abstract_entity(base): 
    7863                    if self.parent: 
    7964                        raise Exception( 
     
    8368                    else: 
    8469                        self.parent = base 
    85                         self.base = base._descriptor.base 
     70                        bases.extend(base._descriptor.bases) 
    8671                        self.parent._descriptor.children.append(entity) 
    8772                else: 
    88                     self.base = base 
     73                    bases.append(base) 
     74        self.bases = bases 
     75        if not is_entity(entity) or is_abstract_entity(entity): 
     76            return 
     77 
     78        # entity.__module__ is not always reliable (eg in mod_python) 
     79        self.module = sys.modules.get(entity.__module__) 
     80 
     81        self.builders = [] 
     82 
     83        #XXX: use entity.__subclasses__ ? 
     84        self.children = [] 
     85 
     86        # used for multi-table inheritance 
     87        self.join_condition = None 
     88        self.has_pk = False 
     89        self._pk_col_done = False 
    8990 
    9091        # columns and constraints waiting for a table to exist 
     
    103104        self.table_args = [] 
    104105 
    105         # base class options_defaults 
    106         base_defaults = getattr(self.base, 'options_defaults', {}) 
     106        # base class(es) options_defaults 
     107        options_defaults = self.options_defaults() 
     108 
    107109        complete_defaults = options.options_defaults.copy() 
    108110        complete_defaults.update({ 
     
    114116        # set default value for other options 
    115117        for key in options.valid_options: 
    116             value = base_defaults.get(key, complete_defaults[key]) 
     118            value = options_defaults.get(key, complete_defaults[key]) 
    117119            if isinstance(value, dict): 
    118120                value = value.copy() 
     
    124126            if hasattr(self.module, attr): 
    125127                setattr(self, key, getattr(self.module, attr)) 
     128 
     129    def options_defaults(self): 
     130        base_defaults = {} 
     131        for base in self.bases: 
     132            base_defaults.update(base._descriptor.options_defaults()) 
     133        base_defaults.update(getattr(self.entity, 'options_defaults', {})) 
     134        return base_defaults 
    126135 
    127136    def setup_options(self): 
     
    153162                self.identity = self.mapper_options['polymorphic_identity'] 
    154163            else: 
    155                 #TODO: include module name 
     164                #TODO: include module name (We could have b.Account inherit 
     165                # from a.Account) 
    156166                self.identity = entity.__name__.lower() 
    157167        elif 'polymorphic_identity' in self.mapper_options: 
     
    273283                            self.add_column(col.copy()) 
    274284 
    275                     #FIXME: use the public equivalent of _get_colspec when 
    276                     # available (e.target_fullname) 
    277285                    for con in self.parent._descriptor.constraints: 
    278286                        self.add_constraint( 
     
    326334        # create a list of callbacks for each event 
    327335        methods = {} 
    328         entity = self.entity 
    329  
    330         # Note that we don't use inspect.getmembers because of 
    331         # http://bugs.python.org/issue1785 
    332         # See also http://elixir.ematia.de/trac/changeset/262 
    333  
    334         # dir returns the attributes of the class and *all its parents* listed 
    335         # alphabetically. 
    336         for key in dir(entity): 
    337             try: 
    338                 value = getattr(entity, key) 
    339                 if isinstance(value, types.MethodType): 
    340                     for event in getattr(value, '_elixir_events', []): 
    341                         event_methods = methods.setdefault(event, []) 
    342                         event_methods.append(value) 
    343             except AttributeError: 
    344                 pass 
     336 
     337        all_methods = getmembers(self.entity, 
     338                                 lambda a: isinstance(a, types.MethodType)) 
     339 
     340        for name, method in all_methods: 
     341            for event in getattr(method, '_elixir_events', []): 
     342                event_methods = methods.setdefault(event, []) 
     343                event_methods.append(method) 
     344 
    345345        if not methods: 
    346346            return 
     
    740740 
    741741 
     742# Note that we don't use inspect.getmembers because of 
     743# http://bugs.python.org/issue1785 
     744# See also http://elixir.ematia.de/trac/changeset/262 
     745def getmembers(object, predicate=None): 
     746    base_props = [] 
     747    for key in dir(object): 
     748        try: 
     749            value = getattr(object, key) 
     750        except AttributeError: 
     751            continue 
     752        if not predicate or predicate(value): 
     753            base_props.append((key, value)) 
     754    return base_props 
     755 
     756def is_abstract_entity(dict_or_cls): 
     757    if not isinstance(dict_or_cls, dict): 
     758        dict_or_cls = dict_or_cls.__dict__ 
     759    for mutator, args, kwargs in dict_or_cls.get(MUTATORS, []): 
     760        if 'abstract' in kwargs: 
     761            return kwargs['abstract'] 
     762 
     763    return False 
     764 
    742765def instrument_class(cls): 
    743766    """ 
     
    745768    the EntityMeta metaclass. 
    746769    """ 
    747     # create the entity descriptor 
     770    # Create the entity descriptor 
    748771    desc = cls._descriptor = EntityDescriptor(cls) 
    749772 
    750     # Determine whether this entity is a *direct* subclass of its base entity 
    751     entity_base = None 
     773    # Process mutators 
     774    # We *do* want mutators to be processed for base/abstract classes 
     775    # (so that statements like using_options_defaults work). 
     776    process_mutators(cls) 
     777 
     778    # We do not want to do any more processing for base/abstract classes 
     779    # (Entity et al.). 
     780    if not is_entity(cls) or is_abstract_entity(cls): 
     781        return 
     782 
     783    cls.table = None 
     784    cls.mapper = None 
     785 
     786    # Copy the properties ('Property' instances) of the entity base class(es). 
     787    # We use getmembers (instead of __dict__) so that we also get the 
     788    # properties from the parents of the base class if any. 
     789    base_props = [] 
    752790    for base in cls.__bases__: 
    753         if isinstance(base, EntityMeta): 
    754             if not is_entity(base): 
    755                 entity_base = base 
    756  
    757     if entity_base: 
    758         # If so, copy the base entity properties ('Property' instances). 
    759         # We use inspect.getmembers (instead of __dict__) so that we also 
    760         # get the properties from the parents of the base_class if any. 
    761         base_props = inspect.getmembers(entity_base, 
    762                                         lambda a: isinstance(a, Property)) 
    763         base_props = [(name, copy(attr)) for name, attr in base_props] 
    764     else: 
    765         base_props = [] 
     791        if isinstance(base, EntityMeta) and \ 
     792           (not is_entity(base) or is_abstract_entity(base)): 
     793            base_props += [(name, deepcopy(attr)) for name, attr in 
     794                           getmembers(base, lambda a: isinstance(a, Property))] 
    766795 
    767796    # Process attributes (using the assignment syntax), looking for 
     
    774803        prop.attach(cls, name) 
    775804 
    776     # Process mutators. Needed before _install_autosetup_triggers so that 
    777     # we know of the metadata (and whether the entity is autosetuped or not). 
    778     process_mutators(cls) 
    779  
    780805    # setup misc options here (like tablename etc.) 
    781806    desc.setup_options() 
     
    797822 
    798823    def __init__(cls, name, bases, dict_): 
    799         # Only process further subclasses of the base classes (Entity et al.), 
    800         # not the base classes themselves. We don't want the base entities to 
    801         # be registered in an entity collection, nor to have a table name and 
    802         # so on. 
    803         if not is_entity(cls): 
    804             if isinstance(cls, EntityMeta): 
    805                 process_mutators(cls) 
    806             return 
    807  
    808824        instrument_class(cls) 
    809825 
  • elixir/branches/0.7.x/elixir/ext/perform_ddl.py

    r443 r517  
    88The 'when' argument can be either 'before-create' or 'after-create'. 
    99The 'statement' argument can be one of: 
     10 
    1011- a single string statement 
    1112- a list of string statements, in which case, each of them will be executed 
  • elixir/branches/0.7.x/elixir/ext/versioned.py

    r409 r517  
    5656from elixir.statements     import Statement 
    5757from elixir.properties     import EntityBuilder 
     58from elixir.entity         import getmembers 
    5859 
    5960__all__ = ['acts_as_versioned', 'after_revert'] 
     
    177178        # look for events 
    178179        after_revert_events = [] 
    179         for name, func in inspect.getmembers(entity, inspect.ismethod): 
     180        for name, func in getmembers(entity, inspect.ismethod): 
    180181            if getattr(func, '_elixir_after_revert', False): 
    181182                after_revert_events.append(func) 
  • elixir/branches/0.7.x/elixir/options.py

    r484 r517  
    2121 
    2222        using_options(shortnames=True, order_by='name') 
     23 
    2324The list of supported arguments are as follows: 
    2425 
     
    3435|                     | #mapping-class-inheritance-hierarchies for an         | 
    3536|                     | explanation of the different kinds of inheritances.   | 
     37+---------------------+-------------------------------------------------------+ 
     38| ``abstract``        | Set 'abstract'=True to declare abstract entity.       | 
     39|                     | Abstract base classes are useful when you want to put | 
     40|                     | some common information into a number of other        | 
     41|                     | entities. Abstract entity will not be used to create  | 
     42|                     | any database table. Instead, when it is used as a base| 
     43|                     | class for other entity, its fields will be added to   | 
     44|                     | those of the child class.                             | 
    3645+---------------------+-------------------------------------------------------+ 
    3746| ``polymorphic``     | Whether the inheritance should be polymorphic or not. | 
     
    174183using_options_defaults (nor specifically on a particular Entity) will use the 
    175184global defaults, so you don't have to provide a default value for all options, 
    176 but only those you want to change. 
    177  
     185but only those you want to change. Please also note that this statement does 
     186not work on normal entities, and the normal using_options statement does not 
     187work on base classes (because normal options do not and should not propagate to 
     188the children classes). 
    178189''' 
    179190 
     
    214225# 
    215226options_defaults = dict( 
     227    abstract=False, 
    216228    autosetup=False, 
    217229    inheritance='single', 
     
    225237    allowcoloverride=False, 
    226238    order_by=None, 
     239    resolve_root=None, 
    227240    mapper_options={}, 
    228241    table_options={} 
     
    242255                            % kwarg) 
    243256 
    244     entity.options_defaults = kwargs 
     257    # We use __dict__ instead of hasattr to not check its presence within the 
     258    # parent, and thus update the parent dict instead of creating a local dict. 
     259    if not entity.__dict__.get('options_defaults'): 
     260        entity.options_defaults = {} 
     261    entity.options_defaults.update(kwargs) 
    245262 
    246263 
     
    255272 
    256273def using_table_options_handler(entity, *args, **kwargs): 
    257     entity._descriptor.table_args = list(args) 
     274    entity._descriptor.table_args.extend(list(args)) 
    258275    entity._descriptor.table_options.update(kwargs) 
    259276 
  • elixir/branches/0.7.x/elixir/relationships.py

    r488 r517  
    185185+--------------------+--------------------------------------------------------+ 
    186186| ``filter``         | Specify a filter criterion (as a clause element) for   | 
    187 |                    | this relationship. This criterion will be and_'ed with | 
    188 |                    | the normal join criterion (primaryjoin) generated by  | 
    189 |                    | Elixir for the relationship. For example:              | 
    190 |                    | boston_addresses = \                                   | 
    191 |                    |  OneToMany('Address', filter=Address.city == 'Boston') | 
     187|                    | this relationship. This criterion will be ``and_`` ed | 
     188|                    | with the normal join criterion (primaryjoin) generated | 
     189|                    | by Elixir for the relationship. For example:           | 
     190|                    | boston_addresses =                                     | 
     191|                    | OneToMany('Address', filter=Address.city == 'Boston') | 
    192192+--------------------+--------------------------------------------------------+ 
    193193 
     
    303303| ``table_kwargs``   | A dictionary holding any other keyword argument you    | 
    304304|                    | might want to pass to the underlying Table object.     | 
    305 +--------------------+--------------------------------------------------------+| ``column_format``  | DEPRECATED. Specify an alternate format string for     | 
     305+--------------------+--------------------------------------------------------+ 
     306| ``column_format``  | DEPRECATED. Specify an alternate format string for     | 
    306307|                    | naming the                                             | 
    307308|                    | columns in the mapping table.  The default value is    | 
  • elixir/branches/0.7.x/examples/videostore/start-videostore.py

    r11 r517  
    1414# probably installed 
    1515if len(sys.argv) > 1: 
    16     update_config(configfile=sys.argv[1],  
     16    update_config(configfile=sys.argv[1], 
    1717        modulename="videostore.config") 
    1818elif exists(join(dirname(__file__), "setup.py")): 
  • elixir/branches/0.7.x/examples/videostore/videostore/controllers/root.py

    r298 r517  
    77 
    88class Root(RootController): 
    9      
     9 
    1010    @expose(template='videostore.templates.index') 
    1111    @identity.require(identity.not_anonymous()) 
    1212    def index(self): 
    1313        return dict(movies=Movie.query.all()) 
    14      
    15      
     14 
     15 
    1616    @expose(template='videostore.templates.movie') 
    1717    @identity.require(identity.not_anonymous()) 
     
    1919    def movie(self, movieID): 
    2020        return dict(movie=Movie.get(movieID)) 
    21      
    22      
     21 
     22 
    2323    @expose(template='videostore.templates.actor') 
    2424    @identity.require(identity.not_anonymous()) 
     
    2626    def actor(self, actorID): 
    2727        return dict(actor=Actor.get(actorID)) 
    28      
    29      
     28 
     29 
    3030    @expose(template='videostore.templates.director') 
    3131    @identity.require(identity.not_anonymous()) 
     
    3333    def director(self, directorID): 
    3434        return dict(director=Director.get(directorID)) 
    35      
    36      
     35 
     36 
    3737    @expose(template='videostore.templates.login') 
    3838    def login(self, forward_url=None, previous_url=None, *args, **kw): 
     
    4141               identity.get_identity_errors(): 
    4242            raise redirect(forward_url) 
    43          
     43 
    4444        forward_url = None 
    4545        previous_url = request.path 
    46          
     46 
    4747        if identity.was_login_attempted(): 
    4848            msg = 'The credentials you supplied were not correct.' 
     
    5252            msg = 'Please log in.' 
    5353            forward_url = request.headers.get('Referer', '/') 
    54          
     54 
    5555        response.status = 403 
    56         return dict(message=msg,  
    57                     previous_url=previous_url,  
     56        return dict(message=msg, 
     57                    previous_url=previous_url, 
    5858                    logging_in=True, 
    5959                    original_parameters=request.params, 
    6060                    forward_url=forward_url) 
    61      
    62      
     61 
     62 
    6363    @expose() 
    6464    def logout(self): 
  • elixir/branches/0.7.x/examples/videostore/videostore/model.py

    r316 r517  
    2929    movies = ManyToMany('Movie', inverse='actors', tablename='movie_casting') 
    3030    using_options(tablename='actors') 
    31     
     31 
    3232 
    3333# 
    3434# identity model 
    35 #  
     35# 
    3636 
    3737class Visit(Entity): 
     
    4040    expiry = Field(DateTime) 
    4141    using_options(tablename='visit') 
    42      
     42 
    4343    @classmethod 
    4444    def lookup_visit(cls, visit_key): 
     
    6868    groups = ManyToMany('Group', inverse='users') 
    6969    using_options(tablename='tg_user') 
    70      
     70 
    7171    @property 
    7272    def permissions(self): 
  • elixir/branches/0.7.x/examples/videostore/videostore/tests/test_controllers.py

    r11 r517  
    77def teardown_func(): 
    88    """Tests for apps using identity need to stop CP/TG after each test to 
    9     stop the VisitManager thread. See http://trac.turbogears.org/turbogears/ticket/1217 
    10     for details. 
     9    stop the VisitManager thread. 
     10    See http://trac.turbogears.org/turbogears/ticket/1217 for details. 
    1111    """ 
    1212    turbogears.startup.stopTurboGears() 
  • elixir/branches/0.7.x/release.howto

    r390 r517  
    66- change version in setup.py 
    77- change version in setup.cfg (trac link) 
     8- make sure API doc generates properly: apydia -v -c setup.cfg 
    89- commit 
    9  
    10 - add version to the trac, mark milestone as completed 
    1110 
    1211- make tag in subversion 
     
    2423  On older systems, you might need to upload the files manually to Pypi. 
    2524 
     25- add version to the trac, mark milestone as completed 
     26 
    2627- announce release on our news page (link to CHANGES) 
    2728 
  • elixir/branches/0.7.x/setup.cfg

    r444 r517  
    1010          elixir.ext.list, elixir.ext.perform_ddl, elixir.ext.versioned, 
    1111trac_browser_url = http://elixir.ematia.de/trac/browser/elixir/tags/0.7.0 
     12trac_link_format = %s%s#L%s%s 
    1213 
  • elixir/branches/0.7.x/tests/test_custombase.py

    r467 r517  
    44 
    55from elixir import * 
     6import elixir 
    67 
    78def setup(): 
     
    3536 
    3637        assert a.name == 'a1' 
     38 
     39    def test_bad_property(self): 
     40        # create a meta entity which mimick cases where dir() method 
     41        # will report attribute that can't be directly accessed. 
     42        # Note: this happens with zope.interface. See ticket #98. 
     43        class BrokenDescriptor(object): 
     44            def __get__(*args): 
     45                raise AttributeError 
     46 
     47        class MyEntity(EntityBase): 
     48            __metaclass__ = EntityMeta 
     49 
     50            d = BrokenDescriptor() 
     51 
     52        class A(MyEntity): 
     53            value = Field(Unicode) 
     54 
     55        # we just check that the instrument_class phases doesn't trigger an 
     56        # exception 
    3757 
    3858    def test_inherit(self): 
     
    96116        assert 'common' in A.table.columns 
    97117        assert 'common' in B.table.columns 
     118 
     119    def test_base_with_relation(self): 
     120        class FieldBase(object): 
     121            __metaclass__ = EntityMeta 
     122 
     123            common = ManyToOne('A') 
     124 
     125        class A(FieldBase): 
     126            name = Field(String(32)) 
     127 
     128        class B(FieldBase): 
     129            pass 
     130 
     131        setup_all(True) 
     132 
     133        assert 'name' in A.table.columns 
     134        assert 'common_id' in A.table.columns 
     135        assert 'common_id' in B.table.columns 
    98136 
    99137    def test_base_with_fields_in_parent(self): 
     
    129167            __metaclass__ = EntityMeta 
    130168 
    131             using_options_defaults(tablename=camel_to_underscore) 
     169            options_defaults = dict(tablename=camel_to_underscore) 
     170            using_options_defaults(identity=camel_to_underscore) 
     171            using_options_defaults(inheritance='multi') 
    132172 
    133173        class TestA(OptionBase): 
    134174            name = Field(String(32)) 
    135175 
    136         class SuperTestB(OptionBase): 
     176        class SuperTestB(TestA): 
    137177            pass 
    138178 
     
    141181        assert TestA.table.name == 'test_a' 
    142182        assert SuperTestB.table.name == 'super_test_b' 
    143  
     183        assert TestA._descriptor.identity == 'test_a' 
     184 
     185    def test_base_custom_collection(self): 
     186        global A, B 
     187 
     188        collection = elixir.collection.RelativeEntityCollection() 
     189 
     190        class Base(object): 
     191            __metaclass__ = EntityMeta 
     192            using_options_defaults(collection=collection) 
     193 
     194        class A(Base): 
     195            b = ManyToOne('B') 
     196 
     197        class B(Base): 
     198            a = OneToOne('A') 
     199 
     200        assert not elixir.entities 
     201        assert A.table is None 
     202        assert B.table is None 
     203 
     204        setup_entities(collection) 
     205 
     206        assert A.table is not None 
     207        assert B.table is not None 
     208 
     209        del A 
     210        del B 
     211 
     212    def test_base_custom_session(self): 
     213        from sqlalchemy.orm import sessionmaker 
     214 
     215        class Base(object): 
     216            __metaclass__ = EntityMeta 
     217            using_options_defaults(session=None) 
     218 
     219        class A(Base): 
     220            b = ManyToOne('B') 
     221 
     222        class B(Base): 
     223            a = OneToOne('A') 
     224         
     225        setup_all(True) 
     226 
     227        a = A() 
     228 
     229        assert a not in elixir.session 
     230 
     231        session = sessionmaker()() 
     232        session.add(a) 
     233        assert a in session 
     234 
  • elixir/branches/0.7.x/tests/test_events.py

    r467 r517  
    44from sqlalchemy import Table, Column 
    55 
    6 stateDict = {} 
    7  
    8 def teardown(): 
    9     cleanup_all() 
    106 
    117class TestEvents(object): 
    12     def setup(self): 
    13         # reset counters 
    14         stateDict.update({ 
     8    def teardown(self): 
     9        cleanup_all(True) 
     10 
     11    def test_events(self): 
     12        # initialize counters 
     13        stateDict = { 
    1514            'reconstructor_called': 0, 
    1615            'before_insert_called': 0, 
     
    2120            'after_delete_called': 0, 
    2221            'before_any_called': 0 
    23         }) 
     22        } 
    2423 
    25     def teardown(self): 
    26         cleanup_all() 
    27  
    28     def test_events(self): 
    2924        events = Table('events', metadata, 
    3025            Column('id', Integer, primary_key=True), 
  • elixir/branches/0.7.x/tests/test_options.py

    r480 r517  
    6060        setup_all(True) 
    6161 
    62         raised = False 
    6362        try: 
    6463            MyEntity._descriptor.add_column(Column('name', String(30))) 
     64            assert False 
    6565        except Exception: 
    66             raised = True 
    67  
    68         assert raised 
     66            pass 
    6967 
    7068    def test_allowcoloverride_true(self): 
     
    204202 
    205203    def teardown(self): 
    206         cleanup_all() 
     204        cleanup_all(True) 
    207205 
    208206    def test_unique_constraint(self): 
    209  
    210207        class Person(Entity): 
    211208            firstname = Field(String(30)) 
     
    223220        homer2 = Person(firstname="Homer", surname='Simpson') 
    224221 
    225         raised = False 
    226         try: 
    227             session.commit() 
    228         except SQLError: 
    229             raised = True 
    230  
    231         assert raised 
     222        try: 
     223            session.commit() 
     224            assert False 
     225        except SQLError: 
     226            pass 
     227 
     228    def test_several_statements(self): 
     229        class A(Entity): 
     230            name1 = Field(String(30)) 
     231            name2 = Field(String(30)) 
     232            name3 = Field(String(30)) 
     233            using_table_options(UniqueConstraint('name1', 'name2')) 
     234            using_table_options(UniqueConstraint('name2', 'name3')) 
     235 
     236        setup_all(True) 
     237 
     238        a000 = A(name1='0', name2='0', name3='0') 
     239        a010 = A(name1='0', name2='1', name3='0') 
     240        session.commit() 
     241 
     242        a001 = A(name1='0', name2='0', name3='1') 
     243        try: 
     244            session.commit() 
     245            assert False 
     246        except SQLError: 
     247            session.close() 
     248 
     249        a100 = A(name1='1', name2='0', name3='0') 
     250        try: 
     251            session.commit() 
     252            assert False 
     253        except SQLError: 
     254            session.close() 
    232255 
    233256    def test_unique_constraint_many_to_one(self): 
  • elixir/branches/0.7.x/tests/test_packages.py

    r467 r517  
    55import sys 
    66 
     7import elixir 
    78from elixir import * 
    89 
    910def setup(): 
    1011    metadata.bind = 'sqlite://' 
    11     sys.modules.pop('tests.a', None) 
    12     sys.modules.pop('tests.b', None) 
     12    for module in ('a', 'b', 'db1', 'db1.a', 'db1.b', 'db1.c', 'db2', 'db2.a'): 
     13        sys.modules.pop('tests.%s' % module, None) 
    1314 
    1415 
     
    4647    def test_ref_to_imported_entity_using_class(self): 
    4748        from tests.a import A 
    48         from tests.b import B 
     49        import tests.b 
    4950 
    5051        class C(Entity): 
     
    5758 
    5859    def test_ref_to_imported_entity_using_name(self): 
    59         from tests.a import A 
    60         from tests.b import B 
     60        import tests.a 
     61        import tests.b 
    6162 
    6263        class C(Entity): 
     
    6869        assert 'a_id' in C.table.columns 
    6970 
     71    def test_resolve_root(self): 
     72        import tests.a 
     73        import tests.b 
     74 
     75        class C(Entity): 
     76            using_options(resolve_root='tests') 
     77 
     78            name = Field(String(30)) 
     79            a = ManyToOne('a.A') 
     80 
     81        setup_all(True) 
     82 
     83        assert 'a_id' in C.table.columns 
     84 
     85    def test_relative_collection(self): 
     86        from elixir.collection import RelativeEntityCollection, \ 
     87                                      GlobalEntityCollection 
     88 
     89        elixir.entities = RelativeEntityCollection() 
     90 
     91        import db1 
     92        import db2 
     93 
     94        setup_all(True) 
     95 
     96        try: 
     97            assert len(elixir.entities) == 5 
     98        finally: 
     99            elixir.entities = GlobalEntityCollection() 
  • elixir/branches/0.7.x/tests/test_through.py

    r484 r517  
    3939 
    4040        user = User(name='log') 
    41         keywords = [Keyword('its_big'), Keyword('its_heavy'), Keyword('its_wood')] 
     41        keywords = [Keyword('its_big'), Keyword('its_heavy'), 
     42                    Keyword('its_wood')] 
    4243        for kw in keywords: 
    4344            user.keywords.append(kw)