Re: [SQLObject] Sobject and validating via schema objects.
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
From: Rajeev J S. <raj...@di...> - 2005-05-12 21:56:58
|
On Thursday 12 May 2005 5:29 pm, David Driver wrote: > I am trying to figure out the best way to organize this. I have a > sqlobject "Customer" and a validator schema CustomerSchema. I want the > application to be able to use sqlobject directly so I don't have to > do anything to wrap up the object traversal. Java programming would > wrap sqlobject with another object that contains the domain logic and > getters+setters etc to get some seperation between the application and > the persistence object. Since I want the application to use the > sqlobject directly I need to decide on how to interject validation and > other DO logic. > > What I am looking at now is an object that be something like > "CustomerUpdater." This would receive the object that needs to be > updated and a dictionary that maps keys to new values. This would be > called by the application. The updater would pass values from the > updated dictionary and values in the sqlobject to the validator schema > doing field level validation then doing chain validation. If > everything validates update the sqlobject. If it doesn't return a > dictionary or list of fields that didn't validate and the sqlobject > remains unchanged. > > I have drawn this on the whiteboard several times and this is the > closest that I can get to making sense out of what I want to do. > > Does it make sense to anyone else? Not only that it makes sense, but also, some of us were discussing this earlier. Well, we were discussing the "dictionaries" that have to be validated. Ian Bicking uses proper dictionaries for this, while Cook has used/does use automatic proxies. I personally, have used dictionaries and "manual" proxies. In any case, this area what you discuss, seems to be out-of-scope for SQLObject. SQLObject is meant to be a pure interface to the data store itself (in spite of its object oriented nature): ie, it would only be a part of the Model in a MVC application (perhaps a GUI, or a webapp). You talk about that part of the Model, which is a glue between SQLObject itself, and the View and/or Controller. When considering dictionaries and an Automatic or "manual" proxy generation system, both have almost the same power. The dictionaries though, if used properly, can also be viewed as a dynamic form of the Command pattern (i have seen the word Memento used for this pattern). This happens when, in your system, you use empty dictionaries to denote unchanged objects. The Command system is useful, since it allows a psuedo-transaction system. Taking care of other problems (like synchronizing the View to the Model, etc), this is the basis for an transaction system (a GUI transaction system can be seen in the Undo-Redo of most word processors). The problem with empty dictionaries, is that the View (say the dialog in which you show the Customer) needs some more intelligence to behave properly. So, thinking over the past few days, I think a good solution would be something like this: class CommandingProxy: def __init__(self, proxiedObj): self.po_object = proxiedObject self.memento = {} def __getattr__(self, attr): if attr in self.memento: return self.memento[attr] ##if you want Undo-Redo, you need a case here ## to see if the previous objects in the memento stack have any ## changes registered for this attribute elif attr in self.po_object.__dict__: return self.po_object.__dict__[attr] else: raise AttributeException('no such attribute '+attr) def __setattr__(self,attr, value): if attr in self.memento: self.memento[attr] = value ##if you want Undo-Redo, you need a case here ## to see if the previous objects in the memento stack have any ## changes registered for this attribute elif attr in self.po_object.__dict__: self.memento[attr] = value else: raise AttributeException('no such attribute '+attr) def isDIrty(self): return len(self.memento.keys()) >0 ## append 'or len(self.mementoStack.keys()) > 0', for the Undo-Redo system def commit(self): ##here, go through all the keys in the memnto, and apply them to the ## Proxied Object that is stored in this proxy: self.po_object ##for an Undo-Redo system, push this memento into a stack of previous ## mementos, and them self.memento = {} def rollback(self): ##here discard the memento ##do the similiar magic required for Undo-Redo In this case, you would pass the CommandingProxy object to the Schema, and allow it to validate it. SQLObject compliance: The above code is probably not correct, (I havent run it yet), and also requires changes to the parts where the proxied objects are queried for attributes ( for e.g., "elif attr in self.po_object.__dict__" ), when the proxied objects are SQLObject-s: The good thing is, much of this can be autogenerated at runtime. See David Cook's email for that. Cook gives an example of using SQLObject's meta class based system to query a database of attributes, which can be used to determine what type they are (maybe useful to the Schema objects), and also to determine the processing of ForeignKey-s, RelatedJoin-s and MultipleJoin-s. So the constructor must also take the appropriate SQLObject class as an attribute. Some special code will be necessary, imho, for the handling of deletion of objects from a relationship (eg, removing one of the Addresses for a Customer). I haven't thought about how to achieve this though (maybe the attribute will point to a mark list with the deleted objects ?) The problem of new objects : Another significant point, is when the CommandingProxy proxies a non-existent object. A problem, which started this discussion with Cook and Bicking, was that I wanted to create SQLObject-s which may at times violate the data integrity constraints, but will not be saved until it meet these constraints. This is a problem, because typically, in a GUI I do not want the editor to know whether an object is new or not - the editor only reports the current state of object as saved or not-saved. A seperate Validator view component (say an Led widget) would report on whether the object is valid and if not what the errors are. When the object is dirty and the user saves it, if the object doesnt already exist, then it should insert it into the database, and if it already exists, it would only update it (say via .set ) Due to this reason, when you query the proxied object for attributes, and the proxied object is None (denoting the case where the object is New), then we need to the use the SQLObject subclass, to query for attributes and types. This is also necessary to get default values for new objects. Out of scope: Anyway, I think all this is out of the scope of the SQLObject project itself and Ian Bicking has also said that client-side transactions is not in the near future of SQLObject. Any of the above would be done in a subclass of SQLObject which all your model classes should derive from. In this subclass, you do the magic of providing proxies and schemas. There should be a mechanism to let the automatic parts like the minimal data integrity constraints in SO (e.g., unique=True, notNone=True, length=10, precision=2, etc, etc), and also allow us to plugin our own features, for eg, an ISBNValidator. Maybe subclassing Col (or its standard subclasses) would be a good way for that ? Or another way would be to do something like this: class CustomerProxy(CommandingProxy): _proxiedClass = 'Customer' ##or a proper python reference like _proxiedClass = Customer ##add any additional stuff that may be necessary to be handled seperately ## by proxies or to provide custom versions of autogenerated code class CustomerSchema(SQLObjectValidator): _validatedClass = 'Customer' ##or a proper python reference like _validatedClass = Customer ##in unit test style we can add tests def test_addresses(self, customer): if len(customer.addresses) == 0: return (False, 'Customer must have atleast one address') return True def test_firstName(self, customer): if len(customer.firstName) == 0: return (False, 'Customer must have a first name') return True Doing it this way, may require the knowledge of meta-classes (which Ian Bicking is probably an expert at right now ;) By the way, if you do decide to do something like this, please do put a message on the list. Rajeev J Sebastian |