--------------------------------------------- Randy Syring Development & Executive Director Level 12 Technologies (formerly Intelicom) Direct: 502-276-0459 Office: 502-212-9913 Intelicom is now Level 12 Technologies, learn more about our name change. Please update your address book with my new email address. Principled People, Technology that Works
I'm wondering what the chances are of getting some kind of instance event that would send INSERT/UPDATE values back to it right before sess.exec() is called for an INSERT or UPDATE operation. I'd imagine something like:
@event.listen_for(Car, 'before_exec'):
def validate_entity(car, values, type):
assert type in ('insert', 'update')
if 'active_flag' in values:
values['active_flag'] = 1 if values['active_flag'] else 0
if values['make'] not in ('Ford', 'Chevy'):
raise ValueError('make not recognized')
if 'mileage' not in values:
values['mileage'] = 'unknown'
return values
I currently have a validation library (https://bitbucket.org/rsyring/sqlalchemy-validation/) that uses the before_flush session event to try and do validation, but I run into problems with validating values that come from column defaults and other more "low level" places.
I realize there are different ways to do validation and there are arguments to be made against ever needing to validate a default value. However, I'd like to be able to do validation of an entity in as low-level of a place as possible so as to guarauntee catching as many exceptions as I can without relying on form libraries to do input validation on my model. I want the model to be self-validating so that I can use it in a web application, GUI, or console app and not have to write validation code in three places.
If this is possible, I'm willing to work on providing a patch. I just want to make sure its possible and would be considered for acceptance before I go down that route.
Thanks.
--------------------------------------------- Randy Syring Development & Executive Director Level 12 Technologies (formerly Intelicom) Direct: 502-276-0459 Office: 502-212-9913 Intelicom is now Level 12 Technologies, learn more about our name change. Please update your address book with my new email address. Principled People, Technology that Works
--
You received this message because you are subscribed to the Google Groups "sqlalchemy-devel" group.
To post to this group, send email to sqlalche...@googlegroups.com.
To unsubscribe from this group, send email to sqlalchemy-dev...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/sqlalchemy-devel?hl=en.
On Mar 13, 2012, at 3:10 PM, Randy Syring wrote:
I'm wondering what the chances are of getting some kind of instance event that would send INSERT/UPDATE values back to it right before sess.exec() is called for an INSERT or UPDATE operation. I'd imagine something like:
@event.listen_for(Car, 'before_exec'):
def validate_entity(car, values, type):
assert type in ('insert', 'update')
if 'active_flag' in values:
values['active_flag'] = 1 if values['active_flag'] else 0
if values['make'] not in ('Ford', 'Chevy'):
raise ValueError('make not recognized')
if 'mileage' not in values:
values['mileage'] = 'unknown'
return values
I currently have a validation library (https://bitbucket.org/rsyring/sqlalchemy-validation/) that uses the before_flush session event to try and do validation, but I run into problems with validating values that come from column defaults and other more "low level" places.
I realize there are different ways to do validation and there are arguments to be made against ever needing to validate a default value. However, I'd like to be able to do validation of an entity in as low-level of a place as possible so as to guarauntee catching as many exceptions as I can without relying on form libraries to do input validation on my model. I want the model to be self-validating so that I can use it in a web application, GUI, or console app and not have to write validation code in three places.
If this is possible, I'm willing to work on providing a patch. I just want to make sure its possible and would be considered for acceptance before I go down that route.
One issue with raising validation errors like that is if the object has multiple validation issues, you only get to see one of them at a time. If you're flushing 50 objects, and five of them have two validation issues, you get just one validation issue out of ten. This suggests the better way to handle these validation issues is to aggregate them together, then report the multiple issues as a batch, which could be a single exception that has a collection of all issues found.
But working with validation issues in the aggregate means you need to look for them before the persistence system has drilled down too far, where you're well within the weeds and can only see the ground right in front of you, not anything else. Invoking a ValueError right at the point of a single SQL statement, for example, would by necessity blow away the whole flush process - if the INSERT fails, it follows that everything has to stop - other objects and states might be dependent on it. Invoking a ValueError at the point of when the mapper batches up a list of commonly-typed objects together might be able to aggregate validation across that subset, but prevents validation for other mappers to occur once an exception has cancelled the process.
In any case, validation is kind of a model-level concern, such as with the event you have above (which is analogous to before_flush). Column default values aren't known until after the INSERT occurs - you can of course get at those within the after_flush() event, so if you're looking for post-INSERT validation that's where you'd put that.
Local validations can also be in events like before_insert() and after_insert() though you have limited ability to look beyond the scope of the object immediately at hand.
import sqlalchemy as saThe above would only abort the exec() for this given instance, the flush would proceed for all other mappers as if this mapper had succeeded.
import sqlalchemy.orm as saorm
# this event should also accept mapper which would indicate all instance types (just like other events)
@event.listen_for(Comment, 'before_exec'):
def validate_entity(comment, values, type):
if not values.get('blog_id'):
comment.add_validation_error('blog_id', 'must not be empty')
# tells SA internals not to send this flush to the DB. Obviously, it
# assumes that there is code in a after_flush() event to pick up the
# validation error, do something with it, and ultimately abort the flush
raise saorm.ABORT_EXEC
But working with validation issues in the aggregate means you need to look for them before the persistence system has drilled down too far, where you're well within the weeds and can only see the ground right in front of you, not anything else. Invoking a ValueError right at the point of a single SQL statement, for example, would by necessity blow away the whole flush process - if the INSERT fails, it follows that everything has to stop - other objects and states might be dependent on it. Invoking a ValueError at the point of when the mapper batches up a list of commonly-typed objects together might be able to aggregate validation across that subset, but prevents validation for other mappers to occur once an exception has cancelled the process.
Agreed. However, my intention was not to throw a validation error. Rather, i would save the errors on the instance itself. Then, I just need to be able to abort the process once all the mappers have been processed (say in before_flush() or after_flush()).
In any case, validation is kind of a model-level concern, such as with the event you have above (which is analogous to before_flush). Column default values aren't known until after the INSERT occurs - you can of course get at those within the after_flush() event, so if you're looking for post-INSERT validation that's where you'd put that.
Yes, but if the values you want to validate are invalid in the DB, then you get a DB exception and the problem is worse (more on this below).
Local validations can also be in events like before_insert() and after_insert() though you have limited ability to look beyond the scope of the object immediately at hand.
Yes, and its really the "limited ability to look beyond the scope of the object immediately at hand" that gets me. Other than this, everything works as-is currently. The main difficulty I have, and the reason for this post, is that some values that actually get sent to the DB are not available in the before_flush() event and I'd like to be able to validate them before they hit the DB (lest they cause an exception and a bigger, uglier mess).
So, maybe my question really is: can we create an event to address the fact that there are values getting sent to the DB that are not available in the before_flush() event? Just for visuals sake, here is an example of my problem:
http://paste.pocoo.org/show/565484/
Currently, I don't have the ability to test either the default or FK values until the after_flush() event.
The FK issue is an even bigger problem IMO b/c it really is something dependent on the data a user might send. Not being able to validate that value means I get different DB errors that I have to deal with (not-null error on some, FK on others, different text in the exceptions) instead of a standard error exception from my validation library telling me that 'Commen.blog_id may not be empty'. Granted, I can and do do validation of values before they get to the model, but currently that has to be repeated for each interface type. Requiring Comment.blog_id seems like something I should be able to enforce at the model level.
Ok, so I'm starting to understand my problem a little better too. As long as a value going to the DB isn't going to create a DB exception, it can be validated in the after_flush() context without a problem. The only time where there are going to be problems is when the invalid value will cause a DB exception that you want to avoid (like a missing FK which is required). So, how about creating an exception that can be thrown in the event I propose above that would tell SA to not send that SQL to the DB. Something like:
import sqlalchemy as sa
import sqlalchemy.orm as saorm
# this event should also accept mapper which would indicate all instance types (just like other events)
@event.listen_for(Comment, 'before_exec'):
def validate_entity(comment, values, type):
if not values.get('blog_id'):
comment.add_validation_error('blog_id', 'must not be empty')
# tells SA internals not to send this flush to the DB. Obviously, it
# assumes that there is code in a after_flush() event to pick up the
# validation error, do something with it, and ultimately abort the flush
raise saorm.ABORT_EXEC
--------------------------------------------- Randy Syring Development & Executive Director Level 12 Technologies
Direct: 502-276-0459 Office: 502-212-9913
Principled People, Technology that Works