Re: new convenience methods (was Re: PyDO bugs)
Matthew Bogosian <[email protected]> Thu, 11 Aug 2005 13:11:24 -0700
| Newsgroups | gmane.comp.web.skunkweb |
|---|---|
| Message-ID | <[email protected]> |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Comments below.
-- Matt
On Aug 11, 2005, at 11:28, Jacob Smullyan wrote:
> On Thu, Aug 11, 2005 at 09:55:22AM -0700, Matthew Bogosian wrote:
>> ...
>>
>> Will these relationships also be inferred by the pydo.autoschema()
>> function? If not, I guess one could always add them to the classes
>> after they were returned (but this seems to defeat the purpose of
>> having a function like autoschema()).
>
> I don't know about autoschema itself yet, but the place to start would
> be the introspection api of the dbi drivers. This looked like a lot
> of work to undertake for 2.0, so I deferred it, and whether it will
> ever get done depends on whether it is really useful to somebody and
> whether anyone wants to pitch in to help do it (or otherwise provides
> inducements!).
I'd certainly be willing to tackle an implementation for MySQL.
However, it appears to be much more work without the introduction of
the INFORMATION_SCHEMA tables introduced in 5.0.2. The only way I know
to approach the problem is to use the SHOW CREATE TABLE table_name
syntax and try to parse the results (ugh).
> The old PyDO did do this in its genscripts; it asked you interactively
> to confirm its inferences.
This also got me thinking...how does one handle foreign key constraints
that reference multiple columns? How did PyDO do this before in its
genscripts (if at all)? How would one do it with the new functions?
Here's a contrived example:
> CREATE TABLE persons
> (
> first_name VARCHAR(255),
> last_name VARCHAR(255),
> gender VARCHAR(1),
> height_in_m FLOAT(3,2),
> weight_in_kg FLOAT(5,2)
> );
>
> ALTER TABLE persons ADD PRIMARY KEY (first_name, last_name);
>
> CREATE TABLE phone_numbers
> (
> first_name VARCHAR(255),
> last_name VARCHAR(255),
> phone_number VARCHAR(63),
> location VARCHAR(63)
> );
>
> ALTER TABLE phone_numbers ADD FOREIGN KEY (first_name, last_name)
> REFERENCES users (first_name, last_name) ON DELETE CASCADE;
Is there a way (using the new functions) to represent this relationship?
> I'm actually a bit annoyed by using these new methods I created,
> because they require that the other PyDO class to which there is a
> relationship be already defined when the relationship is defined.
> That means that I have to do things like this:
>
> class A(PyDO):
> pass
>
> class B(PyDO):
> getA=OneToMany('a_id', 'id', A)
>
> A.relatedB=ForeignKey('a_id', 'id', B)
>
> That's OK, but not fantastic. Before, the references to related
> classes were in methods that were evaluated at runtime, not class
> definition time, so this problem wasn't present. Anyone have a bright
> idea of how to avoid this?
Hmmm...this is difficult since Python has no notion of advanced
declaration (like C/C++ for example). Is there a reason why the
*shouldn't* be made at class definition time?
Without getting into too much metaclass hairiness, how about using a
classmethod to register the names of other classes and resolve the
names into type during run-time. That way the types could be retrieved
at runtime. Here's a small example:
> class Base(object):
> def getPrivateAttr(a_class, a_attr_name):
> attr_name = '_%s__%s' % (a_class.__name__, a_attr_name)
> getattr(a_class, attr_name)
> getPrivateAttr = classmethod(getPrivateAttr)
>
> def setPrivateAttr(a_class, a_attr_name, a_attr_val):
> attr_name = '_%s__%s' % (a_class.__name__, a_attr_name)
> setattr(a_class, attr_name, a_attr_val)
> setPrivateAttr = classmethod(setPrivateAttr)
>
> def register(a_class, a_class_name, a_namespace):
> try:
> registered = a_class.getPrivateAttr('registered')
> except AttributeError:
> registered = {}
> a_class.setPrivateAttr('registered', registered)
> registered[a_class_name] = a_namespace
> register = classmethod(register)
>
> def retrieveRegistered(a_class, a_class_name):
> try:
> registered = a_class.getPrivateAttr('registered')
> except AttributeError:
> registered = {}
> a_class.setPrivateAttr('registered', registered)
> return registered[a_class_name][a_class_name]
> retrieveRegistered = classmethod(retrieveRegistered)
>
> class A(Base):
> pass
> A.register('B', globals()) # class B is not defined yet
>
> class B(Base):
> pass
> B.register('A', globals())
>
> print A.retrieveRegistered('B')
> print B.retrieveRegistered('A')
I don't know if this helps. Compounding matters, the existence of
classmethod is tentative for future versions (see the "Factoid" in
"Overriding the __new__ method" here
<http://python.org/2.2/descrintro.html#__new__>).
I have *not* thought about how to do this more effectively across
namespaces (e.g., module boundaries) other than the namespace hack
above. This does *not* work for circular references between modules:
> # File my_modules/a.py
> from my_modules import Base
> import my_modules.b
> class A(Base):
> pass
> A.register('B', my_modules.b)
>
> # File my_modules/b.py
> from my_modules import Base
> import my_modules.a
> class B(Base):
> pass
> B.register('A', my_modules.a)
So I guess none of this is actually all that useful.... One would have
to do something like this instead:
> # File my_modules/a.py
> from my_modules import Base
> import my_modules.b
> class A(Base):
> pass
>
> # File my_modules/b.py
> from my_modules import Base
> import my_modules.a
> class B(Base):
> pass
>
> # File my_modules/a_and_b.py
> import my_modules.a
> import my_modules.b
> my_modules.a.A.register('B', my_modules.b)
> my_modules.b.B.register('A', my_modules.a)
And this is not really all that elegant (it just kind of avoids the
issue by putting in one layer of indirection).
One could modify Base above to take fully-qualified names (e.g.,
'my_module.a.A') and use the __import__ built-in function to attempt to
resolve it at run-time:
> import types
> class Base(object):
> ...
>
> def register(a_class, a_fqcn, a_alias):
> try:
> registered = a_class.getPrivateAttr('registered')
> except AttributeError:
> registered = {}
> a_class.setPrivateAttr('registered', registered)
> registered[a_alias] = a_fqcn
> register = classmethod(register)
>
> def retrieveRegistered(a_class, a_alias):
> try:
> registered = a_class.getPrivateAttr('registered')
> except AttributeError:
> registered = {}
> a_class.setPrivateAttr('registered', registered)
> klass = registered[a_alias]
> if type(klass) in types.StringTypes:
> path = klass.split('.')
> mod_name = '.'.join(path[:-1])
> class_name = path[-1]
> if mod_name:
> mod = __import__(mod_name, globals(), globals(), ( ' ', ))
> else:
> mod = globals()
> klass = getattr(mod, class_name)
> registered[a_alias] = klass
> return klass
> retrieveRegistered = classmethod(retrieveRegistered)
Yes, this is very ugly, but you could now do this:
> # File my_modules/a.py
> from my_modules import Base
> import my_modules.b
> class A(Base):
> pass
> A.register('my_modules.b.B', 'B')
>
> # File my_modules/b.py
> from my_modules import Base
> import my_modules.a
> class B(Base):
> pass
> B.register('my_modules.a.A', 'A')
>
> # File test.py
> from my_modules.a import A
> from my_modules.b import B
> print B.retrieveRegistered('A')
> print A.retrieveRegistered('B')
I tried to keep this example as simple as possible for readability.
Hopefully it is easy to extrapolate how this approach might be useful
to the dilemma you posed with the relationship methods, but I
understand if it's not. If it isn't straightforward how to apply this
technique, please let me know.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.0 (Darwin)
iD8DBQFC+7DsnLpDzL5I7l8RAqKHAKCIlMJOnwXzqfLQzXv4YvMFjHkZCACbBrLE
3JMHsj0YIie7j4arqJ5+y3A=
=g4r9
-----END PGP SIGNATURE-----
-------------------------------------------------------
SF.Net email is Sponsored by the Better Software Conference & EXPO
September 19-22, 2005 * San Francisco, CA * Development Lifecycle Practices
Agile & Plan-Driven Development * Managing Projects & Teams * Testing & QA
Security * Process Improvement & Measurement * http://www.sqe.com/bsce5sf