.. This is a brainstorm of sorts... Form Processing Plan ==================== Data can come from one of several sources: * Python, i.e., from the programmer * HTTP request (normal GET/POST variables) * XMLRPC request * SQL data source * SOAP request * Other RPC source (perhaps more Python aware than XMLRPC) Data when being received is first converted to Python data structures. This conversion creates the best representations, so while an XMLRPC request gives Python data structures (even an HTTP request does), an XMLRPC array (dictionary) may be converted into a particular object. On the way out a similar conversion must occur, say changing an object into a dictionary. This is driven by a *schema*. This is different from, say, XMLRPC's mechanism that turns an XML document into certain Python types. The conversion is not contained in the data itself, but contained externally, and controlled by the server programmer. In each situation a validation process can occur, which is mixed with the conversion (for instance, something that converts a string to an integer may in the process find the string invalid). In all cases the validation occurs on the Python objects. A `Translator` encompasses both these concepts, and does both operations (conversion and validation) at once. This is different from an object-oriented or polymorphic approach. The data does not contain its own type information -- or at least the type information it does include is not assumed to be complete or entirely correct. That said, it is expected that hooks will be included so that Python objects can make themselves easier to plug into the system. For instance, a `__sqlrepr__` method could translate a Python instance into its SQL representation. This is incomplete, however, as such a method cannot easily be added to pre-existing types, and it does not indicate how to recreate the object on the return path. The conversion also has to be sensitive of the context. For instance, when converting True/False to a SQL boolean, you may want ``'t'`` and ``'f'``, but when converting to a SQL integer you want ``1`` and ``0``. Therefore the conversion must be sensitive of both sides -- both the Python type (which is implicit), and the target's expected type. The target's expected type is often not possible to find programmatically, or it is too inefficient to do so (for instance, database introspection for each value inserted would be impractical, while type introspection on Python objects generally is not). The type information for the target is essentially an interface specification, though not the same sort of interface specification we that is being worked on for Python. It is not a set of methods, but a set of "types", which are not to be confused with native Python types/classes. We'll refer to them as `ExternalType`. ExternalType does not apply to typeless interfaces like HTTP (though it would apply to XMLRPC). The Target Specification ------------------------ An interface specification, the exact format depending on the target. For instance, a SQL specification:: dict( id=SQLInt(notNull=True), first_name=SQLString(length=10), last_name=SQLString(length=30), ) Or an XMLRPC specification:: [ XMLRPCInt(), XMLRPCStruct( first_name=XMLRPCString(), last_name=XMLRPCString(), ), ] `SQLInt` and `XMLRPCStruct` are examples of ExternalTypes. The arguments these take are largely specific to the type and the target. However, there are some arguments which are common to all ExternalTypes: `ifMissing`: The value that should be substituted if nothing was given. This is a Python object. Optional argument, but no default. `optional`: A boolean, default False. If True, this argument can be left out. Trying to access it in that case will cause some exception. `ifMissing` and `optional` are exclusive. ExternalTypes have an opportunity to coerce the values sent to the target just before they are sent, and can reject those values as invalid. However, they need not do so. SQL types would, for instance, do proper quoting. Quoting should **not** occur earlier than the ExternalType. While external targets have specifications, the Python interface specification is the schema itself. The Schema ---------- A schema is a description of a the goal structure for creating Python objects, and a validation scheme for ensuring valid use of the interface without trusting the source. The goal is not to call a particular method or set of methods, but to simply create a static data structure. This data structure allows nesting of schemas, and the repeating of any element (including a schema). Elements of this plan can be used without using the Schema. A sample Schema definition:: Schema( id=int, name=Schema(first_name=str, last_name=str), ) Schemas are related to ExternalTypes. They all share a Translator superclass. In this case we are using simple classes (`int` and `str`) as type declarations. Schema promotes these to full Translators, but in their place you can use full Translators as well, for instance:: Schema( id=Int(), name=Schema(first_name=String(maxLength=10, ifMissing=None), last_name=String(maxLength=20)), ) Only by using the full Translator equivalent will `ifMissing` and other annotations be available. Any Translator can be chained. This is done simply by putting them in a list. Using the Schema ---------------- ...