[Modeling-cvs] ProjectModeling/Modeling/doc/UserGuide CodeRequirements.tex,1.3,1.4
Status: Abandoned
Brought to you by:
sbigaret
From: Sebastien B. <sbi...@us...> - 2004-11-29 17:08:54
|
Update of /cvsroot/modeling/ProjectModeling/Modeling/doc/UserGuide In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv2881/Modeling/doc/UserGuide Modified Files: CodeRequirements.tex Log Message: Documentation: added full documentation concerning: 1. the static generation, or the dynamic building of python package and modules from a model, 2. the requirements the framework imposes on python modules and classes. Index: CodeRequirements.tex =================================================================== RCS file: /cvsroot/modeling/ProjectModeling/Modeling/doc/UserGuide/CodeRequirements.tex,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** CodeRequirements.tex 21 Sep 2003 18:50:37 -0000 1.3 --- CodeRequirements.tex 29 Nov 2004 17:08:43 -0000 1.4 *************** *** 32,43 **** ! \section{The python package generated from an XML model \label{basics-CustomObject}} ! We take the model \code{AuthorBooks} as an example--you'll find it at:\\ ! \file{Modeling/tests/testPackages/AuthorBooks/model_AuthorBooks.xml}, as ! defined in section~\ref{model-author-books}. ! In this model, we have two entities, \class{Writer} and \class{Book}: \begin{verbatim} --- 32,61 ---- ! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ! \section{From model to python code\label{from-model-to-python}} ! A (valid!) model contains all the necessary informations to generate a usable ! python package that can be used immediately to store and retrieve informations ! in a database. + In this section, we examine the different ways offered in the framework to + derive usable python code from a model; the model we'll use is + \code{AuthorBooks}, that you'll find it in:\\ + \file{Modeling/tests/testPackages/AuthorBooks} (either the pymodel in + \code{pymodel_AuthorBooks.py}, or the xml-model in + \code{model_AuthorBooks.xml}), and which is described in + section~\ref{model-author-books}. ! \begin{notice} ! Even if the framework offers different ways of deriving python code from ! models, you do not {\em have to} use these tools: they are provided for ! convenience but you can choose to write your package and code by other means ! of your own. If you choose to do so, you'll find below in the ! section~\ref{class-code-requirements} the few requirements needed to ! bind your classes to a model and to the framework. ! \end{notice} ! ! Quick reminder: in this model, we have two entities, \class{Writer} and ! \class{Book}: \begin{verbatim} *************** *** 52,125 **** \end{verbatim} ! When you generate the python code for that model, you get a ! \module{AuthorBooks} package. \begin{itemize} ! \item \module{__init__.py} takes care to load the model within the default ! \class{ModelSet}, ! \item the model xml file is dropped within the package, ! \item you get two modules, \module{Writer} and \module{Book}, containing, ! respectively the classes \class{Book} and \class{Writer}. \end{itemize} ! Let's have a look at the \class{Book} class -- I won't copy it here, go to ! \file{Modeling/tests/testPackages/AuthorBooks/Book.py} and keep an eye on ! it (or generate your own copy using the ZModelizationTool or the provided ! scripts). ! ! Let's take a look at the code. After the initial imports, we get: ! \begin{enumerate} ! \item class declaration: ! \begin{verbatim} ! class Book (CustomObject): ! \end{verbatim} ! i.e.: every object should derive from \class{CustomObject}, which ! defines the appropriate methods. ! \item Initializer: defines some default values for your attributes. ! {\bf Note}: It {\bf must} be possible to call the \method{__init__} ! with no arguments at all. If you want to add arguments to ! \method{__init__}, each one should have default values. The reason for this is ! that the framework relies on a call to \code{__init__()}, without any ! arguments, when it needs to create an instance before populating it with data ! fetched from the database. ! \item \code{def entityName(self)}: this is the way the framework currently ! binds an object to its entity. This should be changed (see TODO) ! \item Then you get setters and getters for the attributes, whose exact form ! depends on their nature (attributes, to-one or to-many relationships). ! There is also some validation code ready to be used. See ! \ref{customobject-validation}, ``Validation'' for details. ! In getters and setters, notice the methods \method{willRead()} and ! \method{willChange()}. These methods are defined by \class{CustomObject}. \begin{itemize} - \item \method{willRead} informs the object that we need the values stored in - the database. This is because objects can be ``{\em faults}'' (ZODB - speaking, they are ghosts). This method's job is to initialize the - object. ! \item \method{willChange} informs the object that it is about to change. This ! is part of the \class{Observing} interface, and its purpose is to notify ! the \class{EditingContext} that an object is about to change; the ! \class{EditingContext} needs this to keep track of changes in its graph ! of objects. ZODB speaking, this is what the mix-in class ! \class{Persistent} does transparently for immutable attributes (see ! also: TODO). ! Of course, \method{willChange} invokes \method{willRead} when ! appropriate. \begin{notice}[warning] It is your responsability to call \method{willRead} and \method{willChange} when you are about to, respectively, access or ! change a class attribute corresponding to a Model's Attribute or Relationship; if you do not, the \class{EditingContext}, which is responsible for examining the changes and making them persistent, is --- 70,389 ---- \end{verbatim} ! \subsection{Generating the python code\label{from-model-to-python-static-approach}} ! ! \subsubsection{The minimum\label{from-model-to-python-static-approach-compact}} ! ! The first option you have is to generate the whole python package ! \code{AuthorBooks} and its modules \code{Writer.py} and \code{Book.py}. ! ! For that purpose, you'll use the script \program{mdl_generate_python_code.py} ! (note that the same functionalities are also offered in the ZModeler). ! ! On the command-line, type: ! \begin{verbatim} ! mdl_generate_python_code.py model_AuthorBooks.xml ! \end{verbatim} ! ! We'll see in the next paragraph that there exists two different way of ! generating the code. That one is the simplest and uses the default option of ! the script \program{mdl_generate_python_code.py}: \programopt{-C} or ! \longprogramopt{compact-generation-scheme}\footnote{Please refer to ! \program{mdl_generate_python_code.py}~\longprogramopt{help} for a ! comprehensive view on the script's usage and options}. ! ! The script creates the python package \code{AuthorBooks} in the current ! directory, in which you'll find the following files: ! ! \begin{verbatim} ! AuthorBooks/ ! |-- Book.py ! |-- Writer.py ! |-- __init__.py ! |-- * model_AuthorBooks.py ! |-- * model_AuthorBooks.xml ! `-- setup.py ! \end{verbatim} ! ! Note: the files marked with a star~(\code{*}) are the only one that are ! overwritten by the script, if they already exist. ! \begin{itemize} ! \item \module{__init__.py}, which takes care to load the model within the ! default \class{ModelSet}, ! \item a copy of the model in the corresponding xml file which is used, and a ! python file containing a copy of the xml model as well; the reason for which ! the model is copied into a python file is that it makes it easy to ! distribute and install it along w/ the other modules using the standard ! distutils. ! Also note: even if you generate the python module from a pymodel, you'll ! only get those two xml-models in the generated package. This might change in ! the future, in the meantime, feel free to replace them with your pymodel, ! the code generated in \file{__init__.py} loading the model is capable of ! finding either a pymodel or an xml-model (see ! \ulink{Model.searchModel()}{http://modeling.sourceforge.net/API/Modeling-API/public/Modeling.Model-module.html\#searchModel} ! ! \item \module{Writer} and \module{Book}: they contain the classes \class{Book} ! and \class{Writer}, ready to be used ! ! \item \module{setup.py}: a \module{distutils} script that you can use to ! install the package, to distribute it, etc. (see \ulink{Distributing Python ! Modules} {http://www.python.org/doc/current/dist/dist.html} and ! \ulink{Installing Python Modules} ! {http://www.python.org/doc/current/inst/inst.html} for details about the ! Python Distribution Utilities). \end{itemize} ! This bunch of files gives a minimalist view of what is needed to bind python ! code to a model and to the framework --this subject is fully discussed in the ! dedicated section~\ref{framework-code-requirements}. You can use it: ! \begin{itemize} ! \item for quick testing a model, but in this case, you'll probably prefer to ! dynamically build the package and modules (see ! section~\ref{from-model-to-python-dynamic-approach}, below), ! \item or to start coding your classes from that point. Now if you want to use ! the generated code as a basis for your own development, we strongly advise ! you to consider using the other generation scheme exposed in the next ! paragraph. ! \end{itemize} ! \subsubsection{Separating the generated code from your own work\label{from-model-to-python-static-approach-base}} ! During development, especially at early stages, the model is likely to change ! at a high rate, and so does the code which is automatically derived from the ! model. In such situations (and, in fact, each time the model change), it is ! quite easy to see that the so-called ``compact scheme'' used in the paragraph ! above is not very convenient: the script does not overwrite any existing file, ! so integrating the new changes consists in, for example, moving the python ! file elsewhere, regenrate the code, then integrate any code you may have ! written by hand from the previsouly moved python file to the new one... ! ! ! That's why the script \program{mdl_generate_python_code.py} has an other ! option: \programopt{-B} or ~\longprogramopt{base-generation-scheme}, which is ! specially designed to keep the generated code separated from the business ! logic you add, so that you'll never have to worry mixing your own logic with ! automatically derived portions of code. ! ! ! As an example, let's see the generated files by this scheme: ! ! \begin{verbatim} ! AuthorBooks/ ! |-- Book.py ! |-- MDL ! | |-- * Book.py ! | |-- * Writer.py ! | |-- * __init__.py ! | |-- * model_AuthorBooks.py ! | `-- * model_AuthorBooks.xml ! |-- Writer.py ! |-- __init__.py ! `-- setup.py ! \end{verbatim} ! ! As you can see, a new subpackage \code{MDL} is created, where the files are ! always overwritten when regenerating the code --and more: the files within ! that directory are the only ones that can be overwritten by the script. ! ! ! The modules \module{Book} and \module{Writer} in the top-level directory are ! the place where you'll put your own code; the classes within directly inherit ! from the ones in the \code{MDL} subpackage, keeping the automatically ! generated portion of codes completely separated from your own code. ! ! ! \subsection{Building the python package dynamically, at run-time\label{from-model-to-python-dynamic-approach}} ! ! You also have the option to derive all necessary modules and classes directly ! from a model, at runtime. The module \module{Modeling.dynamic} has been added ! for that purpose, and offers two different options. ! ! \subsubsection{Dynamic generating of ``standard'' python code} ! ! The first method simply consists in building the exact same code as the one ! generated by \code{mdl_generate_python_code.py}: ! ! \begin{verbatim} ! ### Load the model ! def load_model(): ! from Modeling import ModelSet, Model ! model=Model.searchModel('AuthorBooks', '.', verbose=1) ! ModelSet.defaultModelSet().addModel(model) ! ! # build and use it! ! from Modeling import dynamic ! dynamic.build(model, define_properties=0) ! from AuthorBooks.Book import Book ! \end{verbatim} ! ! As expected, if you call \method{build} with \code{define_properties=1}, the ! method adds python properties (see \ulink{\code{property')} in built-in ! functions}{http://docs.python.org/lib/built-in-funcs.html} for each attribute ! or relationship in the entity, so that you do not need anymore to use ! e.g. \code{book.getTitle()} or \code{book.setTitle}, but simply \code{print ! book.title} or \code{book.title="my title"}. ! ! ! \subsubsection{Dynamic generation using metaclass} ! ! The second method makes use of the metaclass \class{dynamic.CustomObjectMeta}: ! ! \begin{verbatim} ! # file: Book.py ! # We assume that the model is already loaded ! from Modeling import dynamic ! ! class Book: ! __metaclass__=dynamic.CustomObjectMeta ! entityName='Book' ! mdl_define_properties=1 ! ! # your own code here ! ! \end{verbatim} ! ! % note: class Book should be accessible w/ 'from package.module import class' ! ! The metaclass \class{CustomObjectMeta} automatically adds all the necessary ! methods to a class for integration with the modeling framework. It looks for ! the following attributes in the class: \begin{itemize} ! \item \code{entityNamed}`: mandatory, this is the name of the class' ! entity. The corresponding model should have been loaded prior to the class ! declaration, or you'll get a \exception{dynamic.EntityNotFound} exception. ! ! \item \code{verbose_metaclass}: if set, the metaclass prints on ! \code{sys.stderr} some info while building the class. ! ! \item \code{mdl_define_properties}: if set, the metaclass will also add ! properties for each attribute on relationship in the entity. ! ! \end{itemize} ! ! Last, then \module{dynamic} module also offers a method ! \method{build_with_metaclass(model, define_properties=0, verbose=0)} which you ! can use to derive the necessary package and modules from a model just like ! \code{dynamic.build()} we saw above, the ony difference being that the classes ! created at runtime use the metaclass approach. ! ! ! \subsection{Static vs. dynamic: what's best?\label{from-model-to-python-static-vs-dynamic-discussion}} ! ! The question of which option one should use is an opened question! In fact, ! it highly depends on your own preferences: some people do not want to hear of ! code generators, others keep metaclasses away from their code. Some argue ! that it is best to maintain the model only, deriving the necessary modules at ! runtime, while others prefer to statically generate the code so that it can be ! put under version control, for example, or because they want to keep an eye on ! the generated parts. ! ! This being said, here are a few comments: ! ! \begin{itemize} ! ! \item if you go for the static generation of python code, better choose the ! ``base'' scheme if you use the script \code{mdl_generate_python_code.py} (or ! a similar approach if you build the code by your own means). ! ! \item whichever method you choose, static generation, dynamic build with or ! without metaclasses, it is ``officially'' supported. Some people have ! argued that having all these possible ways available makes it difficult to ! make a choice; our position is that since we are here speaking about the ! code people write, we shouldn't force anyone to follow a given path if they ! prefer the other one. We think (and hope!) that the offered possibilities ! cover most code pratices --if you think we forgot something, we'll be happy ! to hear from you. ! ! Now if you're really looking for an advice, let's say that our personal ! preference consists in dynamically building the modules, using metaclass and ! properties. ! ! \end{itemize} ! ! %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ! \section{The framework's requirements on python code\label{framework-code-requirements}} ! ! \subsection{Package's, modules' and classes' names} ! ! When it fetches objects from the database, the framework needs to be able to ! instantiate every classes referenced in a model, so it also needs to find ! them! It does so by simply importing the class with a statement equivalent ! to: ! \begin{verbatim} ! >>> from package_name.module_name import class_name ! \end{verbatim} ! where the package's name, the module's name and the class' name are the one ! provided in the model (the package's name is in the model's properties while ! the two others are in the crresponding entity's properties, see ! sections~\ref{model-props} and \ref{entity-props}). ! ! Thus, these three properties should always be kept in sync with the ! corresponding code. ! ! \subsection{Within classes} ! ! A class corresponding to an entity must meet the following requirements: ! ! \begin{description} ! ! \item[Inheritance:] the class must include \class{CustomObject} in its ! inheritance list ! ! \item[Initializer:] the method \code{__init__()} should be designed so that it ! is possible to call it with no arguments at all: for example, if the method ! accepts arguments, each one should have default values. The reason for this ! is that the framework relies on a call to \code{__init__()} without any ! arguments, when it needs to create an instance before populating it with ! data fetched from the database. ! ! \item[Entity's name:] the class must define a method \code{entityName()} taking ! no argument: this is the way the framework currently binds an object to its ! entity. This should be changed (see TODO) ! ! \item[\method{willRead()}:] this method defined in \class{CustomObject} must ! be called prior to accessing an object's property\footnote{i.e. either an ! attribute or a relationship defined in the entity}. It informs the object ! that it is time to fetch the values stored in the database if it's not done ! yet. This is because objects can be ``{\em faults}'' (ZODB speaking, they ! are ghosts), i.e. they have been instantiated but not fully initialized yet ! (lazily initialization) ! ! \item[\method{willChange()}:] defined in \code{CustomObject} as well, this ! method should be called prior to modifying an object's property. This is ! part of the \class{Observing} interface, and its purpose is to notify the ! \class{EditingContext} that the object is about to change: the ! \class{EditingContext} needs to keep track of changes in its graph of ! objects in order to be able to save the changes (ZODB speaking, this is what ! the mix-in class \class{Persistent} does transparently for immutable ! attributes (see also: TODO)). ! Of course, \method{willChange} automatically invokes \method{willRead}. ! ! \item[Getters, setters:] although the python code derived from a model stores ! its properties in attributes beginning with an underscore (for example, ! \code{_lastName} for attribute \code{lastName}), the framework itself does ! not require this. Instead, it accesses and sets the values using the ! so-called private API of KeyValueCoding; shortly said, this means that in ! order to read a property 'name' for example, it tries to find either an ! attribute or a method called '_name', '_name()', '_getName()', 'name', ! 'getName()'. Refer to \ref{customobject-key-value-coding} for a complete ! overview. ! ! \end{description} \begin{notice}[warning] It is your responsability to call \method{willRead} and \method{willChange} when you are about to, respectively, access or ! change an object's property corresponding to a Model's Attribute or Relationship; if you do not, the \class{EditingContext}, which is responsible for examining the changes and making them persistent, is *************** *** 128,139 **** \end{notice} - \end{itemize} - \end{enumerate} - - That's it. You can then start coding your own business logic. Objects are - always inserted, deleted or updated in a database via an instance of the - \class{EditingContext} class (see chapter \ref{editing-context}, ``Working - with your objects: insert, changes, deletion''). \section{Automatic validation of referential and business-logic constraints \label {customobject-validation}} --- 392,397 ---- \end{notice} + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section{Automatic validation of referential and business-logic constraints \label {customobject-validation}} |