[DB-SIG] Re: [Python-Dev] database APIs

Luke Kenneth Casson Leighton lkcl@samba-tng.org
Wed, 5 Feb 2003 15:34:36 +0000


On Mon, Feb 03, 2003 at 04:31:28PM -0500, Kevin Jacobs wrote:
> On Mon, 3 Feb 2003, Luke Kenneth Casson Leighton wrote:
> >  my goal ultimately is to be able to write a file, maybe in
> >  XML or other suitable text file format, that specifies all the
> >  reports i want, that they are just tabular display of records,
> >  generation of an input form, or generation of create / delete
> >  input form.
> 
> We've thought about that, but had much more luck rendering reports via
> customer code with many helper objects and functions.  e.g., we have a class
> that emits HTML tables with pluggable data formatters so that our report
> code remains uncluttered.  

 i have started the process of moving away from actually having
 the HTML _in_ the python code, and using a package to template
 the HTML (apt-get install python-htmltmpl).

 what are your thoughts on such a move?

 personally i am not very impressed with python-htmltmpl it smacks
 of phpisms which always makes me shudder.

 is there anything better out there?


> We have very powerful data aggregation functions
> to slice-and-dice information in our OLAP-type reports.  We use CSS style
> sheets extensively so that our rendering code is abstract from many of the
> details of the final rendered output.  

 ah ha!  CSS style sheets.

 i always get confused by style sheets, and always mean to
 investigate them properly.

 netscape, which doesn't print properly it ignores the damn stylesheet
 (!) put me off.


> We even handle the difference between
> 'screen' and 'printed' formatting via style sheets, so that a single report
> renders very differently depending on where you send it.

 coool.

 do you have any advice on how to break up into pages because
 i need to add a "catalogue" / "brochure" and i'm running into
 lack of knowledge as to how to produce a mozilla-compatible
 HTML page that will print on the right A4 page boundaries.


> > >   2) Decode the variable names and map them to schema elements
> > >   3) Validate the variable values against the schema and business logic
> > >   4) Group variables into transactional batches and sub-batches
> > >      based on the physical schema
> > >   5) Render and execute SQL to perform updates
> > 
> > 
> >  what do you define as schema elements?
> >  i think i can work out what you mean by schema by context.
> 
> Schema elements are the relations and columns of your database backend.
> Those elements have type, unique key, foreign key, domain, and business
> logic constraints.

 okay.

 i get it.

 terminology.  okay.


 as i understand it, most of that can be specified _in_ the
 sql file, if you use a good enough sql server.

 e.g. at present i am relying on the ALTER TABLE FOREIGN KEY
 syntax, even though MySQL doesn't support that, to auto-generate
 code for JOINs and LEFT JOINs between tables.

 this Plan doesn't quite cover everything:

 - it doesn't cover reports although if MySQL supported VIEWs
   or even supported them but threw them away i WOULD look at
   specifying reports as VIEWs.

 - weird validation logic such as mentioned in one example:
   what to _do_ on errors in certain fields (red, flashing).

   i described how to deal with this in more detail yesterday,
   it can be bolted in, exactly as i believe you are already
   using.


> >  assuming that code to do all the above is auto-generated from
> >  either a SQL file (with CREATE and ALTER TABLE statements
> >  including FOREIGN KEY statements from which JOINs can be
> >  auto-generated) or from a separate IDL file, then code can
> >  be generated for the following:
> 
> Why not get them from the database at runtime?  
> It is easy to query the
> types, and only slightly harder to extract the unique and foreign keys.

 ... now why didn't i think of that? :)

 the only thing is that MySQL doesn't support foreign key
 constraints so those would be lost.


> However, your forms and reports will end up being very bland and unfriendly
> if you use a one-size-fits-all approach to generating them.

 i realise this.

 the Custom db/custom/html.py class (not the best place to
 have put this code i know) presently supports horizontal and
 vertical table and form display and entry.

 also i haven't put it in properly yet (path names hard-coded)
 but i am starting a move towards HTML-templates so you can
 specify code fragments, i mean it REALLY couldn't be simpler:

	from htmltmpl import TemplateManager, TemplateProcessor

	# Compile or load already precompiled template.
	template = TemplateManager().prepare("template.tmpl")
	tproc = TemplateProcessor()

	# Set the title.
	tproc.set("title", "Our customers")

	# Create the 'Customers' loop.
	customers = []

	# First customer.
	customer = {}
	customer["name"] = "Joe Sixpack"
	customer["city"] = "Los Angeles"
	customer["new"] = 0
	customers.append(customer)

	# Second customer.
	customer = {}
	customer["name"] = "Paul Newman"
	customer["city"] = "New York"
	customer["new"] = 1
	customers.append(customer)

	tproc.set("Customers", customers)

	# Print the processed template.
	print tproc.process(template)

 your HTML template, named (dur template.tmpl) must contain
 a tag <TMPL_VAR title> which will (dur) get substituted with
 the text.

 it's also possible to do loops, where the variable of the
 loop is expected to be None or a list of dictionaries.

 wow, hey what a coincidence, what does fetchall() return? :) :)



> I've looked at some of your code from "Custom" and it looks like a good
> start, though very verbose.  I'm sure you're developing some more powerful
> abstraction interfaces in "pysqldb", though I haven't had a chance to look.

 pysqldb contains the base classes i created that are equivalent
 to SQLbuilder and SQLObject

 it's not as complete as SQLObject although SQLObject i notice
 doesn't handle LEFT JOINs.

 pysqldb doesn't make it easy to do LEFT JOINs but it does cope:
 in any SELECT statement containing a list of tables, if any
 table starts with the text "LEFT JOIN " then it is
 handled differently from the other tables.

 tables=['people', 'addresses',
    """LEFT JOIN account_attributes ON
	 account_attributes.person_id = people.id"""]

 will generate:

 SELECT * FROM people , addresses
 LEFT JOIN account_attributes ON
 account_attributes.person_id = people.id

 rather than:
                                  v
 SELECT * FROM people , addresses ,
                                  ^ 
 LEFT JOIN account_attributes ON
 account_attributes.person_id = people.id

 where the end comma after addresses would obviously cause a
 SQL syntax error.

 stripping out any table name with LEFT JOIN in it into a
 separate list and processing them afterwards seems to
 do the trick:

 ljlist = filter(lambda x: x[:9] == 'LEFT JOIN', tablelist)
 tblist = filter(lambda x: x[:9] != 'LEFT JOIN', tablelist)

 from_stmt = ", ".join(tblist) + "\n" + "\n".join(ljlist)

 [i realise there are better ways to do the above 3 lines]


 also i notice you don't support the INSERT INTO ... SELECT
 syntax (i just had to add that into pysqldb last night due
 to some WEIRD requirements...)


> I'm still working on releasing some of my code so that I can show you how we
> handle some of these cases.  

 great!

> Unfortunately, we have some legally encumbered
> libraries that I have to remove with a chain-saw and then replace with
> clean-room code before I can do that.  Soon though...

 hey, we're not fussy.

 cvs commit it, fix it later _that's_ my motto :)


 if it helps with the decision process, usually the first
 open source project to offer needed functionality is
 the one that becomes the de-facto standard.

 kick up a political fuss about how some nerd named luke
 on the db-sig mailing lists is talking about writing
 alternate code with _his_ name all over it not your
 company's name, that should melt a few objections :)

 open source code is free advertising - there's money to
 be lost by not getting your code out first!

 l.