FormEncode (Pylons): What Is State?

If you’re using Pylons, the Python framework, you’re probably using FormEncode. And if you’re using FormEncode, you’ve probably have noticed, and blithely ignored, the state argument that’s the last argument to a number of the validator class methods.

This is the official FormEncode explanation of state:

All the validators receive a magic, somewhat meaningless state argument (which defaults to None). It’s used for very little in the validation system as distributed, but is primarily intended to be an object you can use to hook your validator into the context of the larger system.

For instance, imagine a validator that checks that a user is permitted access to some resource. How will the validator know which user is logged in? State! Imagine you are localizing it, how will the validator know the locale? State! Whatever else you need to pass in, just put it in the state object as an attribute, then look for that attribute in your validator.

Hmmm. Maybe an example would help. Here’s the best example of its usage I could find on the Pylons site:

  this_schema = TheAppropriateSchema()
  try:
    state = dict() # this won't actually work. you'll need an object that formencode can hang things on.
    state["useful_state_information"] = something_useful
    form_result = this_schema.to_python(postvars, state)
    
    # validation is successful - form_result contains good data for you to consume
    
    redirect_to("somewhere")
  
  except Invalid, e:
    defaults = request.params
    errors = e.error_dict
    unfilled_html = a_function_that_draws_your_form_page_html()
    filled_html = htmlfill.render(unfilled_html, defaults, errors)
    return filled_html

Notice the comment: # this won’t actually work. Thanks for the warning.

So what is state and why would you want to use it?

Let’s answer the second question first, because at work, where we’re using the Pylons framework, I came up with an excellent situation which helped me figure out what it is and why I would want to use it.

The situation, briefly: our application has two separate forms. One is a form where a user can add new records in a multi-row table form. Each row is an individual record. The second is a review form, which looks just like the new record form, but is filled in from data uploaded in csv file (from another form).

Since DRY is a guiding principle of development in my office, the goal is to use the same underlying code for the form, controller, validation, etc. But the problem is that data originate in two different formats: one is as POST data from the add form, the second as the contents of a csv file. So how can we normalize these two data formats so that we can use the same underlying code? All together now: State! Now you’re talking!

If we could just tell our FormEncode validator where the data was coming from, then we could create two separate normalization methods in our validation class that would transform the data into a form that the validator and template could deal with. Anyway, enough verbiage. Find below a representative generalized version of the FormEncode validator I created and the controller it’s used in.

One more note before I finish. Notice the FormencodeState object comment. That explains why the example from the Pylons wiki does not work (in most cases). My first impulse would be to use a dict, too, as the state object. But the internals of the FormEncode validator class require an object. This is what gets passed in the validator method state argument and it is what passes info from the general framework environment to the validator.

Pylons Controller Code (with State Object)

class FormencodeState(object):
    """
    State class for formencode
    Although NOT well documented, to use the state argument in the to_python
    method in the context of schema that does complex, multistep validation,
    the state argument must be an object that formencode can hang additional
    attributes from, else you get errors like:
    Module formencode.schema:114 in _to_python         
    >>  state.full_dict = value_dict
    : 'dict' object has no attribute 'full_dict'
    """
    pass


# ... within actual Controller class method
Validator = SourceDataPreValidator()
ControllerState = FormencodeState()
ControllerState.source = 'csv'
Validator.to_python(DataValue, ControllerState)     # <- state object in action!

FormEncode Validator Code

class SourceDataPreValidator(formencode.validators.FormValidator):
    """normalize data from either a form submission or a csv file upload and validate"""
    validate_partial_form = True
    def _to_python(self, value, state):
        """normalize csv input"""
        if state.source == 'csv':
            value['normal_data'] = self._normalize_csv_data(value.get('csv_import_file'), state)
        elif state.source == 'form':
            pass
        return value

    def _normalize_import_data(self, csv_file_string, state):
        NormalDataList = []
        ImportedLines = csv_file_string.split('n')
        for i in range(len(ImportedLines)):
            ColValues = ImportedLines[i].split(',')
            fpre = 'csv_import-' + str(i)
            NormalDataList.append((fpre + '.id', str(ColValues[1])))
            NormalDataList.append((fpre + '.amount', str(ColValues[2])))
            NormalDataList.append((fpre + '.date',
                self._importdate_to_python_date(ColValues[4])))
        return NormalDataList

    def _importdate_to_python_date(self, datestr):
        """some code to convert a date string to object"""
        pass

I hope that sheds a little light on this powerful mystery. Questions or comments welcome.

Advertisements
FormEncode (Pylons): What Is State?

One thought on “FormEncode (Pylons): What Is State?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s