sqlobject-cvs Mailing List for SQLObject (Page 163)
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
You can subscribe to this list here.
| 2003 |
Jan
|
Feb
|
Mar
(9) |
Apr
(74) |
May
(29) |
Jun
(16) |
Jul
(28) |
Aug
(10) |
Sep
(57) |
Oct
(9) |
Nov
(29) |
Dec
(12) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2004 |
Jan
(7) |
Feb
(14) |
Mar
(6) |
Apr
(3) |
May
(12) |
Jun
(34) |
Jul
(9) |
Aug
(29) |
Sep
(22) |
Oct
(2) |
Nov
(15) |
Dec
(52) |
| 2005 |
Jan
(47) |
Feb
(78) |
Mar
(14) |
Apr
(35) |
May
(33) |
Jun
(16) |
Jul
(26) |
Aug
(63) |
Sep
(40) |
Oct
(96) |
Nov
(96) |
Dec
(123) |
| 2006 |
Jan
(159) |
Feb
(144) |
Mar
(64) |
Apr
(31) |
May
(88) |
Jun
(48) |
Jul
(16) |
Aug
(64) |
Sep
(87) |
Oct
(92) |
Nov
(56) |
Dec
(76) |
| 2007 |
Jan
(94) |
Feb
(103) |
Mar
(126) |
Apr
(123) |
May
(85) |
Jun
(11) |
Jul
(130) |
Aug
(47) |
Sep
(65) |
Oct
(70) |
Nov
(12) |
Dec
(11) |
| 2008 |
Jan
(30) |
Feb
(55) |
Mar
(88) |
Apr
(20) |
May
(50) |
Jun
|
Jul
(38) |
Aug
(1) |
Sep
(9) |
Oct
(5) |
Nov
(6) |
Dec
(39) |
| 2009 |
Jan
(8) |
Feb
(16) |
Mar
(3) |
Apr
(33) |
May
(44) |
Jun
(1) |
Jul
(10) |
Aug
(33) |
Sep
(74) |
Oct
(22) |
Nov
|
Dec
(15) |
| 2010 |
Jan
(28) |
Feb
(22) |
Mar
(46) |
Apr
(29) |
May
(1) |
Jun
(1) |
Jul
(27) |
Aug
(8) |
Sep
(5) |
Oct
(33) |
Nov
(24) |
Dec
(41) |
| 2011 |
Jan
(4) |
Feb
(12) |
Mar
(35) |
Apr
(29) |
May
(19) |
Jun
(16) |
Jul
(32) |
Aug
(25) |
Sep
(5) |
Oct
(11) |
Nov
(21) |
Dec
(12) |
| 2012 |
Jan
(3) |
Feb
(4) |
Mar
(20) |
Apr
(4) |
May
(25) |
Jun
(13) |
Jul
|
Aug
|
Sep
(2) |
Oct
(25) |
Nov
(9) |
Dec
(1) |
| 2013 |
Jan
(6) |
Feb
(8) |
Mar
|
Apr
(10) |
May
(31) |
Jun
(7) |
Jul
(18) |
Aug
(33) |
Sep
(4) |
Oct
(16) |
Nov
|
Dec
(27) |
| 2014 |
Jan
(2) |
Feb
|
Mar
|
Apr
(11) |
May
(39) |
Jun
(8) |
Jul
(11) |
Aug
(4) |
Sep
|
Oct
(27) |
Nov
|
Dec
(71) |
| 2015 |
Jan
(17) |
Feb
(47) |
Mar
(33) |
Apr
|
May
|
Jun
(9) |
Jul
(7) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(8) |
| 2016 |
Jan
(4) |
Feb
(4) |
Mar
|
Apr
|
May
(12) |
Jun
(7) |
Jul
(9) |
Aug
(31) |
Sep
(8) |
Oct
(3) |
Nov
(15) |
Dec
(1) |
| 2017 |
Jan
(13) |
Feb
(7) |
Mar
(14) |
Apr
(8) |
May
(10) |
Jun
(4) |
Jul
(2) |
Aug
(1) |
Sep
|
Oct
(8) |
Nov
(4) |
Dec
(5) |
| 2018 |
Jan
(2) |
Feb
(8) |
Mar
|
Apr
(4) |
May
|
Jun
(6) |
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
|
| 2019 |
Jan
(1) |
Feb
(16) |
Mar
(1) |
Apr
(3) |
May
(5) |
Jun
(1) |
Jul
|
Aug
|
Sep
(2) |
Oct
|
Nov
(1) |
Dec
(3) |
| 2020 |
Jan
|
Feb
|
Mar
|
Apr
(1) |
May
(1) |
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
(2) |
Nov
|
Dec
(2) |
| 2021 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(1) |
Nov
(1) |
Dec
|
| 2022 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
(6) |
Oct
(1) |
Nov
(1) |
Dec
(4) |
| 2023 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
(3) |
Sep
(2) |
Oct
(2) |
Nov
(4) |
Dec
|
| 2024 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
(9) |
| 2025 |
Jan
|
Feb
(4) |
Mar
(2) |
Apr
|
May
|
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(2) |
Dec
(2) |
|
From: <sub...@co...> - 2005-08-02 15:19:09
|
Author: test Date: 2005-08-02 15:19:05 +0000 (Tue, 02 Aug 2005) New Revision: 886 Modified: trunk/SQLObject/docs/web/links.txt Log: Added another project Modified: trunk/SQLObject/docs/web/links.txt =================================================================== --- trunk/SQLObject/docs/web/links.txt 2005-08-02 15:16:27 UTC (rev 885) +++ trunk/SQLObject/docs/web/links.txt 2005-08-02 15:19:05 UTC (rev 886) @@ -29,6 +29,9 @@ utilizes SQLObject and rdflib for storing transactional content from Zope. +* `Bauble <http://bauble.belizebotanic.org/>`_, a Biodiversity + Collection Manager. + Articles and Documentation -------------------------- |
|
From: <sub...@co...> - 2005-08-02 15:16:41
|
Author: test Date: 2005-08-02 15:16:27 +0000 (Tue, 02 Aug 2005) New Revision: 885 Modified: trunk/SQLObject/docs/web/links.txt Log: Added link contributed by Philippe Normand Modified: trunk/SQLObject/docs/web/links.txt =================================================================== --- trunk/SQLObject/docs/web/links.txt 2005-08-02 04:48:56 UTC (rev 884) +++ trunk/SQLObject/docs/web/links.txt 2005-08-02 15:16:27 UTC (rev 885) @@ -45,3 +45,6 @@ <http://ianbicking.org/docs/sqlobject-presentation/sqlobject-and-database-programming.html>`_; slides from a presentation on SQLObject. +* `Connecting databases to Python with SQLObject + <http://www-128.ibm.com/developerworks/library/os-pythonsqlo/index.html?ca=drs>`_; + an article at DeveloperWorks. |
|
From: <sub...@co...> - 2005-08-02 04:08:49
|
Author: ianb
Date: 2005-08-02 04:08:41 +0000 (Tue, 02 Aug 2005)
New Revision: 881
Modified:
trunk/SQLObject/docs/web/index.txt
Log:
Fixed link
Modified: trunk/SQLObject/docs/web/index.txt
===================================================================
--- trunk/SQLObject/docs/web/index.txt 2005-08-02 04:08:19 UTC (rev 880)
+++ trunk/SQLObject/docs/web/index.txt 2005-08-02 04:08:41 UTC (rev 881)
@@ -15,7 +15,7 @@
SQLObject has wiki at http://wiki.sqlobject.org
8-Mar-2004:
SQLObject `0.5.2`_ is released (significant bugfixes).
- SQLObject is now in a `Subversion repository`_.
+ SQLObject is now in a `Subversion repository <repository.html>`_.
12-Nov-2003:
SQLObject 0.5.1 is released (minor bugfixes)
1-Nov-2003:
|
|
From: <sub...@co...> - 2005-08-02 04:08:28
|
Author: ianb
Date: 2005-08-02 04:08:19 +0000 (Tue, 02 Aug 2005)
New Revision: 880
Modified:
trunk/SQLObject/sqlobject/dbconnection.py
Log:
Made the print conditional
Modified: trunk/SQLObject/sqlobject/dbconnection.py
===================================================================
--- trunk/SQLObject/sqlobject/dbconnection.py 2005-08-01 15:38:47 UTC (rev 879)
+++ trunk/SQLObject/sqlobject/dbconnection.py 2005-08-02 04:08:19 UTC (rev 880)
@@ -290,7 +290,8 @@
print '%(n)2i%(threadName)s/%(name)s%(spaces)s%(sep)s %(s)s' % locals()
def _executeRetry(self, conn, cursor, query):
- print query
+ if self.debug:
+ self.printDebug(conn, query, 'QueryR')
return cursor.execute(query)
def _query(self, conn, s):
|
|
From: <sub...@co...> - 2005-08-01 15:38:54
|
Author: test Date: 2005-08-01 15:38:47 +0000 (Mon, 01 Aug 2005) New Revision: 879 Modified: trunk/SQLObject/docs/web/links.txt trunk/SQLObject/docs/web/repository.txt Log: Fixed link; added a couple apps to links page Modified: trunk/SQLObject/docs/web/links.txt =================================================================== --- trunk/SQLObject/docs/web/links.txt 2005-08-01 07:38:06 UTC (rev 878) +++ trunk/SQLObject/docs/web/links.txt 2005-08-01 15:38:47 UTC (rev 879) @@ -21,6 +21,14 @@ * `Teeny Tiny CMS <http://footils.org/cms/show/3>`_. +* `rdflib <http://rdflib.net/>`_ contains a `SQLObject backend + <http://svn.rdflib.net/trunk/rdflib/backends/_sqlobject.py>_. + +* Enfold Systems' `EnTransit + <http://www.enfoldsystems.com/About/Talks/entransit-tutorial.pdf>`_ + utilizes SQLObject and rdflib for storing transactional content + from Zope. + Articles and Documentation -------------------------- Modified: trunk/SQLObject/docs/web/repository.txt =================================================================== --- trunk/SQLObject/docs/web/repository.txt 2005-08-01 07:38:06 UTC (rev 878) +++ trunk/SQLObject/docs/web/repository.txt 2005-08-01 15:38:47 UTC (rev 879) @@ -7,7 +7,7 @@ If you are using a command-line Subversion client, you can check it out like:: - svn co http://svn.pythonpaste.org/trunk/SQLObject + svn co http://svn.colorstudy.com/trunk/SQLObject If you are on Windows you may want to use `TortoiseSVN <http://tortoisesvn.tigris.org/>`_. |
|
From: <sub...@co...> - 2005-08-01 07:38:24
|
Author: ianb Date: 2005-08-01 07:38:06 +0000 (Mon, 01 Aug 2005) New Revision: 878 Modified: trunk/SQLObject/docs/web/links.txt Log: Added link to ttcms Modified: trunk/SQLObject/docs/web/links.txt =================================================================== --- trunk/SQLObject/docs/web/links.txt 2005-08-01 05:56:50 UTC (rev 877) +++ trunk/SQLObject/docs/web/links.txt 2005-08-01 07:38:06 UTC (rev 878) @@ -19,6 +19,8 @@ * `Ultra Gleeper <http://www.crummy.com/software/UltraGleeper/>`_, a Recommendation Engine for Web Pages. +* `Teeny Tiny CMS <http://footils.org/cms/show/3>`_. + Articles and Documentation -------------------------- |
|
From: <sub...@co...> - 2005-08-01 05:56:55
|
Author: ianb
Date: 2005-08-01 05:56:50 +0000 (Mon, 01 Aug 2005)
New Revision: 877
Modified:
trunk/SQLObject/docs/web/index.txt
Log:
Removed stuff that's better documented elsewhere now
Modified: trunk/SQLObject/docs/web/index.txt
===================================================================
--- trunk/SQLObject/docs/web/index.txt 2005-08-01 05:56:10 UTC (rev 876)
+++ trunk/SQLObject/docs/web/index.txt 2005-08-01 05:56:50 UTC (rev 877)
@@ -94,86 +94,6 @@
>>> p is p2
True
-Community
-=========
-
-`Join the mailing list`__ or visit the `sqlobject-discuss archives`_.
-
-The SQLObject mailing list is also available through Gmane_, through
-both a `web interface`_ or a `NNTP (news) interface`_. This also is a
-good source for searchable archives.
-
-.. _gmane: http://www.gmane.org
-.. _`web interface`: http://dir.gmane.org/gmane.comp.python.sqlobject
-.. _`NNTP (news) interface`: news://news.gmane.org/gmane.comp.python.sqlobject
-
-.. __: http://lists.sourceforge.net/lists/listinfo/sqlobject-discuss
-
-.. _sqlobject-discuss archives: http://sourceforge.net/mailarchive/forum.php?forum=sqlobject-discuss
-
-.. _`subversion repository`:
-
-If you are tracking changes, keeping up with the most recent changes
-is helpful. To access the Subversion repository::
-
- svn co http://svn.colorstudy.com/trunk/SQLObject
-
-For the 0.5 branch::
-
- svn co http://svn.colorstudy.com/branches/SQLObject/0.5
-
-The author sometimes hangs out in IRC on ``#webware`` on
-``irc.freenode.net``.
-
-Bugs, patches, etc
-==================
-
-Please submit bugs and patches to the `SourceForge bug tracker`_. You
-might want to send mail to the mailing list before generating patches.
-For simple bug fixes it's just as easy for me to get a line number and
-the fixed line -- submitting patches is more overhead than it's worth
-for small changes. But again, **bug reports should go to the bug
-tracker**, so that I don't lose anything.
-
-.. _SourceForge bug tracker: http://sourceforge.net/tracker/?atid=540672&group_id=74338&func=browse
-
-Download
-========
-
-SQLObject-0.6.1.tar.gz__
-
-__ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.1.tar.gz?download
-
-SQLObject-0.6.1-1.noarch.rpm__
-
-__ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.1-1.noarch.rpm?download
-
-SQLObject-0.6.1.win32.exe__
-
-__ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.1.win32.exe?download
-
-You can also use the `Subversion repository`_ to access the latest
-files.
-
-Documentation
-=============
-
-`FAQ`__
-
-__ docs/FAQ.html
-
-`New in 0.6.1`__
-
-__ docs/News.html
-
-`SQLObject documentation`__
-
-.. __: docs/SQLObject.html
-
-`SQLBuilder documentation`__
-
-.. __: docs/SQLBuilder.html
-
.. image:: http://sourceforge.net/sflogo.php?group_id=74338&type=4
:height: 37
:width: 125
|
|
From: <sub...@co...> - 2005-08-01 05:56:24
|
Author: ianb Date: 2005-08-01 05:56:10 +0000 (Mon, 01 Aug 2005) New Revision: 876 Added: trunk/SQLObject/docs/index.txt trunk/SQLObject/docs/rebuild trunk/SQLObject/docs/web/ trunk/SQLObject/docs/web/community.txt trunk/SQLObject/docs/web/default-site.css trunk/SQLObject/docs/web/index.txt trunk/SQLObject/docs/web/links.txt trunk/SQLObject/docs/web/repository.txt trunk/SQLObject/docs/web/site.js trunk/SQLObject/docs/web/style.css Log: Added files and documents for building the entire site Added: trunk/SQLObject/docs/index.txt =================================================================== --- trunk/SQLObject/docs/index.txt 2005-08-01 05:55:51 UTC (rev 875) +++ trunk/SQLObject/docs/index.txt 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,27 @@ +SQLObject Documentation +======================= + +`SQLObject Documentation <SQLObject.html>`_: + This covers the core of SQLObject in detail. + +`SQLBuilder Documentation <SQLBuilder.html>`_: + This covers the ``sqlobject.sqlbuilder`` module, which is used + to build SQL queries. + +`Inheritance <Inheritance.html>`_: + This covers ``InheritableSQLObject``, a more useful way of doing + inheritance with SQLObject classes/tables. + +`interface.py <interface.py.html>`_: + A somewhat-formal interface for SQLObject classes and related + objects. + +`News <News.html>`_: + What's new in SQLObject, by version. + +`Frequently Asked Questions <FAQ.html>`_: + Q & A + +`Developer's Guide <DeveloperGuide.html>`_: + Guidelines for contributing code to SQLObject. + Added: trunk/SQLObject/docs/rebuild =================================================================== --- trunk/SQLObject/docs/rebuild 2005-08-01 05:55:51 UTC (rev 875) +++ trunk/SQLObject/docs/rebuild 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,22 @@ +#!/bin/sh + +here=`pwd` +parent=`dirname $here` +DOCDIR=~/co/SQLObject/docs/ +echo "Adding $parent to \$PYTHONPATH" +export PYTHONPATH=$parent:$PYTHONPATH + +NORMAL="Authors DeveloperGuide FAQ Inheritance News SQLBuilder + SQLObject TODO web/index web/links web/repository web/community + index" + +for NAME in $NORMAL ; do + if [ -e "$NAME.html" -a ! "$NAME.html" -ot "$NAME.txt" ] ; then + echo "$NAME is up to date." + continue + fi + echo "Building $NAME." + rst2html.py --no-toc-backlinks -- "$NAME.txt" > "$NAME.html" +done + +source-highlight -f html interface.py Property changes on: trunk/SQLObject/docs/rebuild ___________________________________________________________________ Name: svn:executable + * Property changes on: trunk/SQLObject/docs/web ___________________________________________________________________ Name: svn:ignore + *.html Added: trunk/SQLObject/docs/web/community.txt =================================================================== --- trunk/SQLObject/docs/web/community.txt 2005-08-01 05:55:51 UTC (rev 875) +++ trunk/SQLObject/docs/web/community.txt 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,24 @@ +SQLObject Community +=================== + +SQLObject questions and discussion happens on the `sqlobject-discuss +mailing list +<http://lists.sourceforge.net/mailman/listinfo/sqlobject-discuss>`_. +You may want to view the `searchable archives +<http://pythonpaste.org/archives/list/sqlobject-discuss.en.html>`_. +`Gmane <http://www.gmane.org/>`_ also has the list in +`gmane.comp.python.sqlobject +<news://news.gmane.org/gmane.comp.python.sqlobject>`_. + +Bugs should be submitted to the `bug tracker +<http://sourceforge.net/tracker/?group_id=74338&atid=540672>`_, and +`patches to the patch tracker +<http://sourceforge.net/tracker/?group_id=74338&atid=540674>`_. + +Development takes place in the `subversion repository +<repository.html>`_. If you are interested in contributing you should +read the `Developer Guide <docs/DeveloperGuide.html>`_. + +The `Author List <docs/Authors.html>`_ tries to list all the major +contributors. + Added: trunk/SQLObject/docs/web/default-site.css =================================================================== --- trunk/SQLObject/docs/web/default-site.css 2005-08-01 05:55:51 UTC (rev 875) +++ trunk/SQLObject/docs/web/default-site.css 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,391 @@ +/* +:Author: David Goodger, Ian Bicking +:Contact: ia...@co... +:date: $Date: 2003/11/01 20:35:45 $ +:version: $Revision: 1.3 $ +:copyright: This stylesheet has been placed in the public domain. + +A modification of the default cascading style sheet (v.1.3) for the +HTML output of Docutils. +*/ + +pre em, pre i { + font-family: monospace; +} + +em, i { + /* Typically serif fonts have much nicer italics */ + font-family: Times New Roman, Times, serif; +} + +ul li { + list-style-type: circle; +} + +a.toc-backref { + text-decoration: none; + color: black; +} + +a.toc-backref:hover { + background-color: inherit; +} + +h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6:hover { + background-color: inherit; +} + +cite { + font-style: normal; + font-family: monospace; + font-weight: bold; +} + +dd { + margin-bottom: 0.5em; +} + +div.abstract { + margin: 2em 5em; +} + +div.abstract p.topic-title { + font-weight: bold; + text-align: center; +} + +div.attention, div.caution, div.danger, div.error, div.hint, +div.important, div.note, div.tip, div.warning { + background-color: #ccc; + width: 40%; + border: medium outset; + padding: 3px; + float: right +} + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title { + color: #c00; + font-weight: bold; + font-family: sans-serif; + text-align: center; + background-color: #999; + display: block; + margin: 0; +} + +div.hint p.admonition-title, div.important p.admonition-title, +div.note p.admonition-title, div.tip p.admonition-title { + font-weight: bold; + font-family: sans-serif; + text-align: center; + background-color: #999; + display: block; + margin: 0; +} + +div.dedication { + margin: 2em 5em; + text-align: center; + font-style: italic; +} + +div.dedication p.topic-title { + font-weight: bold; + font-style: normal; +} + +div.figure { + margin-left: 2em; +} + +div.footer, div.header { + font-size: smaller; +} + +div.system-messages { + margin: 5em; +} + +div.system-messages h1 { + color: red; +} + +div.system-message { + border: medium outset; + padding: 1em; +} + +div.system-message p.system-message-title { + color: red; + font-weight: bold; +} + +div.topic { + margin: 2em; +} + +h3 a.toc-backref, h4 a.toc-backref, h5 a.toc-backref, +h6 a.toc-backref { + color: #000; +} + +h1.title { + text-align: center; +} + +h2.subtitle { + text-align: center; +} + +hr { + width: 75%; +} + +ol.simple, ul.simple { + margin-bottom: 1em; +} + +ul.contents li { + list-style: none; + margin: 0; + padding: 0; +} + +ul.contents { + position: fixed; + top: 0px; + right: 0px; + background-color: #9df; + border-left: 1px solid #07f; + border-bottom: 1px solid #07f; + width: 300px; + padding: 0; + margin: 0; +} + +ul.contents a { + padding: 2px 1em 2px 1em; + display: block; + text-decoration: none; + color: #024; +} + +ul.contents a:hover { + background-color: #085; + color: #fff; +} + +ul.contents li.header { + padding: 5px 1px 3px 0; + font-weight: bold; +} + +ul.contents li ul { + margin: 0.5em; + padding: 0; +} + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +p.caption { + font-style: italic; +} + +p.credits { + font-style: italic; + font-size: smaller; +} + +p.first { + margin-top: 0; +} + +p.label { + white-space: nowrap; +} + +p.topic-title { + font-weight: bold; +} + +pre.address { + margin-bottom: 0; + margin-top: 0; + font-family: serif; + font-size: 100%; +} + +pre.line-block { + font-family: serif; + font-size: 100%; +} + +pre.literal-block, pre.doctest-block { + margin-left: 2em; + margin-right: 2em; + background-color: #eee; + border: thin black solid; + padding: 5px; +} + +span.classifier { + font-family: sans-serif; + font-style: oblique; +} + +span.classifier-delimiter { + font-family: sans-serif; + font-weight: bold; +} + +span.interpreted { + font-family: sans-serif; +} + +span.option-argument { + font-style: italic; +} + +span.pre { + white-space: pre; +} + +span.problematic { + color: red; +} + +table { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +table.citation { + border-left: solid thin gray; + padding-left: 0.5ex +} + +table.docinfo { +} + +table.footnote { + border-left: solid thin black; + padding-left: 0.5ex; +} + +td, th { + padding-left: 0.5em; + padding-right: 0.5em; + vertical-align: top; +} + +td > p:first-child, th > p:first-child { + margin-top: 0em; +} + +th.docinfo-name, th.field-name { + font-weight: bold; + text-align: left; + white-space: nowrap; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + font-size: 100%; +} + +code, tt { + color: #006; +} + +ul.auto-toc { + list-style-type: none; +} + +/***************************************** + * Doctest embedded examples + *****************************************/ + +span.doctest-url { + background-color: #eee; + border-top: 2px outset #666; + border-left: 2px outset #666; + border-right: 2px outset #666; + padding: 0.25em; +} + +div.doctest-example { + border: outset 5px #666; + background-color: #eee; + font-family: default; + padding: 0.5em; +} + +div.doctest-example h1 { + background-color: inherit; + border: none; + color: inherit; + font-family: default; +} + +div.doctest-example tt { + color: inherit; +} + +div.doctest-status { + background-color: #060; + color: #fff; +} + +span.doctest-header { + background-color: #ccc; + font-family: monospace; +} + +pre.doctest-errors { + border: none; + background-color: #333; + color: #600; +} + +div.source-code { + background-color: #000; + border: inset #999 3px; + overflow: auto; +} + +pre.source-code { + background-color: #000; + border: inset #999 3px; + overflow: auto; + font-family: monospace; + color: #fff; +} + +span.source-filename { + background-color: #000; + border-top: 2px outset #999; + border-left: 2px outset #999; + border-right: 2px outset #999; + padding: 0.25em; + color: #fff +} + Copied: trunk/SQLObject/docs/web/index.txt (from rev 870, Websites/sqlobject.org/index.txt) =================================================================== --- Websites/sqlobject.org/index.txt 2005-08-01 00:13:36 UTC (rev 870) +++ trunk/SQLObject/docs/web/index.txt 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,181 @@ ++++++++++ +SQLObject ++++++++++ + +News +==== + +26-Jan-2005: + SQLObject 0.6.1_ is released (bugfixes). +22-Sep-2004: + SQLObject 0.6_ is released (major updates). +21-Sep-2004: + SQLObject 0.5.3_ is released (bugfixes). +14-Apr-2004: + SQLObject has wiki at http://wiki.sqlobject.org +8-Mar-2004: + SQLObject `0.5.2`_ is released (significant bugfixes). + SQLObject is now in a `Subversion repository`_. +12-Nov-2003: + SQLObject 0.5.1 is released (minor bugfixes) +1-Nov-2003: + SQLObject 0.5 is released. + +.. _0.5.2: docs/News.html#sqlobject-0-5-2 +.. _0.5.3: docs/News.html#sqlobject-0-5-3 +.. _0.6: docs/News.html#sqlobject-0-6 +.. _0.6.1: docs/News.html#sqlobject-0-6-1 + +Introduction +============ + +SQLObject is an *object-relational mapper*. It allows you to +translate RDBMS table rows into Python objects, and manipulate those +objects to transparently manipulate the database. + +In using SQLObject, you will create a class definition that will +describe how the object connects to the database (in addition to any +other methods you may wish to add to the class). SQLObject will +produce the code to access the database, and update the database with +your changes. The interface to the database is meant to be +indistinguishable from other interfaces you may add to the object. + +SQLObject also includes a novel feature to generate WHERE clauses +using Python syntax and objects (intead of generating SQL using string +substitution, as is traditional). + +Example +======= + +I love examples. Examples give a feel for the aesthetic of the API, +which matters to me a great deal. This is just a snippet that creates +a simple class that wraps a table:: + + from SQLObject import * + + __connection__ = MySQLConnection( + host='localhost', db='sqlobject_test', + user='sqlobject_test', passwd='sqltest') + + class Person(SQLObject): + + firstName = StringCol(length=100) + middleInitial = StringCol(length=1, default=None) + lastName = StringCol(length=100) + +Here's the (MySQL) ``CREATE`` statement for that class:: + + CREATE TABLE person ( + id INT PRIMARY KEY AUTO_INCREMENT, + first_name VARCHAR(100) NOT NULL, + middle_initial CHAR(1), + last_name VARCHAR(100) NOT NULL + ); + +SQLObject supports most database schemas that you already have, and +can also issue the ``CREATE`` statement for you. Postgres and SQLite +are also supported (with Sybase and Firebird in the working), and +SQLObject provides an abstraction layer that helps make your +application much more portable between these databases. + +Here's how you'd use the object:: + + >>> p = Person.new(firstName="John", lastName="Doe") + >>> p + <Person 1 firstName='John' middleInitial=None lastName='Doe'> + >>> p.firstName + 'John' + >>> p.middleInitial = 'Q' + >>> p.middleInitial + 'Q' + >>> p2 = Person(1) + >>> p2 + <Person 1 firstName='John' middleInitial='Q' lastName='Doe'> + >>> p is p2 + True + +Community +========= + +`Join the mailing list`__ or visit the `sqlobject-discuss archives`_. + +The SQLObject mailing list is also available through Gmane_, through +both a `web interface`_ or a `NNTP (news) interface`_. This also is a +good source for searchable archives. + +.. _gmane: http://www.gmane.org +.. _`web interface`: http://dir.gmane.org/gmane.comp.python.sqlobject +.. _`NNTP (news) interface`: news://news.gmane.org/gmane.comp.python.sqlobject + +.. __: http://lists.sourceforge.net/lists/listinfo/sqlobject-discuss + +.. _sqlobject-discuss archives: http://sourceforge.net/mailarchive/forum.php?forum=sqlobject-discuss + +.. _`subversion repository`: + +If you are tracking changes, keeping up with the most recent changes +is helpful. To access the Subversion repository:: + + svn co http://svn.colorstudy.com/trunk/SQLObject + +For the 0.5 branch:: + + svn co http://svn.colorstudy.com/branches/SQLObject/0.5 + +The author sometimes hangs out in IRC on ``#webware`` on +``irc.freenode.net``. + +Bugs, patches, etc +================== + +Please submit bugs and patches to the `SourceForge bug tracker`_. You +might want to send mail to the mailing list before generating patches. +For simple bug fixes it's just as easy for me to get a line number and +the fixed line -- submitting patches is more overhead than it's worth +for small changes. But again, **bug reports should go to the bug +tracker**, so that I don't lose anything. + +.. _SourceForge bug tracker: http://sourceforge.net/tracker/?atid=540672&group_id=74338&func=browse + +Download +======== + +SQLObject-0.6.1.tar.gz__ + +__ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.1.tar.gz?download + +SQLObject-0.6.1-1.noarch.rpm__ + +__ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.1-1.noarch.rpm?download + +SQLObject-0.6.1.win32.exe__ + +__ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.1.win32.exe?download + +You can also use the `Subversion repository`_ to access the latest +files. + +Documentation +============= + +`FAQ`__ + +__ docs/FAQ.html + +`New in 0.6.1`__ + +__ docs/News.html + +`SQLObject documentation`__ + +.. __: docs/SQLObject.html + +`SQLBuilder documentation`__ + +.. __: docs/SQLBuilder.html + +.. image:: http://sourceforge.net/sflogo.php?group_id=74338&type=4 + :height: 37 + :width: 125 + :alt: Hosted by SourceForge + Added: trunk/SQLObject/docs/web/links.txt =================================================================== --- trunk/SQLObject/docs/web/links.txt 2005-08-01 05:55:51 UTC (rev 875) +++ trunk/SQLObject/docs/web/links.txt 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,37 @@ +SQLObject Links +=============== + +If you have a link you'd like added to this page, please `submit a bug +report with the link and title +<http://sourceforge.net/tracker/?atid=540672&group_id=74338&func=browse>`_. + +Open Source Projects +-------------------- + +* `sqlos Zope 3 SQLObject integration + <http://codespeak.net/z3/sqlos/>`_. + +* `Subway <http://subway.python-hosting.com/>`_ Web Framework; + integrates tightly with SQLObject for its models. + +* `Nabu <http://furius.ca/nabu/>`_, a publishing system. + +* `Ultra Gleeper <http://www.crummy.com/software/UltraGleeper/>`_, a + Recommendation Engine for Web Pages. + +Articles and Documentation +-------------------------- + +* `Canonical's SQLObject guide + <https://wiki.launchpad.canonical.com/SQLObjectGuide>`_. + +* `Webware/Paste + SQLObject Tutorial + <http://pythonpaste.org/docs/TodoTutorial.html>`_. + +* `SQLObject and CherryPy + <http://www.cherrypy.org/wiki/SQLObjectThreadPerConnection>`_. + +* `SQLObject & Database Programming in Python + <http://ianbicking.org/docs/sqlobject-presentation/sqlobject-and-database-programming.html>`_; + slides from a presentation on SQLObject. + Added: trunk/SQLObject/docs/web/repository.txt =================================================================== --- trunk/SQLObject/docs/web/repository.txt 2005-08-01 05:55:51 UTC (rev 875) +++ trunk/SQLObject/docs/web/repository.txt 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,14 @@ +SQLObject Repository +==================== + +SQLObject is kept in a `Subversion <http://subversion.tigris.org/>`_ +repository at http://svn.colorstudy.com/trunk/SQLObject + +If you are using a command-line Subversion client, you can check +it out like:: + + svn co http://svn.pythonpaste.org/trunk/SQLObject + +If you are on Windows you may want to use `TortoiseSVN +<http://tortoisesvn.tigris.org/>`_. + Added: trunk/SQLObject/docs/web/site.js =================================================================== --- trunk/SQLObject/docs/web/site.js 2005-08-01 05:55:51 UTC (rev 875) +++ trunk/SQLObject/docs/web/site.js 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,69 @@ +function setup_dropdowns() { + var els = document.getElementsByTagName('UL'); + for (var i = 0; i < els.length; i++) { + var el = els[i]; + if (el.className.search(/\bcontents\b/) > -1) { + enable_dropdown(el); + } + } +} + +function enable_dropdown(el) { + var title = el.getElementsByTagName('LI')[0]; + var plus_minus = document.createTextNode(' [-]'); + if (title.childNodes[0].tagName != 'A') { + anchor = document.createElement('A'); + while (title.childNodes.length) { + anchor.appendChild(title.childNodes[0]); + } + anchor.setAttribute('href', '#'); + anchor.style.padding = '1px'; + title.appendChild(anchor); + } else { + anchor = title.childNodes[0]; + } + anchor.appendChild(plus_minus); + function show_hide() { + if (el.sub_hidden) { + set_sub_li(el, ''); + anchor.removeChild(plus_minus); + plus_minus = document.createTextNode(' [-]'); + anchor.appendChild(plus_minus); + } else { + set_sub_li(el, 'none'); + anchor.removeChild(plus_minus); + plus_minus = document.createTextNode(' [+]'); + anchor.appendChild(plus_minus); + } + el.sub_hidden = ! el.sub_hidden; + return false; + } + anchor.onclick = show_hide; + show_hide(); +} + +function set_sub_li(list, display) { + var sub = list.getElementsByTagName('LI'); + for (var i = 1; i < sub.length; i++) { + sub[i].style.display = display; + } +} + +function add_onload(func) { + if (window.onload) { + var old_onload = window.onload; + function new_onload() { + old_onload(); + func(); + } + window.onload = new_onload; + } else { + window.onload = func; + } +} + +add_onload(setup_dropdowns); + + + + Added: trunk/SQLObject/docs/web/style.css =================================================================== --- trunk/SQLObject/docs/web/style.css 2005-08-01 05:55:51 UTC (rev 875) +++ trunk/SQLObject/docs/web/style.css 2005-08-01 05:56:10 UTC (rev 876) @@ -0,0 +1,95 @@ +body { + font-family: Helvetica,Arial,sans-serif; + margin: 0; + background-color: #fff; + color: #000; +} + +i, em { + font-family: Times New Roman,Times,serif; +} + +a:link { + color: #037; +} + +a:visited { + color: #204; +} + +a:hover { + background-color: #8df; +} + +div#header { + display: block; + background-color: #039; + color: #6df; + border-bottom: 3px solid #07f; + padding: 3px; + font-size: 30px; +} + +div#header h1 { + padding: 0; + margin: 0; + font-size: 1.5em; +} + +div#header a { + color: inherit; + text-decoration: none; +} + +div#nav { + float: left; + background-color: #9df; + border: 1px solid #07f; + margin-right: 1em; + border-bottom: 1px solid #07f; + width: 200px; +} + +div#nav ul { + padding: 0; + margin: 0; +} + +div#nav li { + list-style: none; + margin: 0; +} + +div#nav ul li ul li a { + padding: 2px 2em 2px 2em; + font-weight: normal; +} + +div#nav a { + display: block; + padding: 2px 1em 2px 1em; + text-decoration: none; + color: #024; + font-weight: bold; +} + +div#nav a:hover { + background-color: #085; + color: #fff; +} + +/* If I ever do menus that show the "current" page, that would be + by marking some "links" as selected */ +div#nav span.selected { + display: block; + font-weight: bold; + padding: 2px 1em 2px 1em; +} + +div#body { + padding: 1em; +} + +h1.page-title { + margin-top: 0; +} \ No newline at end of file |
|
From: <sub...@co...> - 2005-08-01 05:55:57
|
Author: ianb
Date: 2005-08-01 05:55:51 +0000 (Mon, 01 Aug 2005)
New Revision: 875
Modified:
trunk/SQLObject/docs/SQLObject.txt
Log:
Finished transition back to single document
Modified: trunk/SQLObject/docs/SQLObject.txt
===================================================================
--- trunk/SQLObject/docs/SQLObject.txt 2005-08-01 05:55:31 UTC (rev 874)
+++ trunk/SQLObject/docs/SQLObject.txt 2005-08-01 05:55:51 UTC (rev 875)
@@ -624,7 +624,7 @@
that's name starts with ``_set_``, ``_get_``, ``_del_``, or ``_doc_``,
it will be used to create a property. So, for instance, say you have
images stored under the ID of the person in the ``/var/people/images``
-directory:
+directory::
class Person(SQLObject):
# ...
@@ -683,7 +683,7 @@
To deal with this, SQLObject creates two methods for each getter and
setter, for example: ``_set_lastName`` and ``_SO_set_lastName``. So
-to intercept all changes to ``lastName``:
+to intercept all changes to ``lastName``::
class Person(SQLObject):
lastName = StringCol()
@@ -694,11 +694,31 @@
self._SO_set_lastName(value)
Or perhaps you want to constrain a phone numbers to be actual
-digits, and of proper length, and make the formatting nice:
+digits, and of proper length, and make the formatting nice::
-.. raw:: html
- :file: ../examples/snippets/phonenumber_magicoverride.html
+ import re
+ class PhoneNumber(SQLObject):
+ phoneNumber = StringCol(length=30)
+
+ _garbageCharactersRE = re.compile(r'[\-\.\(\) ]')
+ _phoneNumberRE = re.compile(r'^[0-9]+$')
+ def _set_phoneNumber(self, value):
+ value = self._garbageCharactersRE.sub('', value)
+ if not len(value) >= 10:
+ raise ValueError(
+ 'Phone numbers must be at least 10 digits long')
+ if not self._phoneNumberRE.match(value):
+ raise ValueError, 'Phone numbers can contain only digits'
+ self._SO_set_phoneNumber(value)
+
+ def _get_phoneNumber(self):
+ value = self._SO_get_phoneNumber()
+ number = '(%s) %s-%s' % (value[0:3], value[3:6], value[6:10])
+ if len(value) > 10:
+ number += ' ext.%s' % value[10:]
+ return number
+
.. note::
You should be a little cautious when modifying data that gets set
@@ -709,9 +729,6 @@
out. One advantage of methods (as opposed to attribute access) is
that the programmer is more likely to expect this disconnect.
-
-.. include:: SQLObjectCustomization.txt
-
Reference
=========
@@ -719,18 +736,752 @@
be useful for many situations. Now we'll show how to specify the
class more completely.
-.. include:: SQLObjectCol.txt
-.. include:: SQLObjectClasses.txt
-.. include:: SQLObjectRelationships.txt
-.. include:: SQLObjectTransactions.txt
-.. include:: SQLObjectGeneration.txt
-.. include:: SQLObjectDynamic.txt
-.. include:: SQLObjectLegacy.txt
-.. include:: SQLObjectDBConnection.txt
-.. include:: SQLObjectExported.txt
-.. include:: SQLObjectJoins.txt
-.. include:: SQLObjectSubqueries.txt
+Col Class: Specifying Columns
+-----------------------------
+The list of columns is a list of `Col` objects. These objects don't
+have functionality in themselves, but give you a way to specify the
+column.
+
+`dbName`:
+ This is the name of the column in the database. If you don't
+ give a name, your Pythonic name will be converted from
+ mixed-case to underscore-separated.
+`default`:
+ The default value for this column. Used when creating a new row.
+ If you give a callable object or function, the function will be
+ called, and the return value will be used. So you can give
+ ``DateTime.now`` to make the default value be the current time.
+ Or you can use ``SQLBuilder.func.NOW()`` to have the database use
+ the ``NOW()`` function internally. If you don't give a default
+ there will be an exception if this column isn't specified in the
+ call to `new`.
+`alternateID`:
+ This boolean (default False) indicates if the column can be used
+ as an ID for the field (for instance, a username), though it is
+ not a primary key. If so a class method will be added, like
+ ``byUsername`` which will return that object. Use
+ `alternateMethodName` if you don't like the ``by*`` name
+ (e.g. ``alternateMethodName="username"``).
+
+ The column should be declared ``UNIQUE`` in your table schema.
+`unique`:
+ If true, when SQLObject creates a table it will declare this
+ column to be ``UNIQUE``.
+`notNone`:
+ If true, None/``NULL`` is not allowed for this column. Useful if
+ you are using SQLObject to create your tables.
+`sqlType`:
+ The SQL type for this column (like ``INT``, ``BOOLEAN``, etc).
+ You can use classes (defined below) for this, but if those don't
+ work it's sometimes easiest just to use `sqlType`. Only necessary
+ if SQLObject is creating your tables.
+
+Subclasses of Col
+~~~~~~~~~~~~~~~~~
+
+The `ForeignKey` class should be used instead of `Col` when the column
+is a reference to another table/class. It is generally used like
+``ForeignKey('Role')``, in this instance to create a reference to a
+table `Role`. This is largely equivalent to ``Col(foreignKey='Role',
+sqlType='INT')``. Two attributes will generally be created, ``role``,
+which returns a `Role` instance, and ``roleID``, which returns an
+integer ID for the related role.
+
+There are some other subclasses of `Col`. These are used to indicate
+different types of columns, when SQLObject creates your tables.
+
+`BoolCol`:
+ Will create a ``BOOLEAN`` column in Postgres, or ``INT`` in other
+ databses. It will also convert values to ``"t"/"f"`` or ``0/1``
+ according to the database backend.
+
+`CurrencyCol`:
+ Equivalent to ``DecimalCol(size=10, precision=2)``.
+
+`DateTimeCol`:
+ A date and time (usually returned as an mxDateTime object).
+
+`DecimalCol`:
+ Base-10, precise number. Uses the keyword arguments `size` for
+ number of digits stored, and `precision` for the number of digits
+ after the decimal point.
+
+`EnumCol`:
+ One of several string values -- give the possible strings as a
+ list, with the `enumValues` keyword argument. MySQL has a native
+ ``ENUM`` type, but will work with other databases too (storage
+ just won't be as efficient).
+
+`FloatCol`:
+ Floats.
+
+`ForeignKey`:
+ A key to another table/class. Use like ``user =
+ ForeignKey('User')``.
+
+`IntCol`:
+ Integers.
+
+`StringCol`:
+ A string (character) column. Extra keywords:
+
+ `length`:
+ If given, the type will be something like ``VARCHAR(length)``.
+ If not given, then ``TEXT`` is assumed (i.e., lengthless).
+ `varchar`:
+ A boolean; if you have a length, differentiates between
+ ``CHAR`` and ``VARCHAR``, default True, i.e., use
+ ``VARCHAR``.
+
+`UnicodeCol`:
+ A subclass of `StringCol`. Also accepts a dbEncoding keyword
+ argument, which defaults to ``"UTF-8"``. Values coming in and
+ out from the database will be encoded and decoded. **Note**:
+ parameters in queries will not be automatically encoded, so if
+ you do a query matching a UnicodeCol column you must apply the
+ encoding yourself.
+
+
+SQLObject Class: Specifying Your Class
+--------------------------------------
+
+In addition to the columns, there are a number of other special
+attributes you can set in your class.
+
+`_connection`:
+ The connection object to use, from `DBConnection`. You can also
+ set the variable `__connection__` in the enclosing module and it
+ will be picked up (be sure to define `__connection__` before you
+ class). You can also pass a connection object in at instance
+ creation time, as described in transactions_.
+
+`_table`:
+ The database name of the table for this class. If you don't give
+ a name, then the standard ``MixedCase`` to ``mixed_case``
+ translation is performed.
+
+`_joins`:
+ A list of `Join` objects. This is covered below.
+
+`_cacheValues`:
+ If set to ``False`` then values for attributes from the database
+ won't be cached. So everytime you access an attribute in the
+ object the database will be queried for a value. If you want to
+ handle concurrent access to the database from multiple processes
+ then this is probably the way to do so. You should also use
+ it with transactions_ (it is not implied).
+
+.. _idName:
+
+`_idName`:
+ The name of the primary key column (default ``id``).
+
+`_style`:
+ A style object -- this object allows you to use other algorithms
+ for translating between Python attribute and class names, and the
+ database's column and table names. See `Changing the Naming
+ Style`_ for more.
+
+.. Relationships_:
+
+Relationships Between Classes/Tables
+------------------------------------
+
+You can use the `ForeignKey` to handle foreign references in a table,
+but for back references and many-to-many relationships you'll use
+joins.
+
+MultipleJoin: One-to-Many
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See `One-to-Many Relationships`_ for an example of one-to-many
+relationships.
+
+Several keyword arguments are allowed to the `MultipleJoin` constructor:
+
+.. _`Multiple Join Keywords`:
+
+`joinColumn`:
+ The column name of the key that points to this table. So, if you have
+ a table ``Product``, and another table has a column ``ProductNo`` that
+ points to this table, then you'd use ``joinColumn="ProductNo"``.
+`orderBy`:
+ Like the `orderBy`_ argument to `select()`, you can specify
+ the order that the joined objects should be returned in. `_defaultOrder`
+ will be used if not specified; ``None`` forces unordered results.
+`joinMethodName`:
+ When adding joins dynamically (using the class method `addJoin`_),
+ you can give the name of the accessor for the join. It can also be
+ created automatically, and is normally implied (i.e., ``addresses =
+ MultipleJoin(...)`` implies ``joinMethodName="addresses"``).
+
+RelatedJoin: Many-to-Many
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See `Many-to-Many Relationships`_ for examples of using many-to-many joins.
+
+`RelatedJoin` has all the keyword arguments of `MultipleJoin`__, plus:
+
+__ `Multiple Join Keywords`_
+
+`otherColumn`:
+ Similar to `joinColumn`, but referring to the joined class.
+`intermediateTable`:
+ The name of the intermediate table which references both classes.
+`addRemoveName`:
+ In the `user/role example`__, the methods `addRole(role)` and
+ `removeRole(role)` are created. The ``Role`` portion of these
+ method names can be changed by giving a string value here.
+
+__ `Many-to-Many Relationships`_
+
+An example schema that requires the use of `joinColumn`, `otherColumn`,
+and `intermediateTable`::
+
+ CREATE TABLE person (
+ id SERIAL,
+ username VARCHAR(100) NOT NULL UNIQUE
+ );
+
+ CREATE TABLE role (
+ id SERIAL,
+ name VARCHAR(50) NOT NULL UNIQUE
+ );
+
+ CREATE TABLE assigned_roles (
+ person INT NOT NULL,
+ role INT NOT NULL
+ );
+
+Then the usage in a class::
+
+ class Person(SQLObject):
+ username = StringCol(length=100, alternateID=True)
+ roles = RelatedJoin('Role', joinColumn='person', otherColumn='role',
+ intermediateTable='assigned_roles')
+ class Role(SQLObject):
+ name = StringCol(length=50, alternateID=True)
+ roles = RelatedJoin('Person', joinColumn='role', otherColumn='person',
+ intermediateTable='assigned_roles')
+
+SingleJoin: One-to-One
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Similar to `MultipleJoin`, but returns just one object, not a list.
+
+
+Transactions
+------------
+
+Transaction support in SQLObject is left to the database.
+Transactions can be used like::
+
+ conn = DBConnection.PostgresConnection('yada')
+ trans = conn.transaction()
+ p = Person.get(1, trans)
+ p.firstName = 'Bob'
+ trans.commit()
+ p.firstName = 'Billy'
+ trans.rollback()
+
+The ``trans`` object here is essentially a wrapper around a single
+database connection, and `commit` and `rollback` just pass that
+message to the `psycopg` connection.
+
+If you want to use transactions you should also turn `_cacheValues`
+off, like:
+
+ class Person(SQLObject):
+ _cacheValue = False
+ # ...
+
+Automatic Schema Generation
+---------------------------
+
+All the connections support creating and droping tables based on the
+class definition. First you have to prepare your class definition,
+which means including type information in your columns.
+
+Columns Types
+~~~~~~~~~~~~~
+
+A column type is indicated by using a subclass of `Col`:
+
+`StringCol`:
+ StringCol represents ``CHAR``, ``VARCHAR``, and ``TEXT``. The
+ `length` keyword argument indicates the ``CHAR`` or ``VARCHAR``
+ length -- if not given, then ``TEXT`` is assumed. If you use
+ ``varchar=False`` then ``CHAR`` will be used, otherwise
+ ``VARCHAR`` is the default.
+`IntCol`:
+ The ``INT`` type.
+`FloatCol`:
+ The ``FLOAT`` type.
+`DecimalCol`:
+ The ``DECIMAL`` SQL type, i.e., base 10 number. The keyword
+ arguments `size` and `precision` indicate the scope. So
+ ``DecimalCol(size=5, precision=2)`` is a number like ###.##,
+ i.e., 5 digits, two of them past the decimal point.
+`CurrencyCol`:
+ Like ``DecimalCol(size=10, precision=2)``.
+`EnumCol`:
+ A MySQL ``ENUM``, i.e., one of a finite number of strings.
+ For other databases this will be a ``VARCHAR``.
+`DateTimeCol`:
+ A moment in time. ``TIMESTAMP`` in Postgres, and ``DATETIME``
+ in MySQL. Note the names of these columns match the *Python*
+ type names, not the SQL names.
+`ForeignKey`:
+ This is a reference to another table. You typically need to
+ only give the name of the foreign class that is referenced.
+ `ForeignKey` implies an ``INT`` column.
+
+Indexes
+~~~~~~~
+
+You can also define indexes for your tables, which is only meaningful
+when creating your tables through SQLObject (SQLObject relies on the
+database to implement the indexes). You do this again with attribute
+assignment, like::
+
+ firstLastIndex = DatabaseIndex('firstName', 'lastName')
+
+This creates an index on two columns, useful if you are selecting a
+particular name. Of course, you can give a single column, and you can
+give the column object (``firstName``) instead of the string name.
+Note that if you use ``unique`` or ``alternateID`` (which implies
+``unique``) the database may make an index for you, and primary keys
+are always indexed.
+
+If you give the keyword argument ``unique`` to `DatabaseIndex` you'll
+create a unique index -- the combination of columns must be unique.
+
+You can also use dictionaries in place of the column names, to add
+extra options. E.g.::
+
+ lastNameIndex = DatabaseIndex({'expression': 'lower(last_name)'})
+
+In that case, the index will be on the lower-case version of the
+column. It seems that only PostgreSQL supports this. You can also
+do::
+
+ lastNameIndex = DatabaseIndex({'column': lastName, 'length': 10})
+
+Which asks the database to only pay attention to the first ten
+characters. Only MySQL supports this, but it is ignored in other
+databases.
+
+Creating and Dropping Tables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To create a table call `createTable`. It takes two arguments:
+
+`ifNotExists`:
+ If the table already exists, then don't try to create it. Default
+ False.
+`createJoinTables`:
+ If you used `Many-to-Many relationships`_, then the intermediate tables
+ will be created (but only for one of the two involved classes).
+ Default True.
+
+`dropTable` takes arguments `ifExists` and `dropJoinTables`,
+self-explanatory.
+
+Dynamic Classes
+===============
+
+SQLObject classes can be manipulated dynamically. This leaves open
+the possibility of constructing SQLObject classes from an XML file,
+from database introspection, or from a graphical interface.
+
+Automatic Class Generation
+---------------------------
+
+SQLObject can read the table description from the database, and fill
+in the class columns (as would normally be described in the `_columns`
+attribute). Do this like::
+
+ class Person(SQLObject):
+
+ _fromDatabase = True
+
+You can still specify columns (in `_columns`), and only missing
+columns will be added.
+
+*This is not supported in SQLite*
+
+Runtime Column Changes
+----------------------
+
+*SQLite does not support this feature*
+
+You can add and remove columns to your class at runtime. Such changes
+will effect all instances, since changes are made inplace to the
+class. There are two methods, `addColumn` and `delColumn`, both of
+which take a `Col` object (or subclass) as an argument. There's also
+an option argument `changeSchema` which, if True, will add or drop the
+column from the database (typically with an ``ALTER`` command).
+
+When adding columns, you must pass the name as part of the column
+constructor, like ``StringCol("username", length=20)``. When removing
+columns, you can either use the Col object (as found in `_columns`, or
+which you used in `addColumn`), or you can use the column name (like
+``MyClass.delColumn("username")``).
+
+.. _addJoin:
+
+You can also add Joins__, like
+``MyClass.addJoin(MultipleJoin("MyOtherClass"))``, and remove joins with
+`delJoin`. `delJoin` does not take strings, you have to get the join
+object out of the `_joins` attribute.
+
+__ Relationships_:
+
+Legacy Database Schemas
+=======================
+
+Often you will have a database that already exists, and does not use
+the naming conventions that SQLObject expects, or does not use any
+naming convention at all.
+
+SQLObject requirements
+----------------------
+
+While SQLObject tries not to make too many requirements on your
+schema, some assumptions are made. Some of these may be relaxed in
+the future.
+
+All tables that you want to turn into a class need to have an integer
+primary key. That key should be defined like:
+
+MySQL:
+ ``INT PRIMARY KEY AUTO_INCREMENT``
+Postgres:
+ ``SERIAL PRIMARY KEY``
+SQLite:
+ ``INTEGER PRIMARY KEY``
+
+SQLObject does not support non-integer keys (that may change). It
+does not support sequences in Postgres (that will change -- ``SERIAL``
+uses an implicit sequence). It does not support primary keys made up
+of multiple columns (that probably won't change). It does not
+generally support tables with primary keys with business meaning --
+i.e., primary keys are assumed to be immutable (that won't change).
+
+At the moment foreign key column names must end in ``"ID"``
+(case-insensitive). This restriction will probably be removed in the
+next release.
+
+Changing the Naming Style
+-------------------------
+
+By default names in SQLObject are expected to be mixed case in Python
+(like ``mixedCase``), and underscore-separated in SQL (like
+``mixed_case``). This applies to table and column names. The primary
+key is assumed to be simply ``id``.
+
+Other styles exist. A typical one is mixed case column names, and a
+primary key that includes the table name, like ``ProductID``. You can
+use a different "Style" object to indicate a different naming
+convention. For instance::
+
+ class Person(SQLObject):
+ _style = MixedCaseStyle(longID=True)
+
+ firstName = StringCol()
+ lastName = StringCol()
+
+If you use ``Person.createTable()``, you'll get::
+
+ CREATE TABLE Person (
+ PersonID INT PRIMARY KEY,
+ FirstName Text,
+ LastName Text
+ )
+
+The `MixedCaseStyle` object handles the initial capitalization of
+words, but otherwise leaves them be. By using ``longID=True``, we
+indicate that the primary key should look like a normal reference
+(``PersonID`` for `MixedCaseStyle`, or ``person_id`` for the default
+style).
+
+If you wish to change the style globally, assign the style to the
+connection, like::
+
+ __connection__.style = MixedCaseStyle(longID=True)
+
+Irregular Naming
+----------------
+
+While naming conventions are nice, they are not always present. You
+can control most of the names that SQLObject uses, independent of the
+Python names (so at least you don't have to propagate the
+irregularity to your brand-spanking new Python code).
+
+Here's a simple example::
+
+ class User(SQLObject):
+ _table = "user_table"
+ _idName = "userid"
+
+ username = StringCol(length=20, dbName='name')
+
+The attribute `_table` overrides the table name. `_idName` provides
+an alternative to ``id``. The ``dbName`` keyword argument gives the
+column name.
+
+Non-Integer Keys
+----------------
+
+While not strictly a legacy database issue, this fits into the
+category of "irregularities". If you use non-integer keys, all
+primary key management is up to you. You must create the table
+yourself, and when you create instances you must pass a ``id`` keyword
+argument into constructor (like ``Person(id='555-55-5555', ...)``).
+
+DBConnection: Database Connections
+==================================
+
+The `DBConnection` module currently has four external classes,
+`MySQLConnection`, `PostgresConnection`, `SQLiteConnection`,
+`SybaseConnection`, and `MaxdbConnection`.
+
+You can pass the keyword argument `debug` to any connector. If set to
+true, then any SQL sent to the database will also be printed to the
+console.
+
+MySQL
+-----
+
+`MySQLConnection` takes the keyword arguments `host`, `db`, `user`,
+and `passwd`, just like `MySQLdb.connect` does.
+
+MySQLConnection supports all the features, though MySQL only supports
+transactions_ when using the InnoDB backend, and SQLObject currently
+does not have support for explicitly defining the backend when using
+``createTable``.
+
+Postgres
+--------
+
+`PostgresConnection` takes a single connection string, like
+``"dbname=something user=some_user"``, just like `psycopg.connect`.
+You can also use the same keyword arguments as for `MySQLConnection`,
+and a dsn string will be constructed.
+
+PostgresConnection supports transactions and all other features.
+
+SQLite
+------
+
+`SQLiteConnection` takes the a single string, which is the path to the
+database file.
+
+SQLite puts all data into one file, with a journal file that is opened
+in the same directory during operation (the file is deleted when the
+program quits). SQLite does not restrict the types you can put in a
+column -- strings can go in integer columns, dates in integers, etc.
+
+SQLiteConnection doesn't support `automatic class generation`_ and
+SQLite does not support `runtime column changes`_.
+
+SQLite may have concurrency issues, depending on your usage in a
+multi-threaded environment.
+
+Firebird
+--------
+
+`FirebirdConnection` takes the arguments `host`, `db`, `user` (default
+``"sysdba"``), `passwd` (default ``"masterkey"``).
+
+Firebird supports all the features. Support is still young, so there
+may be some issues, especially with concurrent access, and especially
+using lazy selects. Try ``list(MyClass.select())`` to avoid
+concurrent cursors if you have problems (using ``list()`` will
+pre-fetch all the results of a select).
+
+Firebird support uses the kinterbasdb_ Python library.
+
+.. _kinterbasdb: http://kinterbasdb.sourceforge.net/
+
+If you are using indexes and get an error like *key size exceeds
+implementation restriction for index*, see `this page`_ to understand
+the restrictions on your indexing.
+
+.. _this page: http://www.volny.cz/iprenosil/interbase/ip_ib_indexcalculator.htm
+
+SybaseConnection
+----------------
+
+`SybaseConnection` takes the arguments `host`, `db`, `user`, and
+`passwd`. It also takes the extra boolean argument `locking` (default
+True), which is passed through when performing a connection. You may
+use a False value for `locking` if you are not using multiple threads,
+for a slight performance boost.
+
+It uses the Sybase_ module.
+
+.. _Sybase: http://www.object-craft.com.au/projects/sybase/
+
+MAX DB
+------
+
+MAX DB, also known as SAP DB, is available from a partnership of SAP
+and MySQL. It takes the typical arguments: `host`, `database`,
+`user`, `password`. It also takes the arguments `sqlmode` (default
+``"internal"``), `isolation`, and `timeout`, which are passed through
+when creating the connection to the database.
+
+It uses the sapdb_ module.
+
+.. _sapdb: http://www.sapdb.org/sapdbPython.html
+
+Exported Symbols
+================
+
+You can use ``from sqlobject import *``, though you don't have to. It
+exports a minimal number of symbols. The symbols exported:
+
+From `sqlobject.main`:
+
+* `NoDefault`
+* `SQLObject`
+* `getID`
+* `getObject`
+
+From `sqlobject.col`:
+* `Col`
+* `StringCol`
+* `IntCol`
+* `FloatCol`
+* `KeyCol`
+* `ForeignKey`
+* `EnumCol`
+* `DateTimeCol`
+* `DecimalCol`
+* `CurrencyCol`
+
+From `sqlobject.joins`:
+* `MultipleJoin`
+* `RelatedJoin`
+
+From `sqlobject.styles`:
+* `Style`
+* `MixedCaseUnderscoreStyle`
+* `DefaultStyle`
+* `MixedCaseStyle`
+
+From `sqlobject.sqlbuilder`:
+
+* `AND`
+* `OR`
+* `NOT`
+* `IN`
+* `LIKE`
+* `DESC`
+* `CONTAINSSTRING`
+* `const`
+* `func`
+
+LEFT JOIN and other JOINs
+-------------------------
+
+First look in the FAQ_, question "How can I do a LEFT JOIN?"
+
+Still here? Well. To perform a JOIN use one of the JOIN helpers from
+sqlobject.sqlbuilder. Pass an instance of the helper to .select()
+method. For example::
+
+ from sqlobject.sqlbuilder import LEFTJOINOn
+ MyTable.select(
+ join=LEFTJOINOn(Table1, Table2,
+ Table1.q.name == Table2.q.value))
+
+will generate the query::
+
+ SELECT * FROM my_table, table1
+ LEFT JOIN table2 ON table1.name = table2.value;
+
+.. _FAQ: FAQ.html#how-can-i-do-a-left-join
+
+If you want to join with the primary table - leave the first table
+None::
+
+ MyTable.select(
+ join=LEFTJOINOn(None, Table1,
+ MyTable.q.name == Table1.q.value))
+
+will generate the query::
+
+ SELECT * FROM my_table
+ LEFT JOIN table2 ON my_table.name = table1.value;
+
+The join argument for .select() can be a JOIN() or a list/tuples of
+JOIN()s.
+
+
+How can I join a table with itself?
+-----------------------------------
+
+Use Alias from sqlobject.sqlbuilder. Example::
+
+ from sqlobject.sqlbuilder import Alias
+ alias = Alias(MyTable, "my_table_alias")
+ MyTable.select(MyTable.q.name == alias.q.value)
+
+will generate the query::
+
+ SELECT * FROM my_table, my_table AS my_table_alias
+ WHERE my_table.name = my_table_alias.value;
+
+Can I use LEFTJOIN() with aliases?
+----------------------------------
+
+Sure! That's a situation the JOINs and aliases were primary developed
+for. Code::
+
+ from sqlobject.sqlbuilder import LEFTJOINOn, Alias
+ alias = Alias(OtherTable, "other_table_alias")
+ MyTable.select(MyTable.q.name == OtherTable.q.value,
+ join=LEFTJOINOn(MyTable, alias, MyTable.col1 == alias.q.col2))
+
+will result in the query::
+
+ SELECT * FROM other_table,
+ my_table LEFT JOIN other_table AS other_table_alias
+ WHERE my_table.name == other_table.value AND
+ my_table.col1 = other_table_alias.col2.
+
+Subqueries (subselects)
+-----------------------
+
+You can run queries with subqueries (subselects) on those DBMS that can do
+subqueries (MySQL supports subqueries from version 4.1).
+
+Use corresponding classess and functions from sqlbuilder::
+
+ from sqlobject.sqlbuilder import EXISTS, Select
+ select = Test1.select(EXISTS(Select(Test2.q.col2, where=(Outer(Test1).q.col1 == Test2.q.col2))))
+
+generates the query::
+
+ SELECT test1.id, test1.col1 FROM test1 WHERE
+ EXISTS (SELECT test2.col2 FROM test2 WHERE (test1.col1 = test2.col2))
+
+Note the usage of Outer - this is the helper to allow refering to a table
+in the outer query.
+
+Select() is used instead of .select() because you need to control what
+columns and in what order the inner query returns.
+
+Avalable queries are IN(), NOTIN(), EXISTS(), NOTEXISTS(), SOME(), ANY()
+and ALL(). The last 3 are used with comparison operators, like this:
+"somevalue = ANY(Select(...))".
+
+SQLBuilder
+----------
+
For more information on SQLBuilder, read the `SQLBuilder
Documentation`_.
|
|
From: <sub...@co...> - 2005-08-01 05:55:36
|
Author: ianb
Date: 2005-08-01 05:55:31 +0000 (Mon, 01 Aug 2005)
New Revision: 874
Modified:
trunk/SQLObject/docs/FAQ.txt
Log:
Removed includes from examples
Modified: trunk/SQLObject/docs/FAQ.txt
===================================================================
--- trunk/SQLObject/docs/FAQ.txt 2005-08-01 05:54:48 UTC (rev 873)
+++ trunk/SQLObject/docs/FAQ.txt 2005-08-01 05:55:31 UTC (rev 874)
@@ -23,9 +23,13 @@
Simple
~~~~~~
-.. raw:: html
- :file: ../examples/snippets/leftjoin-simple.html
+::
+ for customer in Customer.select():
+ print customer.firstName, customer.lastName
+ for contact in customer.contacts:
+ print ' ', contact.phoneNumber
+
The effect is the same as the left join -- you get all the customers,
and you get all their contacts. The problem, however, is that you
will be executing more queries -- a query for each customer to fetch
@@ -38,10 +42,15 @@
Efficient
~~~~~~~~~
-Lets say you really don't want to do all those queries. Okay, fine:
+Lets say you really don't want to do all those queries. Okay, fine::
-.. raw:: html
- :file: ../examples/snippets/leftjoin-more.html
+ custContacts = {}
+ for contact in Contact.select():
+ custContacts.setdefault(contact.customerID, []).append(contact)
+ for customer in Customer.select():
+ print customer.firstName, customer.lastName
+ for contact in custContacts.get(customer.id, []):
+ print ' ', contact.phoneNumber
This way there will only be at most two queries. It's a little more
crude, but this is an optimization, and optimizations often look less
@@ -49,10 +58,17 @@
But, say you don't want to get everyone, just some group of people
(presumably a large enough group that you still need this
-optimization):
+optimization)::
-.. raw:: html
- :file: ../examples/snippets/leftjoin-more-query.html
+ query = Customer.q.firstName.startswith('J')
+ custContacts = {}
+ for contact in Contact.select(AND(Contact.q.customerID == Customer.q.id,
+ query)):
+ custContacts.setdefault(contact.customerID, []).append(contact)
+ for customer in Customer.select(query):
+ print customer.firstName, customer.lastName
+ for contact in custContacts.get(customer.id, []):
+ print ' ', contact.phoneNumber
SQL-wise
~~~~~~~~
@@ -75,28 +91,49 @@
you can't do with SQLObject classes. However, some form of
inheritance is possible.
-One way of using this is to create local conventions. Perhaps:
+One way of using this is to create local conventions. Perhaps::
-.. raw:: html
- :file: ../examples/snippets/site-sqlobject.html
+ class SiteSQLObject(SQLObject):
+ _connection = DBConnection.MySQLConnection(user='test', db='test')
+ _style = MixedCaseStyle()
+ # And maybe you want a list of the columns, to autogenerate
+ # forms from:
+ def columns(self):
+ return [col.name for col in self._columns]
+
Since SQLObject doesn't have a firm introspection mechanism (at least
not yet) the example shows the beginnings of a bit of ad hoc
introspection (in this case exposing the ``_columns`` attribute in a
more pleasing/public interface).
However, this doesn't relate to *database* inheritance at all, since
-we didn't define any columns. What if we do?
+we didn't define any columns. What if we do? ::
-.. raw:: html
- :file: ../examples/snippets/inheritance.html
+ class Person(SQLObject):
+ firstName = StringCol()
+ lastName = StringCol()
+ class Employee(Person):
+ position = StringCol()
+
Unfortunately, the resultant schema probably doesn't look like what
-you might have wanted:
+you might have wanted::
-.. raw:: html
- :file: ../examples/snippets/inheritance-schema.html
+ CREATE TABLE person (
+ id INT PRIMARY KEY,
+ first_name TEXT,
+ last_name TEXT
+ );
+ CREATE TABLE employee (
+ id INT PRIMARY KEY
+ first_name TEXT,
+ last_name TEXT,
+ position TEXT
+ )
+
+
All the columns from ``person`` are just repeated in the ``employee``
table. What's more, an ID for a Person is distinct from an ID for an
employee, so for instance you must choose ``ForeignKey("Person")`` or
@@ -108,16 +145,38 @@
two. Of course, you can have that, just create the appropriate
classes/tables -- but it will appear as two distinct classes, and
you'd have to do something like ``Person(1).employee.position``. Of
-course, you can always create the necessary shortcuts, like:
+course, you can always create the necessary shortcuts, like::
-.. raw:: html
- :file: ../examples/snippets/inheritance-faked.html
+ class Person(SQLObject):
+ firstName = StringCol()
+ lastName = StringCol()
-It's not the most elegant setup, but it's functional and flexible.
-There are no plans for further support for inheritance (especially
-since the composition of multiple classes is usually a better solution
-anyway).
+ def _get_employee(self):
+ value = Employee.selectBy(person=self)
+ if value:
+ return value[0]
+ else:
+ raise AttributeError, '%r is not an employee' % self
+ def _get_isEmployee(self):
+ value = Employee.selectBy(person=self)
+ # turn into a bool:
+ return not not value
+ def _set_isEmployee(self, value):
+ if value:
+ # Make sure we are an employee...
+ if not self.isEmployee:
+ Empoyee.new(person=self, position=None)
+ else:
+ if self.isEmployee:
+ self.employee.destroySelf()
+ def _get_position(self):
+ return self.employee.position
+ def _set_position(self, value):
+ self.employee.position = value
+ class Employee(SQLObject):
+ person = ForeignKey('Person')
+ position = StringCol()
There is also another kind of inheritance. See Inheritance.html_
@@ -128,18 +187,44 @@
-----------------------------
A composite attribute is an attribute formed from two columns. For
-example:
+example::
-.. raw:: html
- :file: ../examples/snippets/composite-schema.html
+ CREATE TABLE invoice_item (
+ id INT PRIMARY KEY,
+ amount NUMERIC(10, 2),
+ currency CHAR(3)
+ );
Now, you'll probably want to deal with one amount/currency value,
instead of two columns. SQLObject doesn't directly support this, but
-it's easy (and encouraged) to do it on your own:
+it's easy (and encouraged) to do it on your own::
-.. raw:: html
- :file: ../examples/snippets/composite.html
+ class InvoiceItem(SQLObject):
+ amount = Currency()
+ currency = StringChar(length=3)
+ def _get_price(self):
+ return Price(self.amount, self.currency)
+ def _set_price(self, price):
+ self.amount = price.amount
+ self.currency = price.currency
+
+ class Price(object):
+ def __init__(self, amount, currency):
+ self._amount = amount
+ self._currency = currency
+
+ def _get_amount(self):
+ return self._amount
+ amount = property(_get_amount)
+
+ def _get_currency(self):
+ return self._currency
+ currency = property(_get_currency)
+
+ def __repr__(self):
+ return '<Price: %s %s>' % (self.amount, self.currency)
+
You'll note we go to some trouble to make sure that ``Price`` is an
immutable object. This is important, because if ``Price`` wasn't and
someone changed an attribute, the containing ``InvoiceItem`` instance
@@ -149,11 +234,40 @@
*Value Object*, that can be used similar to how an integer or string
is used.
-You could also use a mutable composite class:
+You could also use a mutable composite class::
-.. raw:: html
- :file: ../examples/snippets/composite-mutable.html
+ class Address(SQLObject):
+ street = StringCol()
+ city = StringCol()
+ state = StringCol(length=2)
+ latitude = FloatCol()
+ longitude = FloatCol()
+
+ def _init(self, id):
+ SQLObject._init(self, id)
+ self._coords = SOCoords(self)
+
+ def _get_coords(self):
+ return self._coords
+
+ class SOCoords(object):
+ def __init__(self, so):
+ self._so = so
+
+ def _get_latitude(self):
+ return self._so.latitude
+ def _set_latitude(self, value):
+ self._so.latitude = value
+ latitude = property(_get_latitude, set_latitude)
+
+ def _get_longitude(self):
+ return self._so.longitude
+ def _set_longitude(self, value):
+ self._so.longitude = value
+ longitude = property(_get_longitude, set_longitude)
+
+
Pretty much a proxy, really, but ``SOCoords`` could contain other
logic, could interact with non-SQLObject-based latitude/longitude
values, or could be used among several objects that have
@@ -209,19 +323,22 @@
any object in the column; the column, naturally, pickles the object upon
assignment and unpickles it upon retrieving the data from the DB.
-Another possible way to keep binary data in a database is by using encoding.
-Base 64 is a good encoding, reasonably compact but also safe. As
-an example, imagine you want to store images in the database:
+Another possible way to keep binary data in a database is by using
+encoding. Base 64 is a good encoding, reasonably compact but also
+safe. As an example, imagine you want to store images in the
+database::
-.. raw:: html
- :file: ../examples/snippets/image-binary.html
+ class Image(SQLObject):
-SQLite does not respect backslash quoting, so for instance ``\n`` is
-not interpreted as a newline. For the moment there's no resolution,
-and this will mess up your base64-encoded values. As a workaround:
+ data = StringCol()
+ height = IntCol()
+ width = IntCol()
+
+ def _set_data(self, value):
+ self._SO_set_data(value.encode('base64'))
-.. raw:: html
- :file: ../examples/snippets/image-binary-sqlite.html
+ def _get_data(self, value):
+ return self._SO_get_data().decode('base64')
Reloading Modules
|
|
From: <sub...@co...> - 2005-08-01 01:08:37
|
Author: ianb Date: 2005-08-01 00:13:36 +0000 (Mon, 01 Aug 2005) New Revision: 870 Modified: trunk/SQLObject/ trunk/SQLObject/setup.py Log: Updated setup.py metadata a bit, moved to setuptools Property changes on: trunk/SQLObject ___________________________________________________________________ Name: svn:externals + ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup Modified: trunk/SQLObject/setup.py =================================================================== --- trunk/SQLObject/setup.py 2005-07-31 18:17:57 UTC (rev 869) +++ trunk/SQLObject/setup.py 2005-08-01 00:13:36 UTC (rev 870) @@ -1,6 +1,6 @@ -from distutils.core import setup -import warnings -warnings.filterwarnings("ignore", "Unknown distribution option") +from ez_setup import use_setuptools +use_setuptools() +from setuptools import setup, find_packages subpackages = ['firebird', 'include', 'inheritance', 'mysql', 'postgres', 'sqlite', 'sybase', 'maxdb', 'util', 'manager'] @@ -13,13 +13,16 @@ DistributionMetadata.download_url = None setup(name="SQLObject", - version="0.6.1", + version="0.7b1", description="Object-Relational Manager, aka database wrapper", long_description="""\ Classes created using SQLObject wrap database rows, presenting a friendly-looking Python object instead of a database/SQL interface. Emphasizes convenience. Works with MySQL, Postgres, SQLite, Firebird. Requires Python 2.2+. + +For development see the `subversion repository +<http://svn.colorstudy.com/trunk/SQLObject#egg=SQLObject>`_ """, classifiers=["Development Status :: 4 - Beta", "Intended Audience :: Developers", @@ -35,7 +38,7 @@ license="LGPL", packages=["sqlobject"] + ['sqlobject.%s' % package for package in subpackages], scripts=["scripts/sqlobject-admin"], - download_url="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.6.1.tar.gz?download") + download_url="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.7b1.tar.gz?download") # Send announce to: # sql...@li... |
|
From: <sub...@co...> - 2005-07-31 18:18:01
|
Author: ianb Date: 2005-07-31 18:17:57 +0000 (Sun, 31 Jul 2005) New Revision: 869 Removed: trunk/SQLObject/docs/build trunk/SQLObject/examples/ Log: Removed examples (which have been merged into SQLObject.txt) Deleted: trunk/SQLObject/docs/build =================================================================== --- trunk/SQLObject/docs/build 2005-07-31 18:16:36 UTC (rev 868) +++ trunk/SQLObject/docs/build 2005-07-31 18:17:57 UTC (rev 869) @@ -1,6 +0,0 @@ -#!/bin/sh - -cd ../examples && \ -./examplestripper.py && \ -cd ../docs && \ -exec buildhtml.py --report=2 --prune=.svn --prune=europython --silent --no-toc-backlinks |
|
From: <sub...@co...> - 2005-07-31 18:16:44
|
Author: ianb Date: 2005-07-31 18:16:36 +0000 (Sun, 31 Jul 2005) New Revision: 868 Removed: trunk/SQLObject/docs/SQLObjectFuture.txt trunk/SQLObject/docs/SQLObjectLegacy.txt Log: A couple more redundant docs Deleted: trunk/SQLObject/docs/SQLObjectFuture.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectFuture.txt 2005-07-31 18:14:18 UTC (rev 867) +++ trunk/SQLObject/docs/SQLObjectFuture.txt 2005-07-31 18:16:36 UTC (rev 868) @@ -1,22 +0,0 @@ -Future -====== - -Here are some things I plan: - -* More databases supported. There has been interest and some work in - the progress for Oracle, Sybase, and MS-SQL support. -* Better transaction support -- right now you can use transactions - for the database, but the object isn't transaction-aware, so - non-database persistence won't be able to be rolled back. -* Optimistic locking and other techniques to handle concurrency. -* Profile of SQLObject performance, so that I can identify bottlenecks. -* Increase hooks with FormEncode (unreleased) validation and form - generation package, so SQLObject classes (read: schemas) can be - published for editing more directly and easily. -* Automatic joins in select queries. -* More kinds of joins, and more powerful join results (closer to how - `select` works). - -See also the `Plan for 0.6`__. - -.. __: Plan06.html Deleted: trunk/SQLObject/docs/SQLObjectLegacy.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectLegacy.txt 2005-07-31 18:14:18 UTC (rev 867) +++ trunk/SQLObject/docs/SQLObjectLegacy.txt 2005-07-31 18:16:36 UTC (rev 868) @@ -1,96 +0,0 @@ -Legacy Database Schemas -======================= - -Often you will have a database that already exists, and does not use -the naming conventions that SQLObject expects, or does not use any -naming convention at all. - -SQLObject requirements ----------------------- - -While SQLObject tries not to make too many requirements on your -schema, some assumptions are made. Some of these may be relaxed in -the future. - -All tables that you want to turn into a class need to have an integer -primary key. That key should be defined like: - -MySQL: - ``INT PRIMARY KEY AUTO_INCREMENT`` -Postgres: - ``SERIAL PRIMARY KEY`` -SQLite: - ``INTEGER PRIMARY KEY`` - -SQLObject does not support non-integer keys (that may change). It -does not support sequences in Postgres (that will change -- ``SERIAL`` -uses an implicit sequence). It does not support primary keys made up -of multiple columns (that probably won't change). It does not -generally support tables with primary keys with business meaning -- -i.e., primary keys are assumed to be immutable (that won't change). - -At the moment foreign key column names must end in ``"ID"`` -(case-insensitive). This restriction will probably be removed in the -next release. - -Changing the Naming Style -------------------------- - -By default names in SQLObject are expected to be mixed case in Python -(like ``mixedCase``), and underscore-separated in SQL (like -``mixed_case``). This applies to table and column names. The primary -key is assumed to be simply ``id``. - -Other styles exist. A typical one is mixed case column names, and a -primary key that includes the table name, like ``ProductID``. You can -use a different "Style" object to indicate a different naming -convention. For instance: - -.. raw:: html - :file: ../examples/snippets/style1.html - -If you use ``Person.createTable()``, you'll get:: - - CREATE TABLE Person ( - PersonID INT PRIMARY KEY, - FirstName Text, - LastName Text - ) - -The `MixedCaseStyle` object handles the initial capitalization of -words, but otherwise leaves them be. By using ``longID=True``, we -indicate that the primary key should look like a normal reference -(``PersonID`` for `MixedCaseStyle`, or ``person_id`` for the default -style). - -If you wish to change the style globally, assign the style to the -connection, like: - -.. raw:: html - :file: ../examples/snippets/default-style.html - -Irregular Naming ----------------- - -While naming conventions are nice, they are not always present. You -can control most of the names that SQLObject uses, independent of the -Python names (so at least you don't have to propagate the -irregularity to your brand-spanking new Python code). - -Here's a simple example: - -.. raw:: html - :file: ../examples/snippets/style-table.html - -The attribute `_table` overrides the table name. `_idName` provides -an alternative to ``id``. The ``dbName`` keyword argument gives the -column name. - -Non-Integer Keys ----------------- - -While not strictly a legacy database issue, this fits into the -category of "irregularities". If you use non-integer keys, all -primary key management is up to you. You must create the table -yourself, and when you create instances you must pass a ``id`` keyword -argument into constructor (like ``Person(id='555-55-5555', ...)``). |
|
From: <sub...@co...> - 2005-07-31 17:54:39
|
Author: ianb
Date: 2005-07-31 17:54:28 +0000 (Sun, 31 Jul 2005)
New Revision: 866
Modified:
trunk/SQLObject/sqlobject/main.py
Log:
Added method for setting the deprecation level
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-07-31 08:14:17 UTC (rev 865)
+++ trunk/SQLObject/sqlobject/main.py 2005-07-31 17:54:28 UTC (rev 866)
@@ -590,6 +590,37 @@
if warnings_level is not None and warnings_level <= level:
warnings.warn(message, DeprecationWarning, stacklevel=stacklevel)
+def setDeprecationLevel(warning=1, exception=None):
+ """
+ Set the deprecation level for SQLObject. Low levels are more
+ actively being deprecated. Any warning at a level at or below
+ ``warning`` will give a warning. Any warning at a level at or
+ below ``exception`` will give an exception. You can use a higher
+ ``exception`` level for tests to help upgrade your code. ``None``
+ for either value means never warn or raise exceptions.
+
+ The levels currently mean:
+
+ 1) Deprecated in current version (0.7). Will be removed in next
+ version (0.8)
+
+ 2) Planned to deprecate in next version, remove later.
+
+ 3) Planned to deprecate sometime, remove sometime much later ;)
+
+ As the SQLObject versions progress, the deprecation level of
+ specific features will go down, indicating the advancing nature of
+ the feature's doom. We'll try to keep features at 1 for a major
+ revision.
+
+ As time continues there may be a level 0, which will give a useful
+ error message (better than ``AttributeError``) but where the
+ feature has been fully removed.
+ """
+ warning_level = warning
+ exception_level = exception
+
+
# SQLObject is the superclass for all SQLObject classes, of
# course. All the deeper magic is done in MetaSQLObject, and
# only lesser magic is done here. All the actual work is done
@@ -1506,4 +1537,5 @@
__all__ = ['NoDefault', 'SQLObject', 'sqlmeta',
'getID', 'getObject',
- 'SQLObjectNotFound', 'sqlhub']
+ 'SQLObjectNotFound', 'sqlhub',
+ 'setDeprecationLevel']
|
|
From: <sub...@co...> - 2005-07-31 08:14:21
|
Author: phd
Date: 2005-07-31 08:14:17 +0000 (Sun, 31 Jul 2005)
New Revision: 865
Modified:
trunk/SQLObject/sqlobject/inheritance/__init__.py
trunk/SQLObject/sqlobject/inheritance/iteration.py
trunk/SQLObject/sqlobject/main.py
Log:
Fixed bugs in transition to sqlmeta.
Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-31 08:13:48 UTC (rev 864)
+++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-31 08:14:17 UTC (rev 865)
@@ -95,12 +95,12 @@
#DSM: Only do this once if possible at object creation and once for
#DSM: each new dynamic column to refresh the current class
if childUpdate or cls._parentClass:
- for col in cls._parentClass._columns:
+ for col in cls._parentClass.sqlmeta.columnList:
cname = col.name
if cname == 'childName': continue
setattr(cls, getterName(cname), eval(
'lambda self: self._parent.%s' % cname))
- if not col.kw.has_key('immutable') or not col.kw['immutable']:
+ if not col.immutable:
setattr(cls, setterName(cname), eval(
'lambda self, val: setattr(self._parent, %s, val)'
% repr(cname)))
@@ -133,7 +133,7 @@
#DSM: Only do this once if possible at object creation and once for
#DSM: each new dynamic join to refresh the current class
if childUpdate or cls._parentClass:
- for jdef in cls._parentClass._joins:
+ for jdef in cls._parentClass.sqlmeta.joins:
join = jdef.withClass(cls)
jname = join.joinMethodName
jarn = join.addRemoveName
@@ -170,11 +170,11 @@
delJoin = classmethod(delJoin)
def _notifyFinishClassCreation(cls):
- if not cls._columns:
+ if not cls.sqlmeta.columnList:
# There are no columns - call addColumn to propagate columns
# from parent classes to children
cls.addColumn(None)
- if not cls._joins:
+ if not cls.sqlmeta.joins:
# There are no joins - call addJoin to propagate joins
# from parent classes to children
cls.addJoin(None)
Modified: trunk/SQLObject/sqlobject/inheritance/iteration.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-07-31 08:13:48 UTC (rev 864)
+++ trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-07-31 08:14:17 UTC (rev 865)
@@ -27,7 +27,7 @@
self._results = []
# Find the index of the childName column
childNameIdx = None
- columns = select.sourceClass.sqlmeta._columns
+ columns = select.sourceClass.sqlmeta.columnList
for i in range(len(columns)): # enumerate() is unavailable python 2.2
if columns[i].name == "childName":
childNameIdx = i
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-07-31 08:13:48 UTC (rev 864)
+++ trunk/SQLObject/sqlobject/main.py 2005-07-31 08:14:17 UTC (rev 865)
@@ -270,7 +270,7 @@
cls.indexDefinitions = cls.indexDefinitions[:]
cls.joins = []
cls.joinDefinitions = cls.joinDefinitions[:]
-
+
setClass = classmethod(setClass)
############################################################
@@ -753,7 +753,7 @@
currentClass = cls
while currentClass._parentClass:
currentClass = currentClass._parentClass
- for column in currentClass.columnDefinitions.values():
+ for column in currentClass.sqlmeta.columnDefinitions.values():
if type(column) == col.ForeignKey: continue
setattr(cls.q, column.name,
getattr(currentClass.q, column.name))
@@ -1471,8 +1471,8 @@
def __init__(self, soObject):
self.soObject = soObject
self.protocol = 'sql'
-
+
########################################
## Utility functions (for external consumption)
########################################
|
|
From: <sub...@co...> - 2005-07-31 08:13:54
|
Author: phd
Date: 2005-07-31 08:13:48 +0000 (Sun, 31 Jul 2005)
New Revision: 864
Modified:
trunk/SQLObject/sqlobject/manager/command.py
Log:
Fixed a bug in transition from _columns to sqlmeta.columnList.
Modified: trunk/SQLObject/sqlobject/manager/command.py
===================================================================
--- trunk/SQLObject/sqlobject/manager/command.py 2005-07-31 07:36:00 UTC (rev 863)
+++ trunk/SQLObject/sqlobject/manager/command.py 2005-07-31 08:13:48 UTC (rev 864)
@@ -62,7 +62,7 @@
col = col.withClass(soClass)
existing[col.dbName] = col
missing = {}
- for col in soClass.sqlmeta._columns:
+ for col in soClass.sqlmeta.columnList:
if existing.has_key(col.dbName):
del existing[col.dbName]
else:
@@ -502,7 +502,7 @@
col = col.withClass(soClass)
existing[col.dbName] = col
missing = {}
- for col in soClass.sqlmeta._columns:
+ for col in soClass.sqlmeta.columnList:
if existing.has_key(col.dbName):
del existing[col.dbName]
else:
|
|
From: <sub...@co...> - 2005-07-31 07:36:09
|
Author: ianb
Date: 2005-07-31 07:36:00 +0000 (Sun, 31 Jul 2005)
New Revision: 863
Added:
trunk/SQLObject/sqlobject/util/backports.py
Modified:
trunk/SQLObject/
trunk/SQLObject/sqlobject/col.py
trunk/SQLObject/sqlobject/main.py
trunk/SQLObject/sqlobject/tests/test_basic.py
Log:
Make columns keep their order of creation.
Property changes on: trunk/SQLObject
___________________________________________________________________
Name: svn:ignore
- MANIFEST
data
build
dist
*.pyc
*.pyo
+ MANIFEST
data
build
dist
*.pyc
*.pyo
*.tmp
Modified: trunk/SQLObject/sqlobject/col.py
===================================================================
--- trunk/SQLObject/sqlobject/col.py 2005-07-31 07:27:37 UTC (rev 862)
+++ trunk/SQLObject/sqlobject/col.py 2005-07-31 07:36:00 UTC (rev 863)
@@ -30,6 +30,7 @@
from include import validators
from classregistry import findClass
from converters import array_type
+from util.backports import count
NoDefault = sqlbuilder.NoDefault
True, False = 1==1, 0==1
@@ -75,6 +76,8 @@
__all__.append("MXDATETIME_IMPLEMENTATION")
+creationOrder = count()
+
class SQLValidator(validators.All):
def attemptConvert(self, value, state, validate):
if validate is validators.toPython:
@@ -100,6 +103,7 @@
def __init__(self,
name,
soClass,
+ creationOrder,
dbName=None,
default=NoDefault,
foreignKey=None,
@@ -131,6 +135,7 @@
assert name, "You must provide a name for all columns"
self.columnDef = columnDef
+ self.creationOrder = creationOrder
self.immutable = immutable
@@ -361,6 +366,7 @@
self._name = name
kw['columnDef'] = self
self.kw = kw
+ self.creationOrder = creationOrder.next()
def _set_name(self, value):
assert self._name is None or self._name == value, (
@@ -374,7 +380,9 @@
name = property(_get_name, _set_name)
def withClass(self, soClass):
- return self.baseClass(soClass=soClass, name=self._name, **self.kw)
+ return self.baseClass(soClass=soClass, name=self._name,
+ creationOrder=self.creationOrder,
+ **self.kw)
def __repr__(self):
return '<%s %s %s>' % (
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-07-31 07:27:37 UTC (rev 862)
+++ trunk/SQLObject/sqlobject/main.py 2005-07-31 07:36:00 UTC (rev 863)
@@ -133,12 +133,13 @@
return depends
def _collectAttributes(cls, new_attrs, look_for_class, delete=True,
- set_name=False):
+ set_name=False, sort=False):
"""
Finds all attributes in `new_attrs` that are instances of
`look_for_class`. Returns them as a list. If `delete` is true
they are also removed from the `cls`. If `set_name` is true, then
- the ``.name`` attribute is set for any matching objects.
+ the ``.name`` attribute is set for any matching objects. If
+ `sort` is true, then they will be sorted by ``obj.creationOrder``.
"""
result = []
for attr, value in new_attrs.items():
@@ -148,6 +149,9 @@
value.name = attr
if delete:
delattr(cls, attr)
+ if sort:
+ result.sort(
+ lambda a, b: cmp(a.creationOrder, b.creationOrder))
return result
class CreateNewSQLObject:
@@ -620,7 +624,7 @@
cls._SO_setupSqlmeta(new_attrs, is_base)
implicitColumns = _collectAttributes(
- cls, new_attrs, col.Col, set_name=True)
+ cls, new_attrs, col.Col, set_name=True, sort=True)
implicitJoins = _collectAttributes(
cls, new_attrs, joins.Join, set_name=True)
implicitIndexes = _collectAttributes(
Modified: trunk/SQLObject/sqlobject/tests/test_basic.py
===================================================================
--- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-07-31 07:27:37 UTC (rev 862)
+++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-07-31 07:36:00 UTC (rev 863)
@@ -83,6 +83,8 @@
def test_foreignKey():
setupClass([TestSO4, TestSO3])
+ test3_order = [col.name for col in TestSO3.sqlmeta.columnList]
+ assert test3_order == ['name', 'otherID', 'other2ID']
tc3 = TestSO3(name='a')
assert tc3.other is None
assert tc3.other2 is None
Added: trunk/SQLObject/sqlobject/util/backports.py
===================================================================
--- trunk/SQLObject/sqlobject/util/backports.py 2005-07-31 07:27:37 UTC (rev 862)
+++ trunk/SQLObject/sqlobject/util/backports.py 2005-07-31 07:36:00 UTC (rev 863)
@@ -0,0 +1,21 @@
+from __future__ import generators
+
+__all__ = ['count', 'enumerate']
+
+# An implementation of itertools' count()
+try:
+ from itertools import count
+except ImportError:
+ def count(start=0):
+ while 1:
+ yield start
+ start += 1
+
+try:
+ enumerate
+except NameError:
+ def enumerate(lst):
+ i = 0
+ for item in lst:
+ yield i, item
+ i += 1
|
|
From: <sub...@co...> - 2005-07-31 07:27:40
|
Author: ianb
Date: 2005-07-31 07:27:37 +0000 (Sun, 31 Jul 2005)
New Revision: 862
Modified:
trunk/SQLObject/sqlobject/main.py
Log:
Added docstring
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-07-31 07:24:51 UTC (rev 861)
+++ trunk/SQLObject/sqlobject/main.py 2005-07-31 07:27:37 UTC (rev 862)
@@ -821,6 +821,11 @@
# @classmethod
def _SO_cleanDeprecatedAttrs(cls, new_attrs):
+ """
+ This removes attributes on SQLObject subclasses that have
+ been deprecated; they are moved to the sqlmeta class, and
+ a deprecation warning is given.
+ """
for attr in ['_table', '_lazyUpdate', '_style', '_idName',
'_defaultOrder', '_cacheValues', '_registry',
'_idType', '_fromDatabase']:
|
|
From: <sub...@co...> - 2005-07-31 07:25:01
|
Author: ianb
Date: 2005-07-31 07:24:51 +0000 (Sun, 31 Jul 2005)
New Revision: 861
Modified:
trunk/SQLObject/sqlobject/index.py
trunk/SQLObject/sqlobject/joins.py
trunk/SQLObject/sqlobject/main.py
Log:
Simplied a couple things in SQLObject.__classinit__
Modified: trunk/SQLObject/sqlobject/index.py
===================================================================
--- trunk/SQLObject/sqlobject/index.py 2005-07-31 06:58:07 UTC (rev 860)
+++ trunk/SQLObject/sqlobject/index.py 2005-07-31 07:24:51 UTC (rev 861)
@@ -133,6 +133,14 @@
assert self.kw.get('name') is None, "You cannot change a name after it has already been set (from %s to %s)" % (self.kw['name'], value)
self.kw['name'] = value
+ def _get_name(self):
+ return kw['name']
+
+ def _set_name(self, value):
+ self.setName(value)
+
+ name = property(_get_name, _set_name)
+
def withClass(self, soClass):
return self.baseClass(soClass=soClass, **self.kw)
Modified: trunk/SQLObject/sqlobject/joins.py
===================================================================
--- trunk/SQLObject/sqlobject/joins.py 2005-07-31 06:58:07 UTC (rev 860)
+++ trunk/SQLObject/sqlobject/joins.py 2005-07-31 07:24:51 UTC (rev 861)
@@ -30,6 +30,7 @@
return self._joinMethodName
joinMethodName = property(_get_joinMethodName, _set_joinMethodName)
+ name = joinMethodName
def withClass(self, soClass):
if self.kw.has_key('joinMethodName'):
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-07-31 06:58:07 UTC (rev 860)
+++ trunk/SQLObject/sqlobject/main.py 2005-07-31 07:24:51 UTC (rev 861)
@@ -132,6 +132,24 @@
depends.append(col)
return depends
+def _collectAttributes(cls, new_attrs, look_for_class, delete=True,
+ set_name=False):
+ """
+ Finds all attributes in `new_attrs` that are instances of
+ `look_for_class`. Returns them as a list. If `delete` is true
+ they are also removed from the `cls`. If `set_name` is true, then
+ the ``.name`` attribute is set for any matching objects.
+ """
+ result = []
+ for attr, value in new_attrs.items():
+ if isinstance(value, look_for_class):
+ result.append(value)
+ if set_name:
+ value.name = attr
+ if delete:
+ delattr(cls, attr)
+ return result
+
class CreateNewSQLObject:
"""
Dummy singleton to use in place of an ID, to signal we want
@@ -598,64 +616,18 @@
# This is true if we're initializing the SQLObject class,
# instead of a subclass:
is_base = cls.__bases__ == (object,)
- #assert cls.__name__ != 'Reparented1'
- if (not new_attrs.has_key('sqlmeta')
- and not is_base):
- # We have to create our own subclass, usually.
- # type(className, bases_tuple, attr_dict) creates a new
- # subclass:
- #cls.sqlmeta = cls.sqlmeta.clone()
- cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {})
- if not issubclass(cls.sqlmeta, sqlmeta):
- # We allow no superclass and an object superclass, instead
- # of inheriting from sqlmeta; but in that case we replace
- # the class and just move over its attributes:
- assert cls.sqlmeta.__bases__ in ((), (object,)), (
- "If you do not inherit your sqlmeta class from "
- "sqlobject.sqlmeta, it must not inherit from any other "
- "class (your sqlmeta inherits from: %s)"
- % cls.sqlmeta.__bases__)
- for base in cls.__bases__:
- superclass = getattr(base, 'sqlmeta', None)
- if superclass:
- break
- else:
- assert 0, (
- "No sqlmeta class could be found in any superclass "
- "(while fixing up sqlmeta %r inheritance)"
- % cls.sqlmeta)
- values = dict(cls.sqlmeta.__dict__)
- for key in values.keys():
- if key.startswith('__') and key.endswith('__'):
- # Magic values shouldn't be passed through:
- del values[key]
- cls.sqlmeta = type('sqlmeta', (superclass,), values)
+ cls._SO_setupSqlmeta(new_attrs, is_base)
- cls.sqlmeta.setClass(cls)
+ implicitColumns = _collectAttributes(
+ cls, new_attrs, col.Col, set_name=True)
+ implicitJoins = _collectAttributes(
+ cls, new_attrs, joins.Join, set_name=True)
+ implicitIndexes = _collectAttributes(
+ cls, new_attrs, index.DatabaseIndex, set_name=True)
- implicitColumns = []
- implicitJoins = []
- implicitIndexes = []
- for attr, value in new_attrs.items():
- if isinstance(value, col.Col):
- value.name = attr
- implicitColumns.append(value)
- delattr(cls, attr)
- continue
- if isinstance(value, joins.Join):
- value.joinMethodName = attr
- implicitJoins.append(value)
- delattr(cls, attr)
- continue
- if isinstance(value, index.DatabaseIndex):
- value.setName(attr)
- implicitIndexes.append(value)
- delattr(cls, attr)
- continue
-
if not is_base:
- cls._cleanDeprecatedAttrs(new_attrs)
+ cls._SO_cleanDeprecatedAttrs(new_attrs)
if new_attrs.has_key('_connection'):
connection = new_attrs['_connection']
@@ -803,7 +775,52 @@
addIndex = _sqlmeta_attr('addIndex', 2)
delIndex = _sqlmeta_attr('delIndex', 2)
- def _cleanDeprecatedAttrs(cls, new_attrs):
+ # @classmethod
+ def _SO_setupSqlmeta(cls, new_attrs, is_base):
+ """
+ This fixes up the sqlmeta attribute. It handles both the case
+ where no sqlmeta was given (in which we need to create another
+ subclass), or the sqlmeta given doesn't have the proper
+ inheritance. Lastly it calls sqlmeta.setClass, which handles
+ much of the setup.
+ """
+ if (not new_attrs.has_key('sqlmeta')
+ and not is_base):
+ # We have to create our own subclass, usually.
+ # type(className, bases_tuple, attr_dict) creates a new
+ # subclass:
+ cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {})
+ if not issubclass(cls.sqlmeta, sqlmeta):
+ # We allow no superclass and an object superclass, instead
+ # of inheriting from sqlmeta; but in that case we replace
+ # the class and just move over its attributes:
+ assert cls.sqlmeta.__bases__ in ((), (object,)), (
+ "If you do not inherit your sqlmeta class from "
+ "sqlobject.sqlmeta, it must not inherit from any other "
+ "class (your sqlmeta inherits from: %s)"
+ % cls.sqlmeta.__bases__)
+ for base in cls.__bases__:
+ superclass = getattr(base, 'sqlmeta', None)
+ if superclass:
+ break
+ else:
+ assert 0, (
+ "No sqlmeta class could be found in any superclass "
+ "(while fixing up sqlmeta %r inheritance)"
+ % cls.sqlmeta)
+ values = dict(cls.sqlmeta.__dict__)
+ for key in values.keys():
+ if key.startswith('__') and key.endswith('__'):
+ # Magic values shouldn't be passed through:
+ del values[key]
+ cls.sqlmeta = type('sqlmeta', (superclass,), values)
+
+ cls.sqlmeta.setClass(cls)
+
+ _SO_setupSqlmeta = classmethod(_SO_setupSqlmeta)
+
+ # @classmethod
+ def _SO_cleanDeprecatedAttrs(cls, new_attrs):
for attr in ['_table', '_lazyUpdate', '_style', '_idName',
'_defaultOrder', '_cacheValues', '_registry',
'_idType', '_fromDatabase']:
@@ -821,7 +838,7 @@
"not use it in your classes until it is fully "
"deprecated" % attr, level=3, stacklevel=5)
- _cleanDeprecatedAttrs = classmethod(_cleanDeprecatedAttrs)
+ _SO_cleanDeprecatedAttrs = classmethod(_SO_cleanDeprecatedAttrs)
def get(cls, id, connection=None, selectResults=None):
@@ -1445,9 +1462,8 @@
def __init__(self, soObject):
self.soObject = soObject
self.protocol = 'sql'
+
-
-
########################################
## Utility functions (for external consumption)
########################################
|
|
From: <sub...@co...> - 2005-07-31 06:58:24
|
Author: ianb Date: 2005-07-31 06:58:07 +0000 (Sun, 31 Jul 2005) New Revision: 860 Added: trunk/SQLObject/docs/interface.py trunk/SQLObject/docs/test.py trunk/SQLObject/sqlobject/tests/test_auto_old.py trunk/SQLObject/sqlobject/tests/test_joins_old.py Modified: trunk/SQLObject/docs/ trunk/SQLObject/docs/SQLObject.txt trunk/SQLObject/sqlobject/col.py trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/index.py trunk/SQLObject/sqlobject/joins.py trunk/SQLObject/sqlobject/main.py trunk/SQLObject/sqlobject/sqlbuilder.py trunk/SQLObject/sqlobject/sresults.py trunk/SQLObject/sqlobject/tests/dbtest.py trunk/SQLObject/sqlobject/tests/test_auto.py trunk/SQLObject/sqlobject/tests/test_enum.py trunk/SQLObject/sqlobject/tests/test_inheritance.py trunk/SQLObject/sqlobject/tests/test_joins.py trunk/SQLObject/sqlobject/tests/test_joins_conditional.py trunk/SQLObject/sqlobject/tests/test_select.py trunk/SQLObject/sqlobject/tests/test_slice.py trunk/SQLObject/sqlobject/tests/test_style.py trunk/SQLObject/sqlobject/tests/test_style_old.py trunk/SQLObject/sqlobject/tests/test_unicode.py Log: Major refactoring to move soClass._columns and company into sqlmeta. Property changes on: trunk/SQLObject/docs ___________________________________________________________________ Name: svn:ignore - *.html + *.html data.db Modified: trunk/SQLObject/docs/SQLObject.txt =================================================================== --- trunk/SQLObject/docs/SQLObject.txt 2005-07-28 08:38:30 UTC (rev 859) +++ trunk/SQLObject/docs/SQLObject.txt 2005-07-31 06:58:07 UTC (rev 860) @@ -4,22 +4,712 @@ .. contents:: Contents: -.. include:: SQLObjectIntro.txt -.. include:: SQLObjectRequirements.txt -.. include:: SQLObjectComparison.txt -.. include:: SQLObjectFuture.txt +Author, Site, and License +========================= +SQLObject is by Ian Bicking (ia...@co...). The website is +sqlobject.org__. + +__ http://sqlobject.org + +The code is licensed under the `Lesser General Public License`_ +(LGPL). + +.. _`Lesser General Public License`: http://www.gnu.org/copyleft/lesser.html + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Introduction +============ + +SQLObject is an *object-relational mapper*. It allows you to +translate RDBMS table rows into Python objects, and manipulate those +objects to transparently manipulate the database. + +In using SQLObject, you will create a class definition that will +describe how the object translates to the database table. SQLObject +will produce the code to access the database, and update the database +with your changes. The generated interface looks similar to any other +interface, and callers need not be aware of the database backend. + +SQLObject also includes a novel feature to avoid generating, +textually, your SQL queries. This also allows non-SQL databases to be +used with the same query syntax. + +Requirements +============ + +Currently SQLObject supports MySQL_, PostgreSQL_ (via ``psycopg``), +SQLite_, Firebird_, Sybase_, and `MAX DB`_ (also known as SAP DB). + +.. _PostgreSQL: http://postgresql.org +.. _SQLite: http://sqlite.org +.. _Firebird: http://firebird.sourceforge.net + +Python 2.2 or higher is required. SQLObject makes extensive use of +new-style classes. + +Compared To Other Database Wrappers +=================================== + +There are several object-relational mappers (ORM) for Python. I +honestly can't comment deeply on the quality of those packages, but +I'll try to place SQLObject in perspective. + +SQLObject uses new-style classes extensively. The resultant objects +have a new-style feel as a result -- setting attributes has side +effects (it changes the database), and defining classes has side +effects (through the use of metaclasses). Attributes are generally +exposed, not marked private, knowing that they can be made dynamic +or write-only later. + +SQLObject creates objects that feel similar to normal Python objects +(with the semantics of new-style classes). An attribute attached to a +column doesn't look different than an attribute that's attached to a +file, or an attribute that is calculated. It is a specific goal that +you be able to change the database without changing the interface, +including changing the scope of the database, making it more or less +prominent as a storage mechanism. + +This is in contrast to some ORMs that provide a dictionary-like +interface to the database (for example, PyDO_). The dictionary +interface distinguishes the row from a normal Python object. I also +don't care for the use of strings where an attribute seems more +natural -- columns are limited in number and predefined, just like +attributes. (Note: newer version of PyDO apparently allow attribute +access as well) + +.. _PyDO: http://skunkweb.sourceforge.net/pydo.html + +SQLObject is, to my knowledge, unique in using metaclasses to +facilitate this seemless integration. Some other ORMs use code +generation to create an interface, expressing the schema in a CSV or +XML file (for example, MiddleKit, part of Webware_). By using +metaclasses you are able to comfortably define your schema in the +Python source code. No code generation, no weird tools, no +compilation step. + +.. _Webware: http://webware.sourceforge.net + +SQLObject provides a strong database abstraction, allowing +cross-database compatibility (so long as you don't sidestep +SQLObject). + +SQLObject has joins, one-to-many, and many-to-many, something which +many ORMs do not have. The join system is also intended to be +extensible. + +You can map between database names and Python attribute and class +names; often these two won't match, or the database style would be +inappropriate for a Python attribute. This way your database schema +does not have to be designed with SQLObject in mind, and the resulting +classes do not have to inherit the database's naming schemes. + +Future +====== + +Here are some things I plan: + +* More databases supported. There has been interest and some work in + the progress for Oracle, Sybase, and MS-SQL support. +* Better transaction support -- right now you can use transactions + for the database, but the object isn't transaction-aware, so + non-database persistence won't be able to be rolled back. +* Optimistic locking and other techniques to handle concurrency. +* Profile of SQLObject performance, so that I can identify bottlenecks. +* Increase hooks with FormEncode (unreleased) validation and form + generation package, so SQLObject classes (read: schemas) can be + published for editing more directly and easily. +* Automatic joins in select queries. +* More kinds of joins, and more powerful join results (closer to how + `select` works). + +See also the `Plan for 0.6`__. + +.. __: Plan06.html + Using SQLObject: An Introduction ================================ -Let's start off quickly... +Let's start off quickly. We'll generally just import everything from +the ``sqlobject`` class:: -.. include:: SQLObjectDeclaration.txt -.. include:: SQLObjectUse.txt -.. include:: SQLObjectLazy.txt -.. include:: SQLObjectOneToMany.txt -.. include:: SQLObjectManyToMany.txt -.. include:: SQLObjectSelect.txt + >>> from sqlobject import * + >>> import sys, os + +Declaring the Class +------------------- + +Lets first set up a connection:: + + >>> db_filename = os.path.abspath('data.db') + >>> if os.path.exists(db_filename): + ... os.unlink(db_filename) + >>> sqlhub.processConnection = connectionForURI( + ... 'sqlite:' + db_filename) + +We'll develop a simple addressbook-like database. We could create the +tables ourselves, and just have SQLObject access those tables, but for +now we'll let SQLObject do that work. First, the class: + + >>> class Person(SQLObject): + ... + ... firstName = StringCol() + ... middleInitial = StringCol(length=1, default=None) + ... lastName = StringCol() + +Many basic table schemas won't be any more complicated than that. +`firstName`, `middleInitial`, and `lastName` are all columns in the +database. The general schema implied by this class definition is:: + + CREATE TABLE person ( + id INT PRIMARY KEY AUTO_INCREMENT, + first_name TEXT, + middle_initial CHAR(1), + last_name TEXT + ); + +This is for MySQL. The schema for other databases looks slightly +different (especially the ``id`` column). You'll notice the names +were changed from mixedCase to underscore_separated -- this is done by +the `style object`_. There are a variety of ways to handle that names +that don't fit conventions (see `Irregular Naming`_). + +.. _`style object`: `Changing the Naming Style`_ + +The tables don't yet exist. We'll let SQLObject create them:: + + >>> Person.createTable() + +We can change the type of the various columns by using something other +than `StringCol`, or using different arguments. More about this in +`Subclasses of Col`_. + +If you don't want to do table creation (you already have tables, or +you want to create the tables yourself), you can just use the vague +`Col` class. SQLObject doesn't do much type checking, allowing the +database and the adapter to handle most of the type conversion. +Databases generally do their own type coercion on inputs. + +You'll note that the ``id`` column is not given in the class +definition, it is implied. For MySQL databases it should be defined +as ``INT PRIMARY KEY AUTO_INCREMENT``, in Postgres ``SERIAL PRIMARY +KEY``, and in SQLite as ``INTEGER PRIMARY KEY``. You can `override +the name`__, but some immutable primary key must exist (`you can use +non-integer keys`_ with some extra effort). + +__ idName_ +.. _`you can use non-integer keys`: `Non-Integer Keys`_ + +Using the Class +--------------- + +Now that you have a class, how will you use it? We'll be considering +the class defined above. + +To create a new object (and row), use class instantiation, like:: + + >>> Person(firstName="John", lastName="Doe") + <Person 1 lastName='Doe' middleInitial=None firstName='John'> + +If you had left out ``firstName`` or ``lastName`` you would have +gotten an error, as no default was given for these columns +(``middleInitial`` has a default, so it will be set to ``NULL``, the +SQL equivalent of ``None``). + +.. note:: + + In SQLObject NULL/None does *not* mean default. NULL is a funny + thing; it mean very different things in different contexts and to + different people. Sometimes it means "default", sometimes "not + applicable", sometimes "unknown". If you want a default, NULL or + otherwise, you always have to be explicit in your class + definition. + + Also note that the SQLObject default isn't the same as the + database's default (SQLObject never uses the database's default). + +You can use the class method `.get()` to fetch instances that +already exist:: + + >>> Person.get(1) + <Person 1 lastName='Doe' middleInitial=None firstName='John'> + +When you create an object, it is immediately inserted into the +database. SQLObject generally uses the database as immediate storage, +unlike some other systems where you explicitly save objects into a +database. + +Here's a longer example of using the class:: + + >>> p = Person.get(1) + >>> p + <Person 1 lastName='Doe' middleInitial=None firstName='John'> + >>> p.firstName + 'John' + >>> p.middleInitial = 'Q' + >>> p.middleInitial + 'Q' + >>> p2 = Person.get(1) + >>> p2 + <Person 1 lastName='Doe' middleInitial='Q' firstName='John'> + >>> p is p2 + True + +Columns are accessed like attributes. (This uses the ``property`` +feature of Python 2.2, so that retrieving and setting these attributes +executes code). Also note that objects are unique -- there is +generally only one ``Person`` instance of a particular id in memory at +any one time. If you ask for a person by a particular ID more than +once, you'll get back the same instance. This way you can be sure of +a certain amount of consistency if you have multiple threads accessing +the same data (though of course across processes there can be no +sharing of an instance). This isn't true if you're using +transactions_, which are necessarily isolated. + +To get an idea of what's happening behind the surface, I'll give the +same actions with the SQL that is sent, along with some commentary:: + + >>> # This will make SQLObject print out the SQL it executes: + >>> Person._connection.debug = True + >>> p = Person(firstName='Bob', lastName='Hope') + 1/QueryIns: INSERT INTO person (last_name, middle_initial, first_name) VALUES ('Hope', NULL, 'Bob') + 1/COMMIT : auto + 1/QueryOne: SELECT last_name, middle_initial, first_name FROM person WHERE id = 2 + 1/COMMIT : auto + >>> p + <Person 2 lastName='Hope' middleInitial=None firstName='Bob'> + >>> p.middleInitial = 'Q' + 1/Query : UPDATE person SET middle_initial = 'Q' WHERE id = 2 + 1/COMMIT : auto + >>> p2 = Person.get(1) + >>> # Note: no database access, since we're just grabbing the same + >>> # instance we already had. + +Hopefully you see that the SQL that gets sent is pretty clear and +predictable. To view the SQL being sent, add ``?debug=t`` to your +connection URI, or set the ``debug`` attribute on the connection, and +all SQL will be printed to the console. This can be reassuring, and I +would encourage you to try it. + +.. comment: + + >>> Person._connection.debug = False + +As a small optimization, instead of assigning each attribute +individually, you can assign a number of them using the ``set`` +method, like:: + + >>> p.set(firstName='Robert', lastName='Hope Jr.') + +This will send only one ``UPDATE`` statement. You can also use `set` +with non-database properties (there's no benefit, but it helps hide +the difference between database and non-database attributes). + +Lazy Updates +------------ + +By default SQLObject sends an ``UPDATE`` to the database for every +attribute you set, or everytime you call ``.set()``. If you want to +avoid this many updates, add ``_lazyUpdate = True`` to your class +definition. Then updates will only be written to the database when +you call ``inst.syncUpdate()`` or ``obj.sync()``: ``.sync()`` also +refetches the data from the database, which ``.syncUpdate()`` does not +do. + +When enabled instances will have a property ``dirty``, which indicates +if there are pending updates. Inserts are still done immediately. + +One-to-Many Relationships +------------------------- + +A real address book should have people, but also addresses. + +First, let's define the new address table. People can have multiple +addresses, of course:: + + >>> class Address(SQLObject): + ... + ... street = StringCol() + ... city = StringCol() + ... state = StringCol(length=2) + ... zip = StringCol(length=9) + ... person = ForeignKey('Person') + >>> Address.createTable() + +Note the column ``person = ForeignKey("Person")``. This is a +reference to a `Person` object. We refer to other classes by name +(with a string) to avoid circular dependencies. In the database +there will be a ``person_id`` column, type ``INT``, which points to +the ``person`` column. + +We want an attribute that gives the addresses for a person. In a +class definition we'd do:: + + class Person(SQLObject): + ... + addresses = MultipleJoin('Address') + +But we already have the class. We can add this to the class +in-place:: + + >>> Person.addJoin(MultipleJoin('Address', + ... joinMethodName='addresses')) + +.. note:: + + In almost all cases you can modify SQLObject classes after they've + been created. Having attributes like ``*Col`` objects is + equivalent to calling certain class methods (like + ``addColumn()``). + +Now we can get the backreference with ``aPerson.addresses``, which +returns a list. An example:: + + >>> p.addresses + [] + >>> Address(street='123 W Main St', city='Smallsville', + ... state='MN', zip='55407', person=p) + <Address 1 ...> + >>> p.addresses + [<Address 1 ...>] + +Many-to-Many Relationships +-------------------------- + +For this example we will have user and role objects. The two have a +many-to-many relationship, which is represented with the +`RelatedJoin`. + + >>> class User(SQLObject): + ... + ... class sqlmeta: + ... # user is a reserved word in some databases, so we won't + ... # use that for the table name: + ... table = "user_table" + ... + ... username = StringCol(alternateID=True, length=20) + ... # We'd probably define more attributes, but we'll leave + ... # that excersize to the reader... + ... + ... roles = RelatedJoin('Role') + + >>> class Role(SQLObject): + ... + ... name = StringCol(alternateID=True, length=20) + ... + ... users = RelatedJoin('User') + + >>> User.createTable() + >>> Role.createTable() + +Note the use of the ``sqlmeta`` class. This class is used to store +different kinds of metadata (and override that metadata, like +``table``). This is new in SQLObject 0.7. + +And usage:: + + >>> bob = User(username='bob') + >>> tim = User(username='tim') + >>> jay = User(username='jay') + >>> admin = Role(name='admin') + >>> editor = Role(name='editor') + >>> bob.addRole(admin) + >>> bob.addRole(editor) + >>> tim.addRole(editor) + >>> bob.roles + [<Role 1 name='admin'>, <Role 2 name='editor'>] + >>> tim.roles + [<Role 2 name='editor'>] + >>> jay.roles + [] + >>> admin.users + [<User 1 username='bob'>] + >>> editor.users + [<User 1 username='bob'>, <User 2 username='tim'>] + +In the process an intermediate table is created, ``role_user``, which +references both of the other classes. This table is never exposed as +a class, and its rows do not have equivalent Python objects -- this +hides some of the nuisance of a many-to-many relationship. + +You may notice that the columns have the extra keyword argument +`alternateID`. If you use ``alternateID=True``, this means that the +column uniquely identifies rows -- like a username uniquely identifies +a user. This identifier is in addition to the primary key (``id``), +which is always present. + +.. note:: + + SQLObject has a strong requirement that the primary key be unique + and *immutable*. You cannot change the primary key through + SQLObject, and if you change it through another mechanism you can + cause inconsistency in any running SQLObject program (and in your + data). For this reason meaningless integer IDs are encouraged -- + something like a username that could change in the future may + uniquely identify a row, but it may be changed in the future. So + long as it is not used to reference the row, it is also *safe* to + change it in the future. + +A alternateID column creates a class method, like ``byUsername`` for a +column named ``username`` (or you can use the `alternateMethodName` +keyword argument to override this). Its use: + + >>> User.byUsername('bob') + <User 1 username='bob'> + >>> Role.byName('admin') + <Role 1 name='admin'> + +Selecting Multiple Objects +-------------------------- + +While the full power of all the kinds of joins you can do with a +relational database are not revealed in SQLObject, a simple ``SELECT`` +is available. + +``select`` is a class method, and you call it like (with the SQL +that's generated):: + + >>> Person._connection.debug = True + >>> peeps = Person.select(Person.q.firstName=="John") + >>> list(peeps) + SELECT person.id FROM person WHERE person.first_name = 'John'; + [<Person 1 lastName='Doe' middleInitial=None firstName='John'>] + +This example returns everyone with the first name John. An expression +could be more complicated as well, like:: + + >>> peeps = Person.select( + ... AND(Address.q.personID == Person.q.id, + ... Address.q.zip.startswith('504'))) + >>> list(peeps) + SELECT person.id FROM person, address WHERE (address.person_id = person.id AND address.zip LIKE '612%'); + [] + +You'll note that classes have an attribute ``q``, which gives access +to special objects for constructing query clauses. All attributes +under ``q`` refer to column names and if you construct logical +statements with these it'll give you the SQL for that statement. You +can also create your SQL more manually:: + + >>> peeps = Person.select("""address.id = person.id AND + ... address.zip LIKE '504%'""", + ... clauseTables=['address']) + +Note that you have to use ``clauseTables`` if you use tables besides +the one you are selecting from. If you use the ``q`` attributes +SQLObject will automatically figure out what extra classes you might +have used. + +You should use `MyClass.sqlrepr` to quote any values you use if you +create SQL manually (quoting is automatic if you use ``q``). + +.. _orderBy: + +You can use the keyword arguments `orderBy` to create ``ORDER BY`` in +the select statements: `orderBy` takes a string, which should be the +*database* name of the column, or a column in the form +``Person.q.firstName``. You can use ``"-colname"`` to specify +descending order, or call ``MyClass.select().reversed()``. + +You can use the special class variable `_defaultOrder` to give a +default ordering for all selects. To get an unordered result when +`_defaultOrder` is used, use ``orderBy=None``. + +Select results are generators, which are lazily evaluated. So the SQL +is only executed when you iterate over the select results, or if you +use ``list()`` to force the result to be executed. When you iterate +over the select results, rows are fetched one at a time. This way you +can iterate over large results without keeping the entire result set +in memory. You can also do things like ``.reversed()`` without +fetching and reversing the entire result -- instead, SQLObject can +change the SQL that is sent so you get equivalent results. + +You can also slice select results. This modifies the SQL query, so +``peeps[:10]`` will result in ``LIMIT 10`` being added to the end of +the SQL query. If the slice cannot be performed in the SQL (e.g., +peeps[:-10]), then the select is executed, and the slice is performed +on the list of results. This will generally only happen when you use +negative indexes. + +In certain cases, you may get a select result with an object in it +more than once, e.g., in some joins. If you don't want this, you can +add the keyword argument ``MyClass.select(..., distinct=True)``, which +results in a ``SELECT DISTINCT`` call. + +You can get the length of the result without fetching all the results +by calling ``count`` on the result object, like +``MyClass.select().count()``. A ``COUNT(*)`` query is used -- the +actual objects are not fetched from the database. Together with +slicing, this makes batched queries easy to write: + + start = 20 + size = 10 + query = Table.select() + results = query[start:start+size] + total = query.count() + print "Showing page %i of %i" % (start/size + 1, total/size + 1) + +.. note:: + + There are several factors when considering the efficiency of this + kind of batching, and it depends very much how the batching is + being used. Consider a web application where you are showing an + average of 100 results, 10 at a time, and the results are ordered + by the date they were added to the database. While slicing will + keep the database from returning all the results (and so save some + communication time), the database will still have to scan through + the entire result set to sort the items (so it knows which the + first ten are), and depending on your query may need to scan + through the entire table (depending on your use of indexes). + Indexes are probably the most important way to improve importance + in a case like this, and you may find caching to be more effective + than slicing. + + In this case, caching would mean retrieving the *complete* results. + You can use ``list(MyClass.select(...))`` to do this. You can save + these results for some limited period of time, as the user looks + through the results page by page. This means the first page in a + search result will be slightly more expensive, but all later pages + will be very cheap. + +For more information on the where clause in the queries, see the +`SQLBuilder documentation`_. + +.. _`SQLBuilder documentation`: SQLBuilder.html + +Select-By Method +~~~~~~~~~~~~~~~~ + +An alternative to ``.select`` is ``.selectBy``. It works like: + + >>> peeps = Person.selectBy(firstName="John", lastName="Doe") + +Each keyword argument is a column, and all the keyword arguments +are ANDed together. The return value is a `SelectResult`, so you +can slice it, count it, order it, etc. + +Customizing the Objects +----------------------- + +While we haven't done so in the examples, you can include your own +methods in the class definition. Writing your own methods should be +obvious enough (just do so like in any other class), but there are +some other details to be aware of. + +Initializing the Objects +~~~~~~~~~~~~~~~~~~~~~~~~ + +There are two ways SQLObject instances can come into existance: they +can be fetched from the database, or they can be inserted into the +database. In both cases a new Python object is created. This makes +the role of `__init__` a little confusing. + +In general, you should not touch `__init__`. Instead use the `_init` +method, which is called after an object is fetched or inserted. This +method has the signature ``_init(self, id, connection=None, +selectResults=None)``, though you may just want to use ``_init(self, +*args, **kw)``. **Note:** don't forget to call +``SQLObject._init(self, *args, **kw)`` if you override the method! + +Adding Magic Attributes (properties) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use all the normal techniques for defining methods in this +new-style class, including `classmethod`, `staticmethod`, and +`property`, but you can also use a shortcut. If you have a method +that's name starts with ``_set_``, ``_get_``, ``_del_``, or ``_doc_``, +it will be used to create a property. So, for instance, say you have +images stored under the ID of the person in the ``/var/people/images`` +directory: + + class Person(SQLObject): + # ... + + def imageFilename(self): + return 'images/person-%s.jpg' % self.id + + def _get_image(self): + if not os.path.exists(self.imageFilename()): + return None + f = open(self.imageFilename()) + v = f.read() + f.close() + return v + + def _set_image(self, value): + # assume we get a string for the image + f = open(self.imageFilename(), 'w') + f.write(value) + f.close() + + def _del_image(self, value): + # I usually wouldn't include a method like this, but for + # instructional purposes... + os.unlink(self.imageFilename()) + + +Later, you can use the ``.image`` property just like an attribute, and +the changes will be reflected in the filesystem by calling these +methods. This is a good technique for information that is better to +keep in files as opposed to the database (such as large, opaque data +like images). + +You can also pass an ``image`` keyword argument to the constructor +or the `set` method, like ``Person(..., image=imageText)``. + +All of the methods (``_get_``, ``_set_``, etc) are optional -- you can +use any one of them without using the others. So you could define +just a ``_get_attr`` method so that ``attr`` was read-only. + +Overriding Column Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's a little more complicated if you want to override the behavior of +an database column attribute. For instance, imagine there's special +code you want to run whenever someone's name changes. In many systems +you'd do some custom code, then call the superclass's code. But the +superclass (``SQLObject``) doesn't know anything about the column in +your subclass. It's even worse with properties. + +SQLObject creates methods like ``_set_lastName`` for each of your +columns, but again you can't use this, since there's no superclass to +reference (and you can't write ``SQLObject._set_lastName(...)``, +because the SQLObject class doesn't know about your class's columns). +You want to override that ``_set_lastName`` method yourself. + +To deal with this, SQLObject creates two methods for each getter and +setter, for example: ``_set_lastName`` and ``_SO_set_lastName``. So +to intercept all changes to ``lastName``: + + class Person(SQLObject): + lastName = StringCol() + firstName = StringCol() + + def _set_lastName(self, value): + self.notifyLastNameChange(value) + self._SO_set_lastName(value) + +Or perhaps you want to constrain a phone numbers to be actual +digits, and of proper length, and make the formatting nice: + +.. raw:: html + :file: ../examples/snippets/phonenumber_magicoverride.html + +.. note:: + + You should be a little cautious when modifying data that gets set + in an attribute. Generally someone using your class will expect + that the value they set the attribute to will be the same value + they get back. In this example we removed some of the characters + before putting it in the database, and reformatted it on the way + out. One advantage of methods (as opposed to attribute access) is + that the programmer is more likely to expect this disconnect. + + .. include:: SQLObjectCustomization.txt Reference Added: trunk/SQLObject/docs/interface.py =================================================================== --- trunk/SQLObject/docs/interface.py 2005-07-28 08:38:30 UTC (rev 859) +++ trunk/SQLObject/docs/interface.py 2005-07-31 06:58:07 UTC (rev 860) @@ -0,0 +1,355 @@ +""" +This is a not-very-formal outline of the interface that SQLObject +provides. While its in the form of a formal interface, it doesn't +use any interface system. +""" + +class Interface(object): + pass + +class ISQLObject(Interface): + + sqlmeta = """ + A class or instance representing internal state and methods for + introspecting this class. + + ``MyClass.sqlmeta`` is a class, and ``myInstance.sqlmeta`` is an + instance of this class. So every instance gets its own instance + of the metadata. + + This object follows the ``Isqlmeta`` interface. + """ + + # classmethod + def get(id, connection=None): + """ + Returns the object with the given `id`. If `connection` is + given, then get the object from the given connection + (otherwise use the default or configured connection) + + It raises ``SQLObjectNotFound`` if no row exists with that ID. + """ + + # classmethod + def selectBy(connection=None, **attrs): + """ + Performs a ``SELECT`` in the given `connection` (or default + connection). + + Each of the keyword arguments should be a column, and the + equality comparisons will be ``ANDed`` together to produce the + result. + """ + + # classmethod + def dropTable(ifExists=False, dropJoinTables=True, cascade=False, + connection=None): + """ + Drops this table from the database. If ``ifExists`` is true, + then it is not an error if the table doesn't exist. + + Join tables (mapping tables for many-to-many joins) are + dropped if this class comes alphabetically before the other + join class, and if ``dropJoinTables`` is true. + + ``cascade`` is passed to the connection, and if true tries to + drop tables that depend on this table. + """ + + # classmethod + def createTable(ifNotExists=False, createJoinTables=True, + createIndexes=True, connection=None): + """ + Creates the table. If ``ifNotExists`` is true, then it is not + an error if the table already exists. + + Join tables (mapping tables for many-to-many joins) are + created if this class comes alphabetically before the other + join class, and if ``createJoinTables`` is true. + + If ``createIndexes`` is true, indexes are also created. + """ + + # classmethod + def createTableSQL(createJoinTables=True, connection=None, + createIndexes=True): + """ + Returns the SQL that would be sent with the analogous call + to ``Class.createTable(...)`` + """ + + def sync(): + """ + This will refetch the data from the database, putting it in + sync with the database (in case another process has modified + the database since this object was fetched). It will raise + ``SQLObjectNotFound`` if the row has been deleted. + + This will call ``self.syncUpdate()`` if ``lazyUpdates`` are + on. + """ + + def syncUpdate(): + """ + If ``.sqlmeta.lazyUpdates`` is true, then this method must be + called to push accumulated updates to the server. + """ + + def expire(): + """ + This will remove all the column information from the object. + The next time this information is used, a ``SELECT`` will be + made to re-fetch the data. This is like a lazy ``.sync()``. + """ + + def set(**attrs): + """ + This sets many column attributes at once. ``obj.set(a=1, + b=2)`` is equivalent to ``obj.a=1; obj.b=2``, except that it + will be grouped into one ``UPDATE`` + """ + + def destroySelf(): + """ + Deletes this row from the database. This is called on + instances, not on the class. The object still persists, + because objects cannot be deleted from the Python process + (they can only be forgotten about, at which time they are + garbage collected). The object becomes obsolete, and further + activity on it will raise errors. + """ + + def sqlrepr(obj, connection=None): + """ + Returns the SQL representation of the given object, for the + configured database connection. + """ + +class Isqlmeta(Interface): + + table = """ + The name of the table in the database. This is derived from + ``style`` and the class name if no explicit name is given. + """ + + idName = """ + The name of the primary key column in the database. This is + derived from ``style`` if no explicit name is given. + """ + + idType = """ + A function that coerces/normalizes IDs when setting IDs. This + is ``int`` by default (all IDs are normalized to integers). + """ + + style = """ + An instance of the ``IStyle`` interface. This maps Python + identifiers to database names. + """ + + lazyUpdate = """ + A boolean (default false). If true, then setting attributes on + instances (or using ``inst.set(...)`` will not send ``UPDATE`` + queries immediately (you must call ``inst.syncUpdates()`` or + ``inst.sync()`` first). + """ + + defaultOrder = """ + When selecting objects and not giving an explicit order, this + attribute indicates the default ordering. It is like this value + is passed to ``.select()`` and related methods; see those method's + documentation for details. + """ + + cacheValues = """ + A boolean (default true). If true, then the values in the row are + cached as long as the instance is kept (and ``inst.expire()`` is + not called). If false, then every attribute access causes a + ``SELECT`` (which is absurdly inefficient). + """ + + registry = """ + Because SQLObject uses strings to relate classes, and these + strings do not respect module names, name clashes will occur if + you put different systems together. This string value serves + as a namespace for classes. + """ + + fromDatabase = """ + A boolean (default false). If true, then on class creation the + database will be queried for the table's columns, and any missing + columns (possible all columns) will be added automatically. + """ + + columns = """ + A dictionary of ``{columnName: anSOColInstance}``. You can get + information on the columns via this read-only attribute. + """ + + columnList = """ + A list of the values in ``columns``. Sometimes a stable, ordered + version of the columns is necessary; this is used for that. + """ + + columnDefinitions = """ + A dictionary like ``columns``, but contains the original column + definitions (which are not class-specific, and have no logic). + """ + + joins = """ + A list of all the Join objects for this class. + """ + + indexes = """ + A list of all the indexes for this class. + """ + + # Instance attributes + + expired = """ + A boolean. If true, then the next time this object's column + attributes are accessed a query will be run. + """ + + # Methods + + def addColumn(columnDef, changeSchema=False, connection=None): + """ + Adds the described column to the table. If ``changeSchema`` + is true, then an ``ALTER TABLE`` query is called to change the + database. + + Attributes given in the body of the SQLObject subclass are + collected and become calls to this method. + """ + + def delColumn(column, changeSchema=False, connection=None): + """ + Removes the given column (either the definition from + ``columnDefinition`` or the SOCol object from ``columns``). + + If ``changeSchema`` is true, then an ``ALTER TABLE`` query is + made. + """ + + def addColumnsFromDatabase(connection=None): + """ + Adds all the columns from the database that are not already + defined. If the ``fromDatabase`` attribute is true, then + this is called on class instantiation. + """ + + def addJoin(joinDef): + """ + Adds a join to the class. + """ + + def delJoin(joinDef): + """ + Removes a join from the class. + """ + + def addIndex(indexDef): + """ + Adds the index to the class. + """ + + +class ICol(Interface): + + def __init__(name=None, **kw): + """ + Creates a column definition. This is an object that describes + a column, basically just holding the keywords for later + creating an ``SOCol`` (or subclass) instance. Subclasses of + ``Col`` (which implement this interface) typically create the + related subclass of ``SOCol``. + """ + + name = """ + The name of the column. If this is not given in the constructor, + ``SQLObject`` will set this attribute from the variable name this + object is assigned to. + """ + +class ISOCol(Interface): + + """ + This is a column description that is bound to a single class. + This cannot be shared by subclasses, so a new instance is created + for every new class (in cases where classes share columns). + + These objects are created by ``Col`` instances, you do not create + them directly. + """ + + name = """ + The name of the attribute that points to this column. This is the + Python name of the column. + """ + + columnDef = """ + The ``Col`` object that created this instance. + """ + + immutable = """ + Boolean, default false. If true, then this column cannot be + modified. It cannot even be modified at construction, rendering + the table read-only. This will probably change in the future, as + it renders the option rather useless. + """ + + cascade = """ + If a foreign key, then this indicates if deletes in that foreign + table should cascade into this table. This can be true (deletes + cascade), false (the default, they do not cascade), or ``'null'`` + (this column is set to ``NULL`` if the foreign key is deleted). + """ + + constraints = """ + A list of ... @@? + """ + + notNone = """ + Boolean, default false. It true, then ``None`` (aka ``NULL``) is + not allowed in this column. Also the ``notNull`` attribute can be + used. + """ + + foreignKey = """ + If not None, then this column points to another table. The + attribute is the name (a string) of that table/class. + """ + + dbName = """ + The name of this column in the database. + """ + + alternateID = """ + Boolean, default false. If true, then this column is assumed to + be unique, and you can fetch individual rows based on this + column's value. This will add a method ``byAttributeName`` to the + parent SQLObject subclass. + """ + + unique = """ + Boolean, default false. If this column is unique; effects the + database ``CREATE`` statement, and is implied by + ``alternateID=True``. + """ + + validator = """ + A IValidator object. All setting of this column goes through the + ``fromPython`` method of the validator. All getting of this + column from the database goes through ``toPython``. + """ + + default = """ + A value that holds the default value for this column. If the + default value passed in is a callable, then that value is called + to return a default (a typical example being ``DateTime.now``). + """ + + sqlType = """ + The SQL type of the column, overriding the default column type. + """ Added: trunk/SQLObject/docs/test.py =================================================================== --- trunk/SQLObject/docs/test.py 2005-07-28 08:38:30 UTC (rev 859) +++ trunk/SQLObject/docs/test.py 2005-07-31 06:58:07 UTC (rev 860) @@ -0,0 +1,15 @@ +import sys, os + +if sys.version_info >= (2, 4): + import doctest +else: + raise ImportError("Python 2.4 doctest required") + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +def test(): + for doc in ['SQLObject.txt']: + doctest.testfile(doc, optionflags=doctest.ELLIPSIS) + +if __name__ == '__main__': + test() Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-07-28 08:38:30 UTC (rev 859) +++ trunk/SQLObject/sqlobject/col.py 2005-07-31 06:58:07 UTC (rev 860) @@ -363,7 +363,9 @@ self.kw = kw def _set_name(self, value): - assert self._name is None, "You cannot change a name after it has already been set (from %s to %s)" % (self.name, value) + assert self._name is None or self._name == value, ( + "You cannot change a name after it has already been set " + "(from %s to %s)" % (self.name, value)) self._name = value def _get_name(self): @@ -374,7 +376,13 @@ def withClass(self, soClass): return self.baseClass(soClass=soClass, name=self._name, **self.kw) + def __repr__(self): + return '<%s %s %s>' % ( + self.__class__.__name__, hex(abs(id(self)))[2:], + self._name or '(unnamed)') + + class SOStringLikeCol(SOCol): """A common ancestor for SOStringCol and SOUnicodeCol""" def __init__(self, **kw): Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-07-28 08:38:30 UTC (rev 859) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-07-31 06:58:07 UTC (rev 860) @@ -290,6 +290,7 @@ print '%(n)2i%(threadName)s/%(name)s%(spaces)s%(sep)s %(s)s' % locals() def _executeRetry(self, conn, cursor, query): + print query return cursor.execute(query) def _query(self, conn, s): @@ -398,7 +399,7 @@ ", ".join(tables)) else: columns = ", ".join(["%s.%s" % (cls.sqlmeta.table, col.dbName) - for col in cls.sqlmeta._columns]) + for col in cls.sqlmeta.columnList]) if columns: q += "%s.%s, %s FROM %s" % \ (cls.sqlmeta.table, cls.sqlmeta.idName, columns, @@ -465,8 +466,8 @@ else: desc = False assert sqlbuilder.sqlIdentifier(s), "Strings in clauses are expected to be column identifiers. I got: %r" % s - if select.sourceClass.sqlmeta._columnDict.has_key(s): - s = select.sourceClass.sqlmeta._columnDict[s].dbName + if s in select.sourceClass.sqlmeta.columns: + s = select.sourceClass.sqlmeta.columns[s].dbName if desc: return sqlbuilder.DESC(sqlbuilder.SQLConstant(s)) else: @@ -520,7 +521,7 @@ def createColumns(self, soClass): columnDefs = [self.createIDColumn(soClass)] \ + [self.createColumn(soClass, col) - for col in soClass.sqlmeta._columns] + for col in soClass.sqlmeta.columnList] return ",\n".join([" %s" % c for c in columnDefs]) def createColumn(self, soClass, col): @@ -624,7 +625,7 @@ if 'id' in kw: data[soClass.sqlmeta.idName] = kw['id'] else: - for key, col in soClass.sqlmeta._columnDict.items(): + for key, col in soClass.sqlmeta.columns.items(): if key in kw: data[col.dbName] = kw[key] elif col.foreignName in kw: Modified: trunk/SQLObject/sqlobject/index.py =================================================================== --- trunk/SQLObject/sqlobject/index.py 2005-07-28 08:38:30 UTC (rev 859) +++ trunk/SQLObject/sqlobject/index.py 2005-07-31 06:58:07 UTC (rev 860) @@ -37,7 +37,7 @@ columnName = desc['column'] if not isinstance(columnName, str): columnName = columnName.name - colDict = self.soClass.sqlmeta._columnDict + colDict = self.soClass.sqlmeta.columns if not colDict.has_key(columnName): for possible in colDict.values(): if possible.origName == columnName: @@ -136,4 +136,10 @@ def withClass(self, soClass): return self.baseClass(soClass=soClass, **self.kw) + def __repr__(self): + return '<%s %s %s>' % ( + self.__class__.__name__, + hex(abs(id(self)))[2:], + self.kw) + __all__ = ['DatabaseIndex'] Modified: trunk/SQLObject/sqlobject/joins.py =================================================================== --- trunk/SQLObject/sqlobject/joins.py 2005-07-28 08:38:30 UTC (rev 859) +++ trunk/SQLObject/sqlobject/joins.py 2005-07-31 06:58:07 UTC (rev 860) @@ -16,7 +16,6 @@ def __init__(self, otherClass=None, **kw): kw['otherClass'] = otherClass - kw['joinDef'] = self self.kw = kw if self.kw.has_key('joinMethodName'): self._joinMethodName = popKey(self.kw, 'joinMethodName') @@ -37,6 +36,7 @@ self._joinMethodName = self.kw['joinMethodName'] del self.kw['joinMethodName'] return self.baseClass(soClass=soClass, + joinDef=self, joinMethodName=self._joinMethodName, **self.kw) @@ -53,6 +53,7 @@ orderBy=NoDefault, joinDef=None): self.soClass = soClass + self.joinDef = joinDef self.otherClassName = otherClass classregistry.registry(soClass.sqlmeta.registry).addClassCallback( otherClass, self._setOtherClass) Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-07-28 08:38:30 UTC (rev 859) +++ trunk/SQLObject/sqlobject/main.py 2005-07-31 06:58:07 UTC (rev 860) @@ -127,7 +127,7 @@ def findDependantColumns(name, klass): depends = [] - for col in klass.sqlmeta._columns: + for col in klass.sqlmeta.columnList: if col.foreignKey == name and col.cascade is not None: depends.append(col) return depends @@ -164,10 +164,26 @@ # when necessary: (bad clever? maybe) expired = False + # This is a mapping from column names to SOCol (or subclass) + # instances: + columns = {} + columnList = [] + + # This is a mapping from column names to Col (or subclass) + # instances; these objects don't have the logic that the SOCol + # objects do, and are not attached to this class closely. + columnDefinitions = {} + + # These are lists of the join and index objects: + joins = [] + indexes = [] + indexDefinitions = [] + joinDefinitions = [] + __metaclass__ = declarative.DeclarativeMeta # These attributes shouldn't be shared with superclasses: - _unshared_attributes = ['table', 'idName'] + _unshared_attributes = ['table', 'idName', 'columns'] # These are internal bookkeeping attributes; the class-level # definition is a default for the instances, instances will @@ -223,17 +239,302 @@ cls._plainJoinRemovers = {} # This is a dictionary of columnName: columnObject - cls._columnDict = {} - cls._columns = [] + # None of these objects can be shared with superclasses + cls.columns = {} + cls.columnList = [] + # These, however, can be shared: + cls.columnDefinitions = cls.columnDefinitions.copy() + cls.indexes = [] + cls.indexDefinitions = cls.indexDefinitions[:] + cls.joins = [] + cls.joinDefinitions = cls.joinDefinitions[:] + + setClass = classmethod(setClass) - # We keep track of the different joins by index, - # putting them in this list. - cls._joinList = [] - cls._joinDict = {} - cls._indexList = [] + ############################################################ + ## Adding special values, like columns and indexes + ############################################################ - setClass = classmethod(setClass) + ######################################## + ## Column handling + ######################################## + def addColumn(cls, columnDef, changeSchema=False, connection=None): + sqlmeta = cls + soClass = cls.soClass + del cls + column = columnDef.withClass(soClass) + name = column.name + assert name != 'id', ( + "The 'id' column is implicit, and should not be defined as " + "a column") + assert name not in sqlmeta.columns, ( + "The class %s.%s already has a column %r (%r), you cannot " + "add the column %r" + % (soClass.__module__, soClass.__name__, name, + sqlmeta.columnDefinitions[name], + columnDef)) + sqlmeta.columnDefinitions[name] = columnDef + sqlmeta.columns[name] = column + # A stable-ordered version of the list... + sqlmeta.columnList.append(column) + + ################################################### + # Create the getter function(s). We'll start by + # creating functions like _SO_get_columnName, + # then if there's no function named _get_columnName + # we'll alias that to _SO_get_columnName. This + # allows a sort of super call, even though there's + # no superclass that defines the database access. + if sqlmeta.cacheValues: + # We create a method here, which is just a function + # that takes "self" as the first argument. + getter = eval('lambda self: self._SO_loadValue(%s)' % repr(instanceName(name))) + + else: + # If we aren't caching values, we just call the + # function _SO_getValue, which fetches from the + # database. + getter = eval('lambda self: self._SO_getValue(%s)' % repr(name)) + setattr(soClass, rawGetterName(name), getter) + + # Here if the _get_columnName method isn't in the + # definition, we add it with the default + # _SO_get_columnName definition. + if not hasattr(soClass, getterName(name)) or (name == 'childName'): + setattr(soClass, getterName(name), getter) + sqlmeta._plainGetters[name] = 1 + + ################################################# + # Create the setter function(s) + # Much like creating the getters, we will create + # _SO_set_columnName methods, and then alias them + # to _set_columnName if the user hasn't defined + # those methods themself. + + # @@: This is lame; immutable right now makes it unsettable, + # making the table read-only + if not column.immutable: + # We start by just using the _SO_setValue method + setter = eval('lambda self, val: self._SO_setValue(%s, val, self.%s, self.%s)' % (repr(name), '_SO_fromPython_%s' % name, '_SO_toPython_%s' % name)) + setattr(soClass, '_SO_fromPython_%s' % name, column.fromPython) + setattr(soClass, '_SO_toPython_%s' % name, column.toPython) + setattr(soClass, rawSetterName(name), setter) + # Then do the aliasing + if not hasattr(soClass, setterName(name)) or (name == 'childName'): + setattr(soClass, setterName(name), setter) + # We keep track of setters that haven't been + # overridden, because we can combine these + # set columns into one SQL UPDATE query. + sqlmeta._plainSetters[name] = 1 + + ################################################## + # Here we check if the column is a foreign key, in + # which case we need to make another method that + # fetches the key and constructs the sister + # SQLObject instance. + if column.foreignKey: + + # We go through the standard _SO_get_columnName + # deal, except chopping off the "ID" ending since + # we're giving the object, not the ID of the + # object this time: + if sqlmeta.cacheValues: + # self._SO_class_className is a reference + # to the class in question. + getter = eval('lambda self: self._SO_foreignKey(self._SO_loadValue(%r), self._SO_class_%s)' % (instanceName(name), column.foreignKey)) + else: + # Same non-caching version as above. + getter = eval('lambda self: self._SO_foreignKey(self._SO_getValue(%s), self._SO_class_%s)' % (repr(name), column.foreignKey)) + if column.origName.upper().endswith('ID'): + origName = column.origName[:-2] + else: + origName = column.origName + setattr(soClass, rawGetterName(origName), getter) + + # And we set the _get_columnName version + # (sans ID ending) + if not hasattr(soClass, getterName(name)[:-2]): + setattr(soClass, getterName(name)[:-2], getter) + sqlmeta._plainForeignGetters[name[:-2]] = 1 + + if not column.immutable: + # The setter just gets the ID of the object, + # and then sets the real column. + setter = eval('lambda self, val: setattr(self, %s, self._SO_getID(val))' % (repr(name))) + setattr(soClass, rawSetterName(name)[:-2], setter) + if not hasattr(soClass, setterName(name)[:-2]): + setattr(soClass, setterName(name)[:-2], setter) + sqlmeta._plainForeignSetters[name[:-2]] = 1 + + # We'll need to put in a real reference at + # some point. See needSet at the top of the + # file for more on this. + classregistry.registry(sqlmeta.registry).addClassCallback( + column.foreignKey, + lambda foreign, me, attr: setattr(me, attr, foreign), + soClass, '_SO_class_%s' % column.foreignKey) + + if column.alternateMethodName: + func = eval('lambda cls, val, connection=None: cls._SO_fetchAlternateID(%s, %s, val, connection=connection)' % (repr(column.name), repr(column.dbName))) + setattr(soClass, column.alternateMethodName, classmethod(func)) + + if changeSchema: + conn = connection or soClass._connection + conn.addColumn(sqlmeta.table, column) + + if soClass._SO_finishedClassCreation: + makeProperties(soClass) + + addColumn = classmethod(addColumn) + + def addColumnsFromDatabase(sqlmeta, connection=None): + soClass = sqlmeta.soClass + conn = connection or soClass._connection + for columnDef in conn.columnsFromSchema(sqlmeta.table, soClass): + if columnDef.name not in sqlmeta.columnDefinitions: + sqlmeta.addColumn(columnDef) + + addColumnsFromDatabase = classmethod(addColumnsFromDatabase) + + def delColumn(sqlmeta, column, changeSchema=False, connection=None): + soClass = sqlmeta.soClass + if isinstance(column, str): + column = sqlmeta.columns[column] + if isinstance(column, col.Col): + for c in sqlmeta.columns.values(): + if column is c.columnDef: + column = c + break + else: + raise IndexError( + "Column with definition %r not found" % column) + name = column.name + del sqlmeta.columns[name] + del sqlmeta.columnDefinitions[name] + sqlmeta.columnList.remove(column) + delattr(soClass, rawGetterName(name)) + if sqlmeta._plainGetters.has_key(name): + delattr(soClass, getterName(name)) + delattr(soClass, rawSetterName(name)) + if sqlmeta._plainSetters.has_key(name): + delattr(soClass, setterName(name)) + if column.foreignKey: + delattr(soClass, rawGetterName(name)[:-2]) + if sqlmeta._plainForeignGetters.has_key(name[:-2]): + delattr(soClass, getterName(name)[:-2]) + delattr(soClass, rawSetterName(name)[:-2]) + if sqlmeta._plainForeignSetters.has_key(name[:-2]): + delattr(soClass, setterName(name)[:-2]) + if column.alternateMethodName: + delattr(soClass, column.alternateMethodName) + + if changeSchema: + conn = connection or soClass._connection + conn.delColumn(sqlmeta.table, column) + + if soClass._SO_finishedClassCreation: + unmakeProperties(soClass) + + delColumn = classmethod(delColumn) + + ######################################## + ## Join handling + ######################################## + + def addJoin(cls, joinDef): + sqlmeta = cls + soClass = cls.soClass + # The name of the method we'll create. If it's + # automatically generated, it's generated by the + # join class. + join = joinDef.withClass(soClass) + meth = join.joinMethodName + + sqlmeta.joins.append(join) + index = len(sqlmeta.joins)-1 + if joinDef not in sqlmeta.joinDefinitions: + sqlmeta.joinDefinitions.append(joinDef) + + # The function fetches the join by index, and + # then lets the join object do the rest of the + # work: + func = eval('lambda self: self.sqlmeta.joins[%i].performJoin(self)' % index) + + # And we do the standard _SO_get_... _get_... deal + setattr(soClass, rawGetterName(meth), func) + if not hasattr(soClass, getterName(meth)): + setattr(soClass, getterName(meth), func) + sqlmeta._plainJoinGetters[meth] = 1 + + # Some joins allow you to remove objects from the + # join. + if hasattr(join, 'remove'): + # A... [truncated message content] |
|
From: <sub...@co...> - 2005-07-28 08:38:37
|
Author: phd
Date: 2005-07-28 08:38:30 +0000 (Thu, 28 Jul 2005)
New Revision: 859
Modified:
trunk/SQLObject/examples/codebits.py
Log:
Fixed two minor bugs found by Cedric BRINER.
Modified: trunk/SQLObject/examples/codebits.py
===================================================================
--- trunk/SQLObject/examples/codebits.py 2005-07-27 19:18:12 UTC (rev 858)
+++ trunk/SQLObject/examples/codebits.py 2005-07-28 08:38:30 UTC (rev 859)
@@ -162,7 +162,7 @@
## Snippet "composite"
class InvoiceItem(SQLObject):
- amount = Currency()
+ amount = CurrencyCol()
currency = StringChar(length=3)
def _get_price(self):
@@ -218,7 +218,7 @@
return self._so.longitude
def _set_longitude(self, value):
self._so.longitude = value
- longitude = property(_get_longitude, set_longitude)
+ longitude = property(_get_longitude, _set_longitude)
## end snippet
## Snippet "image-binary"
|
|
From: <sub...@co...> - 2005-07-27 19:18:18
|
Author: phd
Date: 2005-07-27 19:18:12 +0000 (Wed, 27 Jul 2005)
New Revision: 858
Modified:
trunk/SQLObject/sqlobject/inheritance/__init__.py
Log:
Yet another minor inheritance optimization from alexander smishlajev -
create the object in one INSERT instead of INSERT+UPDATE.
Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-27 15:30:12 UTC (rev 857)
+++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-27 19:18:12 UTC (rev 858)
@@ -194,7 +194,7 @@
if self._parentClass:
parentClass = self._parentClass
new_kw = {}
- parent_kw = {}
+ parent_kw = {'childName': self.__class__.__name__}
for (name, value) in kw.items():
if hasattr(parentClass, name):
parent_kw[name] = value
@@ -202,7 +202,6 @@
new_kw[name] = value
kw = new_kw
self._parent = parentClass(kw=parent_kw)
- self._parent.childName = self.__class__.__name__
id = self._parent.id
super(InheritableSQLObject, self)._create(id, **kw)
|
|
From: <sub...@co...> - 2005-07-27 16:28:53
|
Author: phd Date: 2005-07-27 15:30:12 +0000 (Wed, 27 Jul 2005) New Revision: 857 Modified: trunk/SQLObject/sqlobject/tests/test_select.py Log: Fixed incompatibility with Python 2.2 - test.select.py uses yield in enumerate(). Modified: trunk/SQLObject/sqlobject/tests/test_select.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_select.py 2005-07-27 14:56:05 UTC (rev 856) +++ trunk/SQLObject/sqlobject/tests/test_select.py 2005-07-27 15:30:12 UTC (rev 857) @@ -1,3 +1,4 @@ +from __future__ import generators # for enumerate from sqlobject import * from sqlobject.tests.dbtest import * |
|
From: <sub...@co...> - 2005-07-27 16:22:21
|
Author: phd
Date: 2005-07-27 14:55:30 +0000 (Wed, 27 Jul 2005)
New Revision: 855
Modified:
trunk/SQLObject/sqlobject/inheritance/__init__.py
Log:
Fixed a bug - pass connection to all calls.
Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py
===================================================================
--- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-27 10:45:40 UTC (rev 854)
+++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-27 14:55:30 UTC (rev 855)
@@ -78,12 +78,12 @@
if hasattr(val, 'childName'):
childName = val.childName
if childName is not None:
- return val._childClasses[childName].get(id, selectResults=childResults)
+ return val._childClasses[childName].get(id, connection=connection, selectResults=childResults)
#DSM: Now, we know we are alone or the last child in a family...
#DSM: It's time to find our parents
inst = val
while inst._parentClass and not inst._parent:
- inst._parent = inst._parentClass.get(id, childUpdate=True)
+ inst._parent = inst._parentClass.get(id, connection=connection, childUpdate=True)
inst = inst._parent
#DSM: We can now return ourself
return val
@@ -114,7 +114,7 @@
#DSM: Update each child class if needed and existing (only for new
#DSM: dynamic column as no child classes exists at object creation)
for c in cls._childClasses.values():
- c.addColumn(columnDef, childUpdate=True)
+ c.addColumn(columnDef, connection=connection, childUpdate=True)
addColumn = classmethod(addColumn)
|
|
From: <sub...@co...> - 2005-07-27 16:22:19
|
Author: phd
Date: 2005-07-27 14:56:05 +0000 (Wed, 27 Jul 2005)
New Revision: 856
Modified:
trunk/SQLObject/sqlobject/main.py
Log:
Optimization - pass result to cls.get().
Modified: trunk/SQLObject/sqlobject/main.py
===================================================================
--- trunk/SQLObject/sqlobject/main.py 2005-07-27 14:55:30 UTC (rev 855)
+++ trunk/SQLObject/sqlobject/main.py 2005-07-27 14:56:05 UTC (rev 856)
@@ -1149,15 +1149,9 @@
if obj:
return obj
if connection:
- obj = cls.get(result[0], connection=connection)
+ obj = cls.get(result[0], connection=connection, selectResults=result[1:])
else:
- obj = cls.get(result[0])
- if not obj.sqlmeta.cacheValues:
- obj._SO_writeLock.acquire()
- try:
- obj._SO_selectInit(result[1:])
- finally:
- obj._SO_writeLock.release()
+ obj = cls.get(result[0], selectResults=result[1:])
return obj
_SO_fetchAlternateID = classmethod(_SO_fetchAlternateID)
|