Hi
The method resolve() of elixir.collection.EntityCollection is used by 
relations like ManyToOne(target, [...]). The relations use it to get a 
corresponding entity defined by the string target. Atm. you either have 
to use the full module path to address your target or if your are lucky 
you can address it just by the classname under certain conditions.
This behavior is okay for those who just want to define all their 
entities in one module. For clarity I instead prefer to split up my 
model into submodules. Therefore I've to use full module path which is 
very verbose and leads to problems, if you want to use your model module 
or package in a different application.

I propose a patch for resolve(), which makes to major changes:


User defined resolve function:

I think you should be able to pass a function through the options which 
replaces the default resolve function. This is similar to the tablename 
option, which also accepts a function. With this patch you are able to 
pass a function with the option "resolve_function".
The function will be passed same information as the original resolve 
method and is expected to return an Entity object. But an fallback to 
default resolve behavior is possible by returning None. If you want 
instead to have an exception raised, because you could not resolve the 
target with your own function, you've got to raise the exception in the 
user defined function.


Resolving the module path:

My second idea is to change the way module paths are resolved by 
default. Basically I would like to have tow ways to do that: relative 
and (pseudo) absolute paths. An relative path starts with a dot and is 
relative to the entity which is passed to resolve(key, entity) or better 
to entity.__module__. Therefore it is only possible to use relative 
paths when an entity is passed to resolve().
The absolute path instead must not start with a dot. They are by the 
default real absolute path (that means they are relative to __main__). 
Using "resolve_root" option you can change this. Think of chroot in 
POSIX. You can define your pseudo root module. So that all path a 
relative to that one.
(What I said about module paths (not) starting with a dot is not quite 
true. "..foo.bar" will still be an abs path and while "...foo.bar" is an 
rel path. Got it? Have a look at the example!)

Okay, allot information about how my patch works here. So what are they 
advantages?
- This way paths are unique (it is clear whether a rel or abs path is 
meant).
- Think of a db package where define resolve_root as __name__ through 
options_defaults: it doesn't matter anymore whether your db packages 
lives in "my_app.db" or in "meta_app.my_app.db". Atm. you would have to 
change all the btw. very verbose absolute paths if you are forced to use 
absolute paths.


Thanks for reading this far ;) What do you think about it? Please have a 
look at the patch, which is against svn trunk r455. Has this patch any 
chance to get into elixir? Any further suggestions concerning the 
implementations. For backwards compatibility you might give users the 
possibility to switch back manually to old behavior and give them a 
deprecation warning (for a few versions if there are to be more;)).

Also have a look at the example I provide. It shows how the new path 
resolving works and what advantages it might offer in a multi-file model 
definition.

Kind regards
Johannes


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"SQLElixir" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/sqlelixir?hl=en
-~----------~----~----~----~------~----~------~--~---

Index: elixir/collection.py
===================================================================
--- elixir/collection.py	(Revision 455)
+++ elixir/collection.py	(Arbeitskopie)
@@ -5,6 +5,15 @@
 
 from elixir.py23compat import rsplit
 
+from elixir.options import options_defaults
+
+
+def is_relative_path(path):
+    while path.startswith('..'):
+        path = path[2:]
+    return path.startswith('.')
+
+
 # default entity collection
 class EntityCollection(list):
     def __init__(self, entities=None):
@@ -32,30 +41,56 @@
         Resolve a key to an Entity. The optional `entity` argument is the
         "source" entity when resolving relationship targets.
         '''
-        path = rsplit(key, '.', 1)
-        classname = path.pop()
-        if path:
-            # Do we have a fully qualified entity name?
-            module = sys.modules[path.pop()]
-            return getattr(module, classname, None)
+        if entity is not None:
+            user_resolve = entity._descriptor.resolve_function
+            module_root = entity._descriptor.resolve_root
         else:
-            # Otherwise we look in the entities of this collection
-            res = self._entities.get(key, None)
-            if res is None:
-                if entity:
-                    raise Exception("Couldn't resolve target '%s' in '%s'" \
-                                    % (key, entity.__name__))
-                else:
-                    raise Exception("This collection does not contain any "
-                                    "entity corresponding to the key '%s'!"
-                                    % key)
-            elif len(res) > 1:
-                raise Exception("'%s' resolves to several entities, you should"
-                                " use the full path (including the full module"
-                                " name) to that entity." % key)
+            user_resolve = options_defaults.get('resolve_function')
+            module_root = options_defaults.get('resolve_root')
+        
+        print (user_resolve, module_root)
+
+        # try to use userdefined function first        
+        if hasattr(user_resolve, '__call__'):
+            res = user_resolve(key, entity)
+            if res is not None:
+                return res
+        
+        # default resolve methode (fallback for userdefined functions)
+        if is_relative_path(key):
+            if entity is None:
+                raise Exception("Couldn't resolve relative target '%s', "
+                                "because no entity was passed." % key)
             else:
-                return res[0]
+                full_path = '%s%s' % (entity.__module__, key) 
+        else:
+            if module_root is not None:
+                full_path = '%s.%s' % (module_root, key)
+            else:
+                full_path = key
+        
+        # deal with .. in full_path
+        while True:
+            r = full_path.find('..')
+            if r == -1:
+                break
+            l = full_path.rfind('.', 0, r)
+            full_path = ''.join((full_path[:l], full_path[r+2:]))
+          
+        module_path, classname = rsplit(full_path, '.', 1)
+        module = sys.modules[module_path]       
+        res = getattr(module, classname, None)
 
+        if res is None:
+            if entity is not None:
+                raise Exception("Couldn't resolve target '%s' <%s> in '%s'!"
+                            % (key, full_path, entity.__name__) )
+            else:
+                raise Exception("Couldn't resolve target '%s' <%s>!"
+                                % (key, full_path) )
+            
+        return res
+
     def clear(self):
         self._entities = {}
         del self[:]
Index: elixir/options.py
===================================================================
--- elixir/options.py	(Revision 455)
+++ elixir/options.py	(Arbeitskopie)
@@ -212,7 +212,9 @@
     allowcoloverride=False,
     order_by=None,
     mapper_options={},
-    table_options={}
+    table_options={},
+    resolve_function=None,
+    resolve_root=None
 )
 
 valid_options = options_defaults.keys() + [

Attachment: example.tar.gz
Description: GNU Zip compressed data

Reply via email to