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
|
Sep
|
Oct
|
Nov
|
Dec
|
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) |
From: <sub...@co...> - 2005-07-27 10:45:51
|
Author: phd Date: 2005-07-27 10:45:40 +0000 (Wed, 27 Jul 2005) New Revision: 854 Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py trunk/SQLObject/sqlobject/inheritance/iteration.py Log: Applied two patches made by alexander smishlajev. The first one is to remove childName column from repr(). The second one is to optimize ineritance - don't fetch parents one by one, fetch them at once. Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-23 19:45:45 UTC (rev 853) +++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-27 10:45:40 UTC (rev 854) @@ -231,11 +231,12 @@ self._parent.destroySelf() super(InheritableSQLObject, self).destroySelf() - def _reprItems(self): items = super(InheritableSQLObject, self)._reprItems() + # add parent attributes (if any) if self._parentClass: items.extend(self._parent._reprItems()) - return items + # filter out our special column + return [item for item in items if item[0] != 'childName'] __all__ = ['InheritableSQLObject'] Modified: trunk/SQLObject/sqlobject/inheritance/iteration.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-07-23 19:45:45 UTC (rev 853) +++ trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-07-27 10:45:40 UTC (rev 854) @@ -3,33 +3,47 @@ from sqlobject.dbconnection import Iteration class InheritableIteration(Iteration): - #phd: default array size for cursor.fetchmany() + # Default array size for cursor.fetchmany() defaultArraySize = 10000 def __init__(self, dbconn, rawconn, select, keepConnection=False): + # unless we do a lazy select, replace sourceClass + # with the root of the inheritance tree (children + # will be attached by sourceClass.get) + lazyColumns = select.ops.get('lazyColumns', False) + sourceClass = select.sourceClass + if sourceClass._parentClass and not lazyColumns: + addClauses = [] + while sourceClass._parentClass: + addClauses.append(sourceClass._parentClass.q.childName + == sourceClass.__name__) + sourceClass = sourceClass._parentClass + select = select.__class__(sourceClass, + sqlbuilder.AND(select.clause, *addClauses), + select.clauseTables, **select.ops) super(InheritableIteration, self).__init__(dbconn, rawconn, select, keepConnection) + self.lazyColumns = lazyColumns self.cursor.arraysize = self.defaultArraySize self._results = [] - #phd: find the index of the childName column + # Find the index of the childName column childNameIdx = None columns = select.sourceClass.sqlmeta._columns - for i in range(len(columns)): #phd: enumerate() is unavailable python 2.2 + for i in range(len(columns)): # enumerate() is unavailable python 2.2 if columns[i].name == "childName": childNameIdx = i break self._childNameIdx = childNameIdx def next(self): - lazyColumns = self.select.ops.get('lazyColumns', 0) if not self._results: self._results = list(self.cursor.fetchmany()) - if not lazyColumns: self.fetchChildren() + if not self.lazyColumns: self.fetchChildren() if not self._results: self._cleanup() raise StopIteration result = self._results[0] del self._results[0] - if lazyColumns: + if self.lazyColumns: obj = self.select.sourceClass.get(result[0], connection=self.dbconn) return obj else: |
From: <sub...@co...> - 2005-07-21 20:07:25
|
Author: phd Date: 2005-07-21 20:00:32 +0000 (Thu, 21 Jul 2005) New Revision: 851 Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py trunk/SQLObject/sqlobject/main.py Log: Added _reprItems() created by alexander smishlajev. Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-21 07:11:36 UTC (rev 850) +++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-07-21 20:00:32 UTC (rev 851) @@ -232,4 +232,10 @@ super(InheritableSQLObject, self).destroySelf() + def _reprItems(self): + items = super(InheritableSQLObject, self)._reprItems() + if self._parentClass: + items.extend(self._parent._reprItems()) + return items + __all__ = ['InheritableSQLObject'] Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-07-21 07:11:36 UTC (rev 850) +++ trunk/SQLObject/sqlobject/main.py 2005-07-21 20:00:32 UTC (rev 851) @@ -1334,7 +1334,7 @@ row.destroySelf() else: row.set(**{setnull: None}) - + self.sqlmeta._obsolete = True self._connection._SO_delete(self) self._connection.cache.expire(self.id, self.__class__) |
From: <sub...@co...> - 2005-07-21 07:11:41
|
Author: ianb Date: 2005-07-21 07:11:36 +0000 (Thu, 21 Jul 2005) New Revision: 850 Modified: trunk/SQLObject/sqlobject/joins.py trunk/SQLObject/sqlobject/main.py Log: Moved _SO_perConnection to sqlmeta Modified: trunk/SQLObject/sqlobject/joins.py =================================================================== --- trunk/SQLObject/sqlobject/joins.py 2005-07-21 07:11:20 UTC (rev 849) +++ trunk/SQLObject/sqlobject/joins.py 2005-07-21 07:11:36 UTC (rev 850) @@ -127,7 +127,7 @@ self.otherClass, self.joinColumn, inst.id) - if inst._SO_perConnection: + if inst.sqlmeta._perConnection: conn = inst._connection else: conn = None @@ -180,7 +180,7 @@ self.otherColumn, self.joinColumn, inst.id) - if inst._SO_perConnection: + if inst.sqlmeta._perConnection: conn = inst._connection else: conn = None Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-07-21 07:11:20 UTC (rev 849) +++ trunk/SQLObject/sqlobject/main.py 2005-07-21 07:11:36 UTC (rev 850) @@ -182,6 +182,9 @@ # left. _creating = False _obsolete = False + # Sometimes an intance is attached to a connection, not + # globally available. In that case, self.sqlmeta._perConnection + # will be true. It's false by default: _perConnection = False def __classinit__(cls, new_attrs): @@ -274,11 +277,6 @@ __metaclass__ = declarative.DeclarativeMeta - # Sometimes an intance is attached to a connection, not - # globally available. In that case, self._SO_perConnection - # will be true. It's false by default: - _SO_perConnection = False - _connection = sqlhub _columns = [] @@ -827,7 +825,7 @@ # Sometimes we need to know if this instance is # global or tied to a particular connection. # This flag tells us that: - self._SO_perConnection = True + self.sqlmeta._perConnection = True if not selectResults: dbNames = [col.dbName for col in self.sqlmeta._columns] @@ -1045,7 +1043,7 @@ def _SO_foreignKey(self, id, joinClass): if id is None: return None - elif self._SO_perConnection: + elif self.sqlmeta._perConnection: return joinClass.get(id, connection=self._connection) else: return joinClass.get(id) @@ -1060,7 +1058,7 @@ # Pass the connection object along if we were given one. if kw.has_key('connection'): self._connection = kw['connection'] - self._SO_perConnection = True + self.sqlmeta._perConnection = True del kw['connection'] self._SO_writeLock = threading.Lock() |
From: <sub...@co...> - 2005-07-21 07:11:27
|
Author: ianb Date: 2005-07-21 07:11:20 +0000 (Thu, 21 Jul 2005) New Revision: 849 Modified: trunk/SQLObject/sqlobject/tests/test_auto.py Log: changed to sqlmeta style Modified: trunk/SQLObject/sqlobject/tests/test_auto.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_auto.py 2005-07-21 06:06:04 UTC (rev 848) +++ trunk/SQLObject/sqlobject/tests/test_auto.py 2005-07-21 07:11:20 UTC (rev 849) @@ -140,10 +140,10 @@ if not supports('fromDatabase'): return class AutoTest(SQLObject): - _fromDatabase = True _connection = getConnection() class sqlmeta(sqlmeta): idName = 'auto_id' + fromDatabase = True john = AutoTest(firstName='john', lastName='doe', age=10, |
From: <sub...@co...> - 2005-07-21 06:06:09
|
Author: ianb Date: 2005-07-21 06:06:04 +0000 (Thu, 21 Jul 2005) New Revision: 848 Modified: trunk/SQLObject/sqlobject/main.py Log: Moved _expired into sqlmeta Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-07-21 06:02:09 UTC (rev 847) +++ trunk/SQLObject/sqlobject/main.py 2005-07-21 06:06:04 UTC (rev 848) @@ -160,6 +160,9 @@ cacheValues = True registry = None fromDatabase = False + # Default is false, but we set it to true for the *instance* + # when necessary: (bad clever? maybe) + expired = False __metaclass__ = declarative.DeclarativeMeta @@ -284,10 +287,6 @@ _indexes = [] - # Default is false, but we set it to true for the *instance* - # when necessary: (bad clever? maybe) - _expired = False - sqlmeta = sqlmeta #DSM: The _inheritable attribute controls wheter the class can by @@ -506,6 +505,7 @@ _registry = _sqlmeta_attr('registry', 2) _idType = _sqlmeta_attr('idType', 2) _fromDatabase = _sqlmeta_attr('fromDatabase', 2) + _expired = _sqlmeta_attr('expired', 2) def _cleanDeprecatedAttrs(cls, new_attrs): for attr in ['_table', '_lazyUpdate', '_style', '_idName', @@ -513,12 +513,17 @@ '_idType', '_fromDatabase']: if new_attrs.has_key(attr): new_name = attr[1:] - deprecated("'%s' is deprecated; please set the '%s' " + deprecated("%r is deprecated; please set the %r " "attribute in sqlmeta instead" % (attr, new_name), level=2, stacklevel=5) setattr(cls.sqlmeta, new_name, new_attrs[attr]) delattr(cls, attr) + for attr in ['_expired']: + if new_attrs.has_key(attr): + deprecated("%r is deprecated and read-only; please do " + "not use it in your classes until it is fully " + "deprecated" % attr, level=3, stacklevel=5) _cleanDeprecatedAttrs = classmethod(_cleanDeprecatedAttrs) @@ -851,7 +856,7 @@ pass else: return result - self._expired = False + self.sqlmeta.expired = False dbNames = [col.dbName for col in self.sqlmeta._columns] selectResults = self._connection._SO_selectOne(self, dbNames) if not selectResults: @@ -872,7 +877,7 @@ if not selectResults: raise SQLObjectNotFound, "The object %s by the ID %s has been deleted" % (self.__class__.__name__, self.id) self._SO_selectInit(selectResults) - self._expired = False + self.sqlmeta.expired = False finally: self._SO_writeLock.release() @@ -891,15 +896,15 @@ self._SO_writeLock.release() def expire(self): - if self._expired: + if self.sqlmeta.expired: return self._SO_writeLock.acquire() try: - if self._expired: + if self.sqlmeta.expired: return for column in self.sqlmeta._columns: delattr(self, instanceName(column.name)) - self._expired = True + self.sqlmeta.expired = True self._connection.cache.expire(self.id, self.__class__) finally: self._SO_writeLock.release() |