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() + [
example.tar.gz
Description: GNU Zip compressed data
