sqlobject-cvs Mailing List for SQLObject (Page 191)
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: <ian...@us...> - 2003-04-08 09:05:23
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv27668/SQLObject Modified Files: DBConnection.py Log Message: Made fcntl not a requirement (it's not available on Windows) Index: DBConnection.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/DBConnection.py,v retrieving revision 1.14 retrieving revision 1.15 diff -C2 -d -r1.14 -r1.15 *** DBConnection.py 7 Apr 2003 19:48:41 -0000 1.14 --- DBConnection.py 8 Apr 2003 09:05:19 -0000 1.15 *************** *** 5,9 **** from Cache import CacheSet import Col ! import fcntl try: import cPickle as pickle --- 5,12 ---- from Cache import CacheSet import Col ! try: ! import fcntl ! except ImportError: ! fcntl = None try: import cPickle as pickle |
From: <ian...@us...> - 2003-04-08 09:05:00
|
Update of /cvsroot/sqlobject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv27524 Modified Files: setup.py Log Message: distribution notes Index: setup.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/setup.py,v retrieving revision 1.7 retrieving revision 1.8 diff -C2 -d -r1.7 -r1.8 *** setup.py 8 Apr 2003 04:07:08 -0000 1.7 --- setup.py 8 Apr 2003 09:04:55 -0000 1.8 *************** *** 32,33 **** --- 32,38 ---- download_url="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.3.tar.gz?download") + # Send announce to: + # sql...@li... + # db...@py... + # pyt...@py... + # pyt...@py... |
From: <ian...@us...> - 2003-04-08 04:07:11
|
Update of /cvsroot/sqlobject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv28202 Modified Files: setup.py Log Message: Fixed typo in trove Index: setup.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/setup.py,v retrieving revision 1.6 retrieving revision 1.7 diff -C2 -d -r1.6 -r1.7 *** setup.py 8 Apr 2003 03:26:29 -0000 1.6 --- setup.py 8 Apr 2003 04:07:08 -0000 1.7 *************** *** 18,22 **** """, classifiers=["Development Status :: 4 - Beta", ! "Indended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Programming Language :: Python", --- 18,22 ---- """, classifiers=["Development Status :: 4 - Beta", ! "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Programming Language :: Python", |
From: <ian...@us...> - 2003-04-08 04:05:08
|
Update of /cvsroot/sqlobject/SOWeb In directory sc8-pr-cvs1:/tmp/cvs-serv27207 Modified Files: index.html index.txt Log Message: Updated for 0.3 Index: index.html =================================================================== RCS file: /cvsroot/sqlobject/SOWeb/index.html,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** index.html 13 Mar 2003 20:45:38 -0000 1.3 --- index.html 8 Apr 2003 04:05:04 -0000 1.4 *************** *** 106,116 **** <div class="section" id="download"> <h1><a class="toc-backref" href="#id12" name="download">Download</a></h1> ! <p><a class="reference" href="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.2.1.tar.gz?download">SQLObject-0.2.1.tar.gz</a></p> ! <p><a class="reference" href="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.2.1-1.noarch.rpm?download">SQLObject-0.2.1-1.noarch.rpm</a></p> ! <p><a class="reference" href="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.2.1.win32.exe?download">SQLObject-0.2.1.win32.exe</a></p> </div> <div class="section" id="documentation"> <h1><a class="toc-backref" href="#id13" name="documentation">Documentation</a></h1> ! <p><a class="reference" href="docs/News.html">New in 0.2.1</a></p> <p><a class="reference" href="docs/SQLObject.html">SQLObject documentation</a></p> <p><a class="reference" href="docs/SQLBuilder.html">SQLBuilder documentation</a></p> --- 106,116 ---- <div class="section" id="download"> <h1><a class="toc-backref" href="#id12" name="download">Download</a></h1> ! <p><a class="reference" href="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.3.tar.gz?download">SQLObject-0.3.tar.gz</a></p> ! <p><a class="reference" href="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.3-1.noarch.rpm?download">SQLObject-0.3-1.noarch.rpm</a></p> ! <p><a class="reference" href="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.3.win32.exe?download">SQLObject-0.3.win32.exe</a></p> </div> <div class="section" id="documentation"> <h1><a class="toc-backref" href="#id13" name="documentation">Documentation</a></h1> ! <p><a class="reference" href="docs/News.html">New in 0.3</a></p> <p><a class="reference" href="docs/SQLObject.html">SQLObject documentation</a></p> <p><a class="reference" href="docs/SQLBuilder.html">SQLBuilder documentation</a></p> Index: index.txt =================================================================== RCS file: /cvsroot/sqlobject/SOWeb/index.txt,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** index.txt 13 Mar 2003 20:44:21 -0000 1.2 --- index.txt 8 Apr 2003 04:05:04 -0000 1.3 *************** *** 107,126 **** ======== ! SQLObject-0.2.1.tar.gz__ ! __ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.2.1.tar.gz?download ! SQLObject-0.2.1-1.noarch.rpm__ ! __ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.2.1-1.noarch.rpm?download ! SQLObject-0.2.1.win32.exe__ ! __ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.2.1.win32.exe?download Documentation ============= ! `New in 0.2.1`__ __ docs/News.html --- 107,126 ---- ======== ! SQLObject-0.3.tar.gz__ ! __ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.3.tar.gz?download ! SQLObject-0.3-1.noarch.rpm__ ! __ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.3-1.noarch.rpm?download ! SQLObject-0.3.win32.exe__ ! __ http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.3.win32.exe?download Documentation ============= ! `New in 0.3`__ __ docs/News.html |
From: <ian...@us...> - 2003-04-08 03:26:32
|
Update of /cvsroot/sqlobject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv17293 Modified Files: setup.py Log Message: Added more information (for PyPI) Index: setup.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/setup.py,v retrieving revision 1.5 retrieving revision 1.6 diff -C2 -d -r1.5 -r1.6 *** setup.py 8 Apr 2003 03:08:00 -0000 1.5 --- setup.py 8 Apr 2003 03:26:29 -0000 1.6 *************** *** 1,10 **** from distutils.core import setup setup(name="SQLObject", version="0.3", ! description="Object-Relational Manager", author="Ian Bicking", author_email="ia...@co...", ! url="http://www.colorstudy.com/software/SQLObject/", license="LGPL", ! packages=["SQLObject"]) --- 1,33 ---- from distutils.core import setup + + import sys + # patch distutils if it can't cope with the "classifiers" keyword + if sys.version < '2.2.3': + from distutils.dist import DistributionMetadata + DistributionMetadata.classifiers = None + DistributionMetadata.download_url = None + setup(name="SQLObject", version="0.3", ! 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, and a ! native anydbm-based backend. Requires Python 2.2+. ! """, ! classifiers=["Development Status :: 4 - Beta", ! "Indended Audience :: Developers", ! "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", ! "Programming Language :: Python", ! "Topic :: Database", ! "Topic :: Database :: Front-Ends", ! "Topic :: Software Development :: Libraries :: Python Modules", ! ], author="Ian Bicking", author_email="ia...@co...", ! url="http://sqlobject.org", license="LGPL", ! packages=["SQLObject"], ! download_url="http://prdownloads.sourceforge.net/sqlobject/SQLObject-0.3.tar.gz?download") |
From: <ian...@us...> - 2003-04-08 03:08:11
|
Update of /cvsroot/sqlobject/SQLObject/docs In directory sc8-pr-cvs1:/tmp/cvs-serv12016/docs Modified Files: SQLObject.txt Log Message: Updated docs (almost ready for release!) Index: SQLObject.txt =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/docs/SQLObject.txt,v retrieving revision 1.7 retrieving revision 1.8 diff -C2 -d -r1.7 -r1.8 *** SQLObject.txt 3 Apr 2003 19:36:59 -0000 1.7 --- SQLObject.txt 8 Apr 2003 03:08:02 -0000 1.8 *************** *** 1,4 **** ``````````````` ! SQLObject 0.2.1 ``````````````` --- 1,4 ---- ``````````````` ! SQLObject 0.3 ``````````````` *************** *** 9,13 **** SQLObject is by Ian Bicking, <ia...@co...>. The website ! is http://colorstudy.com/software/SQLObject The code is licensed under the `Lesser General Public License`_ --- 9,13 ---- SQLObject is by Ian Bicking, <ia...@co...>. The website ! is http://sqlobject.org The code is licensed under the `Lesser General Public License`_ *************** *** 29,51 **** 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). Requirements ============ ! Currently SQLObject supports MySQL, PostgreSQL (via ``psycopg``) and ! SQLite. The SQLite support is the youngest. ! Python 2.2 or higher is strictly required. SQLObject uses metaclasses ! to create the objects and properties throughout, and those features ! are only available in 2.2+. Compared To Other Database Wrappers --- 29,53 ---- 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 most SQL. ! Queries are constructed using Python syntax, which is turned into SQL. ! This also allows non-SQL databases to be used with the same query ! syntax. Requirements ============ ! Currently SQLObject supports MySQL, PostgreSQL (via ``psycopg``), ! SQLite, and a DBM-based store. The DBM backend is the youngest, ! and somewhat experimental. ! Python 2.2 or higher is strictly required. No backward compatible ! version is planned -- SQLObject uses many 2.2 features, and in many ! ways is my own learning tool for the dramatically different ! programming style that new-style classes allow. Compared To Other Database Wrappers *************** *** 53,64 **** There are several object-relational mappers (ORM) for Python. I ! honestly can't comment 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 that feel as well -- setting attributes has side effects (it ! changes the database), and defining classes has side effects (through ! the use of metaclasses). For me this is something of an experiment in ! new-style classes. I believe it has been successful. SQLObject creates objects that feel similar to normal Python objects --- 55,66 ---- 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 exposed ! without fear, knowing that they can be made dynamic later. SQLObject creates objects that feel similar to normal Python objects *************** *** 71,81 **** 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. ! .. _PyDo: http://skunkweb.sourceforge.net/pydo.html SQLObject is, to my knowledge, unique in using metaclasses to --- 73,84 ---- 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 *************** *** 84,88 **** 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. .. _Webware: http://webware.sourceforge.net --- 87,92 ---- 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 *************** *** 93,114 **** Here are some things I plan: ! * More databases supported. * 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. ! * Pre-fetching of values for a SQLObject. In particular, when you ! do a `select` that fetches a hundred objects, you ought to fetch ! all those objects in one ``SELECT`` statement, instead of just ! fetching the IDs and issuing one ``SELECT`` for each object. ! * `select` gives an iterator, so all objects don't have to be in ! memory at the same time. ! * Slicing of results, creating ``LIMIT`` and ``OFFSET`` in the SQL. ! * Cache management -- purging old objects, keeping size down, or ! not caching any objects. ! * Maybe even profile some of this, so that I know what really is ! the performance problem (though it also depends greatly on the ! structure of the database). ! * Allow different naming schemes, both for ``id`` and for ! mixedCase/mixed_case. Using SQLObject --- 97,111 ---- Here are some things I plan: ! * More databases supported. SQL databases (like Oracle or Firebird) ! should be easy, non-SQL databases like Metakit should be possible. * 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. ! * Profile SQLObject of this, so that I can identify bottlenecks. ! * Allow different naming schemes. ! * Makes hooks with a validation and form generation package, so ! SQLObject classes (read: schemas) can be published for editing ! more directly and easily. ! * Automatic joins in select queries. Using SQLObject *************** *** 133,142 **** CREATE TABLE person ( ! id INT PRIMARY KEY, first_name VARCHAR(100) NOT NULL, middle_initial CHAR(1), last_name VARCHAR(150) NOT NULL ); - CREATE SEQUENCE person_seq; The Python class will look like this:: --- 130,138 ---- CREATE TABLE person ( ! id SERIAL, first_name VARCHAR(100) NOT NULL, middle_initial CHAR(1), last_name VARCHAR(150) NOT NULL ); The Python class will look like this:: *************** *** 145,150 **** __connection__ = MySQLConnection( ! host='localhost', db='sqlobject_test', ! user='sqlobject_test', passwd='sqltest') class Person(SQLObject): --- 141,145 ---- __connection__ = MySQLConnection( ! host='localhost', db='test', user='test', passwd='') class Person(SQLObject): *************** *** 154,159 **** Col('lastName')] ! The special variable `__connection__` gives the access method for any ! classes in the module. You can also use the class variable `_connection` in the same way. The MySQL connection is shown, for Postgres use ``DBConnection.PostgresConnection(dsnString)``. --- 149,154 ---- Col('lastName')] ! The special variable `__connection__` gives the database connection ! for any classes in the module. You can also use the class variable `_connection` in the same way. The MySQL connection is shown, for Postgres use ``DBConnection.PostgresConnection(dsnString)``. *************** *** 171,197 **** use the keyword `dbName` analogously. If the primary key for the row isn't ``id``, you can use a _idName class attribute (like ! ``_idName='PersonID'``). ! You'll note that each column is a ``Col`` instance. This way you can give extra information about the column -- the first argument is always the name of the column, but you can give default values (for when new instances are created) and indicate it is a foreign key to ! another class/table. More options may be added later (perhaps type ! checking, for instance). ! SQLObject differs from most other systems in that it currently doesn't ! do any type checking. It is assumed that the database does this -- ! the database also usually does type coercion, so that the string ! ``"10"`` will be converted to the integer ``10`` if necessary. This ! should not present any security problem, as all values are properly ! quoted; but perhaps you'll find that bugs aren't caught that could ! be. You can still add extra constraints, if you wish (see ! `Overriding Column Attributes`_). You'll note that the ``id`` column is not given in the class ! definition. The ``id`` column must always exist (and be declared as ! ``INT PRIMARY KEY AUTO_INCREMENT`` in MySQL or ``INT PRIMARY KEY`` in ! Postgres with the accompanying sequence), and so there's no need to ! declare it. Using the Class --- 166,200 ---- use the keyword `dbName` analogously. If the primary key for the row isn't ``id``, you can use a _idName class attribute (like ! ``_idName='PersonID'``). An example using all of these:: ! class Person(SQLObject): ! ! _table = 'people' ! _id = 'peopleID' ! _columns = [Col('firstName', dbName='fname'), ! Col('middleInitial', dbName='MI'), ! Col('lastName', dbName='lname)] ! ! Each column is described with a ``Col`` instance. This way you can give extra information about the column -- the first argument is always the name of the column, but you can give default values (for when new instances are created) and indicate it is a foreign key to ! another class/table. To indicate the type of the column subclasses ! are provided. See `Automatic Schema Generation`_ for more (type ! information is not required, though). ! SQLObject doesn't require any type checking, and though some type ! information can be provided, it's not extensively used. It is assumed ! that the database does the type checking -- the database also usually ! does type coercion, so that the string ``"10"`` will be converted to ! the integer ``10`` if necessary. This should not present any security ! problem, as all values are properly quoted; but perhaps you'll find ! that bugs aren't caught that could be. You can still add extra ! constraints, if you wish (see `Overriding Column Attributes`_). 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``, and in ! SQLite as ``INTEGER PRIMARY KEY``. Using the Class *************** *** 210,214 **** 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 equivalent of ``None``). When you create an object, it is immediately inserted into the --- 213,217 ---- 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``). When you create an object, it is immediately inserted into the *************** *** 237,244 **** unique -- there is only one ``Person`` instance of a particular id in memory at any one time (unless you are using transactions_). If you ! ask for more than one, 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). To get an idea of what's happening behind the surface, I'll give the --- 240,248 ---- unique -- there is only one ``Person`` instance of a particular id in memory at any one time (unless you are using transactions_). If you ! ask for more than one person by a particular ID, 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). To get an idea of what's happening behind the surface, I'll give the *************** *** 266,270 **** UPDATE person SET middle_initial='Q' WHERE id = 1; >>> p.middleInitial ! -- In this case, we've actually saved the info. 'Q' >>> p2 = Person(1) --- 270,275 ---- UPDATE person SET middle_initial='Q' WHERE id = 1; >>> p.middleInitial ! -- In this case, we've actually saved the info, so ! -- no query is made. 'Q' >>> p2 = Person(1) *************** *** 277,292 **** Hopefully you see that the SQL that gets sent is pretty clear and ! predictable. There are slight inefficiencies in inserting rows, but ! this is relatively less common than retrieval anyway. To view the SQL ! being sent, pass the keyword argument ``debug=1`` to MySQLConnection ! or PostgresConnection -- all SQL will be printed to the console. This ! can be reassuring. ! ! If you find yourself retrieving large numbers of these objects the ! performance penalty should not be too great because of the caching. ! However, if you iterate through them each and access them in sequence, ! then many SQL statements will be sent instead of a single statement ! that fetches all the rows. Maybe later that will be changed, but it's ! a performance problem for the time being. As a small optimization, instead of assigning each attribute --- 282,290 ---- Hopefully you see that the SQL that gets sent is pretty clear and ! predictable. There are some inefficiences; these should be worked out ! in time. To view the SQL being sent, pass the keyword argument ! ``debug=1`` to your connection object -- all SQL will be printed to ! the console. This can be reassuring, and I would encourage you to try ! it. As a small optimization, instead of assigning each attribute *************** *** 311,316 **** The list of columns is a list of `Col` objects. These objects don't ! have functionality, but give you a way to specify the column. In the ! future more manners of specification will probably be available. If you find yourself writing ``Col(colName)`` a lot, you might be --- 309,313 ---- The list of columns is a list of `Col` objects. These objects don't ! have functionality, but give you a way to specify the column. If you find yourself writing ``Col(colName)`` a lot, you might be *************** *** 319,324 **** be turned into a `Col` object for you. ! The first argument is always `name` -- the (Pythonic) name of the ! column. The other keyword arguments: `dbName`: --- 316,321 ---- be turned into a `Col` object for you. ! The first argument to `Col` is always `name` -- the (Pythonic) name of ! the column. The other keyword arguments: `dbName`: *************** *** 328,343 **** `default`: The default value for this column. Used when creating a new ! row. If you give a callable object or function, then the result ! of that call will be used. So you can give ``DateTime.now`` to ! make the default value be the current time. Or you can use ! ``func.NOW()`` to have the database use the ! ``NOW()`` function. If you don't give a default there will be an exception if this column isn't specified in the call to `new`. `foreignKey`: ! You can give a class name for `foreignKey`, which means that the ! column references another class (which is another row in another ! table). The column name should end with ``ID``, and the database ! column is expected to end with ``_id``. This will create another ! method in your class. For instance, if the ``personID`` column points to the ``Person`` --- 325,341 ---- `default`: The default value for this column. Used when creating a new ! row. If you give a callable object or function, then on object ! creation 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`. `foreignKey`: ! You can give a class name (as a string) for `foreignKey`, which ! means that the column references another class (which is another ! row in another table). The column name should end with ``ID``, ! and the database column is expected to end with ``_id``. This ! will create another method in your class. For instance, if the ``personID`` column points to the ``Person`` *************** *** 346,350 **** corresponding ``Person`` instance. ! Pass a *string* with `foreignKey`, not the actual class. `alternateID`: This boolean (default False) indicates if the column can be used --- 344,349 ---- corresponding ``Person`` instance. ! Pass a *string* with `foreignKey`, not the actual class. This ! avoids circular dependencies. `alternateID`: This boolean (default False) indicates if the column can be used *************** *** 352,356 **** class method will be added, like ``byUsername`` which will return that object. User `alternateMethodName` if you don't like the ! ``by*`` algorithm (e.g. ``alternateMethodName="username"``). The column should be declared ``UNIQUE`` in your table schema. --- 351,355 ---- class method will be added, like ``byUsername`` which will return that object. User `alternateMethodName` if you don't like the ! ``by*`` name (e.g. ``alternateMethodName="username"``). The column should be declared ``UNIQUE`` in your table schema. *************** *** 365,369 **** The connection object to use, from `DBConnection`. You can also set the variable `__connection__` in the enclosing module and ! it will be picked up. `_table`: The database name of the table for this class. If you don't give --- 364,369 ---- The connection object to use, from `DBConnection`. You can also set the variable `__connection__` in the enclosing module and ! it will be picked up. 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 *************** *** 379,382 **** --- 379,384 ---- then this is probably the only way to do so. You should also use it with transactions_ (it is not implied). + `_idName`: + The name of the primary key column (default ``id``). Relations Between Classes/Tables *************** *** 418,423 **** This will create another attribute ``person``, which will retrieve a ``Person`` object, while ``personID`` will still retrieve the integer ! ID. We use a string instead of a class here because circular ! dependencies will kill us otherwise. We also add an attribute `_joins` to ``Person``. This is a list of --- 420,424 ---- This will create another attribute ``person``, which will retrieve a ``Person`` object, while ``personID`` will still retrieve the integer ! ID. We also add an attribute `_joins` to ``Person``. This is a list of *************** *** 434,437 **** --- 435,440 ---- method. + .. _Many-to-Many: + Relations: Many-to-Many ~~~~~~~~~~~~~~~~~~~~~~~ *************** *** 541,544 **** --- 544,557 ---- selecting is always assumed to be included, of course. + You can use the keyword arguments `orderBy` and `groupBy` to create + ``ORDER BY`` and ``GROUP 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``; `groupBy` is similar. + Both accept lists or tuples of arguments. + + 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 *************** *** 552,557 **** 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. This will only happen when you use negative ! indexes. You can get the length of the result without executing the full SQL. --- 565,570 ---- 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 only happen when you use ! negative indexes. You can get the length of the result without executing the full SQL. *************** *** 567,572 **** While we haven't done so in the examples, you can include your own ! functionality in the object in the class definition. Including ! methods should be obvious enough (just define them). Initializing the Objects --- 580,586 ---- While we haven't done so in the examples, you can include your own ! methods in the class definition. Including methods should be obvious ! enough (just define them), but there are some other details to be ! aware of. Initializing the Objects *************** *** 575,580 **** With new-style classes, `__init__` is called everytime the class is called. That means it's called when an object is just fetched from ! the cache. That's useless in most cases, so it's better to use the ! `_init` method, which is only called once in an objects life (with one argument -- the object's ID). --- 589,594 ---- With new-style classes, `__init__` is called everytime the class is called. That means it's called when an object is just fetched from ! the cache. That's useless in most cases, so instead we use a `_init` ! method, which is only called once in an object's life (with one argument -- the object's ID). *************** *** 633,645 **** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ! It's a little complicated, though, if you want to figure out how to ! override the behavior of an attribute. For instance, imagine there's ! special code you want to run whenever someone's name changes -- you ! could make a subclass, and then use ``Person.__setattr__(self, ! 'lastName', value)`` to actually do the deed -- but that's obviously ! very awkward. SQLObject creates methods like ``_set_lastName`` for ! each of your columns (but no ``_del_`` or ``_doc_``), but again you ! can't use this, since there's no superclass to reference, and you want ! to override that ``_set_lastName`` method yourself. To deal with this, SQLObject creates two methods for each getter and --- 647,659 ---- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ! It's a little more complicated if you want to override the behavior of ! an attribute. For instance, imagine there's special code you want to ! run whenever someone's name changes -- you could make a subclass, and ! then use ``Person.__setattr__(self, 'lastName', value)`` to actually ! do the deed -- but that's obviously very awkward. SQLObject creates ! methods like ``_set_lastName`` for each of your columns (but no ! ``_del_`` or ``_doc_``), but again you can't use this, since there's ! no superclass to reference, and you want to override that ! ``_set_lastName`` method yourself. To deal with this, SQLObject creates two methods for each getter and *************** *** 684,693 **** then formatted the number into a nice string on the way out. This is one disadvantage of making actual work look like simple attribute ! access. Hopefully people will get used to this sort of thing, though. Transactions ------------ ! Transaction support for SQLObject is still a little vague. Transactions can be used like:: --- 698,707 ---- then formatted the number into a nice string on the way out. This is one disadvantage of making actual work look like simple attribute ! access. Transactions ------------ ! Transaction support in SQLObject is left to the database. Transactions can be used like:: *************** *** 714,737 **** ---------------------------------- ! The `DBConnection` module currently has three classes, `MySQLConnection`, ! `PostgresConnection`, and `SQLiteConnection`. `MySQLConnection` takes the keyword arguments `host`, `db`, `user`, and `passwd`, just like `MySQLdb.connect` does. `PostgresConnection` takes a single connection string, like ``"dbname=something user=some_user"``, just like `psycopg.connect`. ! `SQLiteConnection` takes the a single connection string. ! ! You can pass the keyword argument `debug` to either connector. If set ! to true, then any SQL sent to the database will also be printed to the ! console. SQLite ! ------ ! SQLite doesn't support a great deal of concurrency. For instance, say ! you want to delete a bunch of rows:: for p in Person.select(Person.q.firstName == 'Bob'): --- 728,775 ---- ---------------------------------- ! The `DBConnection` module currently has four external classes, ! `MySQLConnection`, `PostgresConnection`, `SQLiteConnection`, ! and `DBMConnection`. ! ! 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 does not + support transactions_. + + 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 except ! `automatic class generation`_. 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`_ or transactions_. ! ! SQLite doesn't support much concurrency. For instance, say you want ! to delete a bunch of rows:: for p in Person.select(Person.q.firstName == 'Bob'): *************** *** 740,748 **** Because the ``select`` is an iterator, and keeps a connection open, when you do the ``p.destroy()`` you'll get an exception, because ! SQLite doesn't support this concurrency. Instead, do:: for p in list(Person.select(Person.q.firstName == 'Bob')): p.destroy() Exported Symbols ================ --- 778,880 ---- Because the ``select`` is an iterator, and keeps a connection open, when you do the ``p.destroy()`` you'll get an exception, because ! SQLite doesn't support two open connections. Instead, do:: for p in list(Person.select(Person.q.firstName == 'Bob')): p.destroy() + DBMConnection + ~~~~~~~~~~~~~ + + `DBMConnection` takes a single string, which is the path to a + directory in which to store the database. + + DBMConnection uses flat hash databases to store all the data. These + databases are created by the standard `anydbm` module. It's something + of an experiment, and things like safety under concurrent access + (multithreaded or multiprocess) should not be expected. `groupBy` is + also not supported, though select queries, ordering, and slices are + supported. + + DBMConnection allows any kind of objects to be put in columns -- all + values are pickled, and so only normal pickling restrictions apply. + + DBMConnection does not support `automatic class generation` or + transactions_. + + 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 (though + DBMConnection_ do not require or use type information). + + Columns Types + ~~~~~~~~~~~~~ + + A column type is indicated by using a subclass of `Col`: + + `StringCol`: + StringCol represents ``CHAR``, ``VARCHAR``, and ``TEXT``. + If you pass no `length` keyword argument, then ``TEXT`` will + be assumed. If indicate ``varchar=False`` then ``CHAR`` will + be used, otherwise ``VARCHAR`` is the default. + `IntCol`: + The ``INT`` type. + `FloatCol`: + The ``FLOAT`` type. + `KeyCol`: + A foreign key. Just like `Col` right now... the interface will + be trimmed down eventually. + `EnumCol`: + A MySQL ``ENUM``, i.e., one of a finite number of strings. + For other databases this will be a ``VARCHAR``. + `DateTimeCol`: + ``DATETIME``. + + Creating and Dropping Tables + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + To create a database 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_ relations, 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. + + Automatic Class Generation + --------------------------- + + *Currently this is only available for with MySQL_* + + 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. + + 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). + Exported Symbols ================ *************** *** 759,768 **** * `RelatedJoin` * `getID` ! * `getObject`. From `DBConnection`: * `MySQLConnection` ! * `PostgresConnection`. From `SQLBuilder`: --- 891,902 ---- * `RelatedJoin` * `getID` ! * `getObject` From `DBConnection`: * `MySQLConnection` ! * `PostgresConnection` ! * `SQLiteConnection` ! * `DBMConnection` From `SQLBuilder`: *************** *** 775,779 **** * `CONTAINSSTRING` * `const` ! * `func`. For more information on SQLBuilder, read the `SQLBuilder --- 909,913 ---- * `CONTAINSSTRING` * `const` ! * `func` For more information on SQLBuilder, read the `SQLBuilder |
From: <ian...@us...> - 2003-04-08 03:08:10
|
Update of /cvsroot/sqlobject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv12016 Modified Files: README.txt setup.py Log Message: Updated docs (almost ready for release!) Index: README.txt =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/README.txt,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** README.txt 13 Mar 2003 20:32:43 -0000 1.2 --- README.txt 8 Apr 2003 03:08:00 -0000 1.3 *************** *** 1,5 **** ! =============== ! SQLObject 0.2.1 ! =============== :Author: Ian Bicking --- 1,5 ---- ! ============= ! SQLObject 0.3 ! ============= :Author: Ian Bicking *************** *** 12,17 **** It absolutely requires Python 2.2 or higher. It currently supports ! MySQL through the `MySQLdb` package, and PostgreSQL through the ! `psycopg` package. For more information please see the documentation in --- 12,17 ---- It absolutely requires Python 2.2 or higher. It currently supports ! MySQL through the `MySQLdb` package, PostgreSQL through the ! `psycopg` package, SQLite, and a DBM-based backend. For more information please see the documentation in Index: setup.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/setup.py,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** setup.py 14 Mar 2003 04:17:53 -0000 1.4 --- setup.py 8 Apr 2003 03:08:00 -0000 1.5 *************** *** 1,5 **** from distutils.core import setup setup(name="SQLObject", ! version="0.2.1", description="Object-Relational Manager", author="Ian Bicking", --- 1,5 ---- from distutils.core import setup setup(name="SQLObject", ! version="0.3", description="Object-Relational Manager", author="Ian Bicking", |
From: <ian...@us...> - 2003-04-08 03:01:38
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv10413/SQLObject Modified Files: SQLObject.py Log Message: Changed ifExists argument to createTable to make more sense, as ifNotExists Index: SQLObject.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLObject.py,v retrieving revision 1.16 retrieving revision 1.17 diff -C2 -d -r1.16 -r1.17 *** SQLObject.py 7 Apr 2003 23:28:07 -0000 1.16 --- SQLObject.py 8 Apr 2003 03:01:34 -0000 1.17 *************** *** 778,790 **** dropTable = classmethod(dropTable) ! def createTable(cls, ifExists=False, createJoinTables=True): ! if ifExists and cls._connection.tableExists(cls._table): return cls._connection.createTable(cls) if createJoinTables: ! cls.createJoinTables(ifExists=ifExists) createTable = classmethod(createTable) ! def createJoinTables(cls, ifExists=False): for join in cls._joins: if not join.hasIntermediateTable(): --- 778,790 ---- dropTable = classmethod(dropTable) ! def createTable(cls, ifNotExists=False, createJoinTables=True): ! if ifNotExists and cls._connection.tableExists(cls._table): return cls._connection.createTable(cls) if createJoinTables: ! cls.createJoinTables(ifNotExists=ifNotExists) createTable = classmethod(createTable) ! def createJoinTables(cls, ifNotExists=False): for join in cls._joins: if not join.hasIntermediateTable(): *************** *** 796,800 **** if join.callingClass > join.otherClass: continue ! if ifExists and \ cls._connection.tableExists(join.intermediateTable): continue --- 796,800 ---- if join.callingClass > join.otherClass: continue ! if ifNotExists and \ cls._connection.tableExists(join.intermediateTable): continue |
From: <ian...@us...> - 2003-04-08 03:01:38
|
Update of /cvsroot/sqlobject/SQLObject/tests In directory sc8-pr-cvs1:/tmp/cvs-serv10413/tests Modified Files: SQLObjectTest.py Log Message: Changed ifExists argument to createTable to make more sense, as ifNotExists Index: SQLObjectTest.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/SQLObjectTest.py,v retrieving revision 1.5 retrieving revision 1.6 diff -C2 -d -r1.5 -r1.6 *** SQLObjectTest.py 7 Apr 2003 19:48:42 -0000 1.5 --- SQLObjectTest.py 8 Apr 2003 03:01:33 -0000 1.6 *************** *** 52,56 **** __connection__.query(c.create) elif hasattr(c, 'createTable'): ! c.createTable(ifExists=True) #__connection__.debug = 0 self.inserts() --- 52,56 ---- __connection__.query(c.create) elif hasattr(c, 'createTable'): ! c.createTable(ifNotExists=True) #__connection__.debug = 0 self.inserts() |
From: <ian...@us...> - 2003-04-08 02:51:53
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv7411/SQLObject Modified Files: Col.py Log Message: Fixed SQLite's type generation Index: Col.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/Col.py,v retrieving revision 1.5 retrieving revision 1.6 diff -C2 -d -r1.5 -r1.6 *** Col.py 7 Apr 2003 23:27:02 -0000 1.5 --- Col.py 8 Apr 2003 02:51:49 -0000 1.6 *************** *** 123,128 **** def _sqliteType(self): ! # SQLite is naturally typeless ! return '' def mysqlCreateSQL(self): --- 123,132 ---- def _sqliteType(self): ! # SQLite is naturally typeless, so as a fallback it uses ! # no type. ! try: ! return self._sqlType() ! except ValueError: ! return '' def mysqlCreateSQL(self): |
From: <ian...@us...> - 2003-04-07 23:28:11
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv8871/SQLObject Modified Files: SQLObject.py Log Message: Assertion to avoid Col('id') Index: SQLObject.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLObject.py,v retrieving revision 1.15 retrieving revision 1.16 diff -C2 -d -r1.15 -r1.16 *** SQLObject.py 7 Apr 2003 19:48:41 -0000 1.15 --- SQLObject.py 7 Apr 2003 23:28:07 -0000 1.16 *************** *** 317,320 **** --- 317,321 ---- def addColumn(cls, column, changeSchema=False): name = column.name + assert name != 'id', "The 'id' column is implicit, and should not be defined as a column" cls._SO_columnDict[name] = column |
From: <ian...@us...> - 2003-04-07 23:27:07
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv8439/SQLObject Modified Files: Col.py Log Message: Default SQLite to typless (i.e., Col can be used with auto schema generation) Index: Col.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/Col.py,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** Col.py 7 Apr 2003 19:48:40 -0000 1.4 --- Col.py 7 Apr 2003 23:27:02 -0000 1.5 *************** *** 123,127 **** def _sqliteType(self): ! return self._sqlType() def mysqlCreateSQL(self): --- 123,128 ---- def _sqliteType(self): ! # SQLite is naturally typeless ! return '' def mysqlCreateSQL(self): |
From: <ian...@us...> - 2003-04-07 20:11:32
|
Update of /cvsroot/sqlobject/SQLObject/docs In directory sc8-pr-cvs1:/tmp/cvs-serv17865 Modified Files: News.txt Log Message: Updated Index: News.txt =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/docs/News.txt,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** News.txt 14 Mar 2003 09:10:11 -0000 1.4 --- News.txt 7 Apr 2003 20:11:28 -0000 1.5 *************** *** 14,21 **** -------- * New column classes (see `Col` module), indicates type ! * Table creation (SQL schema generation) via new class method ! `createTable`. * Objects are not cached indefinitely. Cached objects are expired --- 14,28 ---- -------- + * Table creation (SQL schema generation) via new class method + `createTable`. And of course a `dropTable` method to go with. + + * Add and remove columns at runtime, optionally modifying the + schema in the database (via ``ALTER``). (Does not work in + SQLite) + * New column classes (see `Col` module), indicates type ! * Classes can be created by parsing an already existant table ! (MySQL only). * Objects are not cached indefinitely. Cached objects are expired *************** *** 25,28 **** --- 32,57 ---- pass ``nocache=True`` to your connection object to eliminate as much caching as possible. See `Cache` module for a bit more. + + * New DBMConnection, implements a database-like backend without any + database to speak of, including queries (so long as you use + `SQLBuilder` and don't generate your where clauses manually). + Actual SQL generation is done entirely by the database connection, + allowing portability across very different backends. + + * Postgres table IDs should be created with type ``SERIAL`` (which + implicitly creates a sequence). + + * New `_defaultOrder` class variable gives a default for the + `orderBy` parameter to `select` queries. + + Bugs + ---- + + * LIMIT/OFFSET (select result slicing) works in Postgres and SQLite. + + * ``tableExists`` method from DBConnection works in same. + + * mxDateTime not required (never should have been, always just an + option). SQLObject 0.2.1 |
From: <ian...@us...> - 2003-04-07 19:49:18
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv3612/SQLObject Modified Files: Col.py DBConnection.py SQLObject.py Log Message: * Extended the tests to work with Postgres, SQLite, DBM * Some databases don't implement some features (SQLite doesn't support dynamic columns, all but MySQL don't support auto-class creation), at least for now. * Make SERIAL work for columns under Postgres, as well as _idName * Fixed LIMIT/OFFSET bugs in Postgres, SQLite * tableExists in Postgres Index: Col.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/Col.py,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** Col.py 31 Mar 2003 05:51:28 -0000 1.3 --- Col.py 7 Apr 2003 19:48:40 -0000 1.4 *************** *** 122,125 **** --- 122,128 ---- return self._sqlType() + def _sqliteType(self): + return self._sqlType() + def mysqlCreateSQL(self): return ' '.join([self.dbName, self._mysqlType()] + self._extraSQL()) *************** *** 190,193 **** --- 193,200 ---- def _sqliteType(self): + return 'INT' + + def _postgresType(self): + # @@: there's a better type for this... return 'INT' Index: DBConnection.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/DBConnection.py,v retrieving revision 1.13 retrieving revision 1.14 diff -C2 -d -r1.13 -r1.14 *** DBConnection.py 7 Apr 2003 07:43:36 -0000 1.13 --- DBConnection.py 7 Apr 2003 19:48:41 -0000 1.14 *************** *** 28,32 **** --- 28,34 ---- sqlite = None import re + import warnings + warnings.filterwarnings("ignore", "DB-API extension cursor.lastrowid used") __all__ = ['MySQLConnection', 'PostgresConnection', 'SQLiteConnection', *************** *** 118,123 **** return Transaction(self) ! def queryInsertID(self, table, names, values): ! return self._runWithConnection(self._queryInsertID, table, names, values) def iterSelect(self, select): --- 120,125 ---- return Transaction(self) ! def queryInsertID(self, table, idName, names, values): ! return self._runWithConnection(self._queryInsertID, table, idName, names, values) def iterSelect(self, select): *************** *** 346,350 **** user=self.user, passwd=self.passwd) ! def _queryInsertID(self, conn, table, names, values): c = conn.cursor() q = self._insertSQL(table, names, values) --- 348,352 ---- user=self.user, passwd=self.passwd) ! def _queryInsertID(self, conn, table, idName, names, values): c = conn.cursor() q = self._insertSQL(table, names, values) *************** *** 428,436 **** return psycopg.connect(self.dsn) ! def _queryInsertID(self, conn, table, names, values): c = conn.cursor() ! c.execute('SELECT nextval(\'%s_seq\')' % table) (id,) = c.fetchone() ! names.append('id') values.append(id) q = self._insertSQL(table, names, values) --- 430,438 ---- return psycopg.connect(self.dsn) ! def _queryInsertID(self, conn, table, idName, names, values): c = conn.cursor() ! c.execute('SELECT nextval(\'%s_id_seq\')' % table) (id,) = c.fetchone() ! names.append(idName) values.append(id) q = self._insertSQL(table, names, values) *************** *** 445,449 **** if not end: return "%s OFFSET %i" % (query, start) ! return "%s LIMIT %i OFFSET %i" % (query, start, end-start) def createColumn(self, soClass, col): --- 447,451 ---- if not end: return "%s OFFSET %i" % (query, start) ! return "%s LIMIT %i OFFSET %i" % (query, end-start, start) def createColumn(self, soClass, col): *************** *** 458,462 **** def tableExists(self, tableName): # @@: obviously broken ! return False class SQLiteConnection(DBAPI): --- 460,477 ---- def tableExists(self, tableName): # @@: obviously broken ! result = self.queryOne("SELECT COUNT(relname) FROM pg_class WHERE relname = '%s'" ! % tableName) ! return result[0] ! ! def addColumn(self, tableName, column): ! self.query('ALTER TABLE %s ADD COLUMN %s' % ! (tableName, ! column.postgresCreateSQL())) ! ! def delColumn(self, tableName, column): ! self.query('ALTER TABLE %s DROP COLUMN %s' % ! (tableName, ! column.dbName)) ! class SQLiteConnection(DBAPI): *************** *** 470,474 **** return sqlite.connect(self.dsn) ! def _queryInsertID(self, conn, table, names, values): c = conn.cursor() q = self._insertSQL(table, names, values) --- 485,489 ---- return sqlite.connect(self.dsn) ! def _queryInsertID(self, conn, table, idName, names, values): c = conn.cursor() q = self._insertSQL(table, names, values) *************** *** 484,488 **** if not end: return "%s OFFSET %i" % (query, start) ! return "%s LIMIT %i OFFSET %i" % (query, start, end-start) def createColumn(self, soClass, col): --- 499,503 ---- if not end: return "%s OFFSET %i" % (query, start) ! return "%s LIMIT %i OFFSET %i" % (query, end-start, start) def createColumn(self, soClass, col): *************** *** 492,496 **** return '%s INTEGER PRIMARY KEY' % soClass._idName ! def joinSQLType(self): return 'INT NOT NULL' --- 507,511 ---- return '%s INTEGER PRIMARY KEY' % soClass._idName ! def joinSQLType(self, join): return 'INT NOT NULL' *************** *** 522,526 **** """ ! def queryInsertID(self, table, names, values): id = self._newID(table) self._saveDict(table, id, dict(zip(names, values))) --- 537,541 ---- """ ! def queryInsertID(self, table, idName, names, values): id = self._newID(table) self._saveDict(table, id, dict(zip(names, values))) *************** *** 790,796 **** results.remove(int(id1)) db[join2 + str(id2)] = ",".join(map(str, results)) - - - class DBMSelectResults(object): --- 805,808 ---- Index: SQLObject.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLObject.py,v retrieving revision 1.14 retrieving revision 1.15 diff -C2 -d -r1.14 -r1.15 *** SQLObject.py 7 Apr 2003 01:13:54 -0000 1.14 --- SQLObject.py 7 Apr 2003 19:48:41 -0000 1.15 *************** *** 729,733 **** # up to DBConnection, since getting a new ID is # non-standard. ! id = self._connection.queryInsertID(self._table, names, values) cache = self._connection.cache cache.created(id, self.__class__, self) --- 729,734 ---- # up to DBConnection, since getting a new ID is # non-standard. ! id = self._connection.queryInsertID(self._table, self._idName, ! names, values) cache = self._connection.cache cache.created(id, self.__class__, self) |
From: <ian...@us...> - 2003-04-07 19:48:49
|
Update of /cvsroot/sqlobject/SQLObject/tests In directory sc8-pr-cvs1:/tmp/cvs-serv3612/tests Modified Files: SQLObjectTest.py test.py Log Message: * Extended the tests to work with Postgres, SQLite, DBM * Some databases don't implement some features (SQLite doesn't support dynamic columns, all but MySQL don't support auto-class creation), at least for now. * Make SERIAL work for columns under Postgres, as well as _idName * Fixed LIMIT/OFFSET bugs in Postgres, SQLite * tableExists in Postgres Index: SQLObjectTest.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/SQLObjectTest.py,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** SQLObjectTest.py 7 Apr 2003 01:13:56 -0000 1.4 --- SQLObjectTest.py 7 Apr 2003 19:48:42 -0000 1.5 *************** *** 2,12 **** from SQLObject import * ! __connection__ = MySQLConnection(host='localhost', ! db='test', ! user='test', ! passwd='', ! debug=0) ! #__connection__ = DBMConnection('data') class SQLObjectTest(unittest.TestCase): --- 2,32 ---- from SQLObject import * ! True, False = 1==1, 0==1 ! def mysqlConnection(): ! SQLObjectTest.supportDynamic = True ! SQLObjectTest.supportAuto = True ! return MySQLConnection(host='localhost', ! db='test', ! user='test', ! passwd='', ! debug=0) ! ! def dbmConnection(): ! SQLObjectTest.supportDynamic = True ! SQLObjectTest.supportAuto = False ! return DBMConnection('data') ! ! def postgresConnection(): ! SQLObjectTest.supportDynamic = True ! SQLObjectTest.supportAuto = False ! return PostgresConnection(db='test') ! ! def sqliteConnection(): ! SQLObjectTest.supportDynamic = False ! SQLObjectTest.supportAuto = False ! return SQLiteConnection('data/sqlite.data') ! ! supportedDatabases = ['mysql', 'postgres', 'sqlite', 'dbm'] class SQLObjectTest(unittest.TestCase): *************** *** 22,25 **** --- 42,47 ---- unittest.TestCase.setUp(self) #__connection__.debug = self.debugSQL + for c in self.classes: + c._connection = __connection__ for c in self.classes + [self]: if hasattr(c, 'drop'): *************** *** 45,48 **** __connection__.query(c.drop) ! __all__ = ['SQLObjectTest', '__connection__',] --- 67,79 ---- __connection__.query(c.drop) ! def setDatabaseType(t): ! global __connection__ ! conn = globals()[t + "Connection"]() ! __connection__ = conn ! ! def connection(): ! return __connection__ ! ! __all__ = ['SQLObjectTest', 'setDatabaseType', 'connection', ! 'supportedDatabases'] Index: test.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/test.py,v retrieving revision 1.5 retrieving revision 1.6 diff -C2 -d -r1.5 -r1.6 *** test.py 7 Apr 2003 01:13:57 -0000 1.5 --- test.py 7 Apr 2003 19:48:44 -0000 1.6 *************** *** 93,96 **** --- 93,98 ---- def testDynamicColumn(self): + if not self.supportDynamic: + return nickname = StringCol('nickname', length=10) Person.addColumn(nickname, changeSchema=True) *************** *** 101,104 **** --- 103,108 ---- def testDynamicJoin(self): + if not self.supportDynamic: + return col = KeyCol('personID', foreignKey='Person') Phone.addColumn(col, changeSchema=True) *************** *** 110,115 **** else: phone.person = Person.selectBy(name='bob')[0] ! self.assertEqual([p.phone for p in Person.selectBy(name='tim')[0].phones], ! ['555-555-5555', '555-394-2930']) Phone.delColumn(col, changeSchema=True) Person.delJoin(join) --- 114,121 ---- else: phone.person = Person.selectBy(name='bob')[0] ! l = [p.phone for p in Person.selectBy(name='tim')[0].phones] ! l.sort() ! self.assertEqual(l, ! ['555-394-2930', '555-555-5555']) Phone.delColumn(col, changeSchema=True) Person.delJoin(join) *************** *** 122,126 **** class AutoTest(SQLObjectTest): ! create = """ CREATE TABLE IF NOT EXISTS auto_test ( id INT AUTO_INCREMENT PRIMARY KEY, --- 128,132 ---- class AutoTest(SQLObjectTest): ! mysqlCreate = """ CREATE TABLE IF NOT EXISTS auto_test ( id INT AUTO_INCREMENT PRIMARY KEY, *************** *** 133,143 **** """ ! drop = """ DROP TABLE IF EXISTS auto_test """ def testClassCreate(self): class AutoTest(SQLObject): _fromDatabase = True AutoTest.new(firstName='john', lastName='doe', --- 139,167 ---- """ ! postgresCreate = """ ! CREATE TABLE auto_test ( ! id SERIAL, ! first_name VARCHAR(100), ! last_name VARCHAR(200) NOT NULL, ! age INT, ! created DATETIME NOT NULL, ! happy char(1) DEFAULT 'Y' NOT NULL ! ) ! """ ! ! mysqlDrop = """ DROP TABLE IF EXISTS auto_test """ + postgresDrop = """ + DROP TABLE auto_test + """ + def testClassCreate(self): + if not self.supportAuto: + return class AutoTest(SQLObject): _fromDatabase = True + _connection = connection() AutoTest.new(firstName='john', lastName='doe', *************** *** 200,205 **** if __name__ == '__main__': import unittest, sys for arg in sys.argv[1:]: if arg in ('-vv', '--extra-verbose'): SQLObjectTest.debugSQL = 1 ! unittest.main() --- 224,246 ---- if __name__ == '__main__': import unittest, sys + dbs = [] for arg in sys.argv[1:]: + if arg.startswith('-d'): + dbs.append(arg[2:]) + if arg.startswith('--database='): + dbs.append(arg[11:]) if arg in ('-vv', '--extra-verbose'): SQLObjectTest.debugSQL = 1 ! sys.argv = [a for a in sys.argv ! if not a.startswith('-d') and not a.startswith('--database=')] ! if not dbs: ! dbs = ['mysql'] ! if dbs == ['all']: ! dbs = supportedDatabases ! for db in dbs: ! setDatabaseType(db) ! print 'Testing %s' % db ! try: ! unittest.main() ! except SystemExit: ! pass |
From: <ian...@us...> - 2003-04-07 07:43:40
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv17939/SQLObject Modified Files: DBConnection.py Log Message: PostgresConnection accepts keyword arguments matching MySQLConnection (and creates a dsn out of that). DSN is still accepted as before. Index: DBConnection.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/DBConnection.py,v retrieving revision 1.12 retrieving revision 1.13 diff -C2 -d -r1.12 -r1.13 *** DBConnection.py 7 Apr 2003 01:13:54 -0000 1.12 --- DBConnection.py 7 Apr 2003 07:43:36 -0000 1.13 *************** *** 404,412 **** class PostgresConnection(DBAPI): ! def __init__(self, dsn, autoCommit=1, **kw): assert psycopg, 'psycopg module cannot be found' if not autoCommit and not kw.has_key('pool'): # Pooling doesn't work with transactions... kw['pool'] = 0 self.dsn = dsn DBAPI.__init__(self, **kw) --- 404,425 ---- class PostgresConnection(DBAPI): ! def __init__(self, dsn=None, host=None, db=None, ! user=None, passwd=None, autoCommit=1, **kw): assert psycopg, 'psycopg module cannot be found' if not autoCommit and not kw.has_key('pool'): # Pooling doesn't work with transactions... kw['pool'] = 0 + if dsn is None: + dsn = [] + if db: + dsn.append('dbname=%s' % db) + if user: + dsn.append('user=%s' % user) + if passwd: + dsn.append('password=%s' % passwd) + if host: + # @@: right format? + dsn.append('host=%s' % host) + dsn = ' '.join(dsn) self.dsn = dsn DBAPI.__init__(self, **kw) |
From: <ian...@us...> - 2003-04-07 01:14:29
|
Update of /cvsroot/sqlobject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv1207 Modified Files: .cvsignore Log Message: * Moved all SQL into DBConnection (_SO_* methods) * A test case for RelatedJoin * Created a DBM-based backend. Passes almost all tests! * Incomplete Pickle-based backend (like DBM, only records are kept in individual files... I'm not sure I want to use it, though). Index: .cvsignore =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/.cvsignore,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -C2 -d -r1.1.1.1 -r1.2 *** .cvsignore 26 Feb 2003 23:59:40 -0000 1.1.1.1 --- .cvsignore 7 Apr 2003 01:13:52 -0000 1.2 *************** *** 3,4 **** --- 3,5 ---- dist MANIFEST + data |
From: <ian...@us...> - 2003-04-07 01:14:02
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv1207/SQLObject Modified Files: DBConnection.py SQLBuilder.py SQLObject.py Log Message: * Moved all SQL into DBConnection (_SO_* methods) * A test case for RelatedJoin * Created a DBM-based backend. Passes almost all tests! * Incomplete Pickle-based backend (like DBM, only records are kept in individual files... I'm not sure I want to use it, though). Index: DBConnection.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/DBConnection.py,v retrieving revision 1.11 retrieving revision 1.12 diff -C2 -d -r1.11 -r1.12 *** DBConnection.py 6 Apr 2003 07:56:27 -0000 1.11 --- DBConnection.py 7 Apr 2003 01:13:54 -0000 1.12 *************** *** 5,8 **** --- 5,15 ---- from Cache import CacheSet import Col + import fcntl + try: + import cPickle as pickle + except ImportError: + import pickle + import os + import anydbm try: *************** *** 20,26 **** except ImportError: sqlite = None ! __all__ = ['MySQLConnection', 'PostgresConnection', 'SQLiteConnection'] _connections = {} --- 27,35 ---- except ImportError: sqlite = None + import re ! __all__ = ['MySQLConnection', 'PostgresConnection', 'SQLiteConnection', ! 'PickleConnection', 'DBMConnection'] _connections = {} *************** *** 290,293 **** --- 299,310 ---- SQLBuilder.sqlRepr(secondValue))) + def _SO_columnClause(self, soClass, kw): + return ' '.join(['%s = %s' % + (soClass._SO_columnDict[key].dbName, + SQLBuilder.sqlRepr(value)) + for key, value + in kw.items()]) + + class Transaction(object): *************** *** 469,470 **** --- 486,855 ---- # turn it into a boolean: return not not result + + ######################################## + ## File-based connections + ######################################## + + class FileConnection(DBConnection): + + """ + Files connections should deal with setup, and define the + methods: + + * ``_fetchDict(self, table, id)`` + * ``_saveDict(self, table, id, d)`` + * ``_newID(table)`` + * ``tableExists(table)`` + * ``createTable(soClass)`` + * ``dropTable(table)`` + * ``clearTable(table)`` + * ``_SO_delete(so)`` + * ``_allIDs()`` + * ``_SO_createJoinTable(join)`` + """ + + def queryInsertID(self, table, names, values): + id = self._newID(table) + self._saveDict(table, id, dict(zip(names, values))) + return id + + def createColumns(self, soClass): + pass + + def _SO_update(self, so, values): + d = self._fetchDict(so._table, so.id) + for dbName, value in values: + d[dbName] = value + self._saveDict(so._table, so.id, d) + + def _SO_selectOne(self, so, columnNames): + d = self._fetchDict(so._table, so.id) + return [d[name] for name in columnNames] + + def _SO_selectOneAlt(self, cls, columnNames, column, value): + for id in self._allIDs(cls._table): + d = self._fetchDict(cls._table, id) + if d[column] == value: + d['id'] = id + return [d[name] for name in columnNames] + + _createRE = re.compile('CREATE TABLE\s+(IF NOT EXISTS\s+)?([^ ]*)', re.I) + _dropRE = re.compile('DROP TABLE\s+(IF EXISTS\s+)?([^ ]*)', re.I) + + def query(self, q): + match = self._createRE.search(q) + if match: + if match.group(1) and self.tableExists(match.group(2)): + return + class X: pass + x = X() + x._table = match.group(2) + return self.createTable(x) + match = self._dropRE.search(q) + if match: + if match.group(1) and not self.tableExists(match.group(2)): + return + return self.dropTable(match.group(2), match.group(1)) + + def addColumn(self, tableName, column): + for id in self._allIDs(tableName): + d = self._fetchDict(tableName, id) + d[column.dbName] = None + self._saveDict(tableName, id, d) + + def delColumn(self, tableName, column): + for id in self._allIDs(tableName): + d = self._fetchDict(tableName, id) + del d[column.dbName] + self._saveDict(tableName, id, d) + + def _SO_columnClause(self, soClass, kw): + clauses = [] + for name, value in kw.items(): + clauses.append(getattr(SQLBuilder.SmartTable(soClass._table), name) == value) + return SQLBuilder.AND(*clauses) + + def _SO_selectJoin(self, soClass, column, value): + results = [] + for id in self._allIDs(soClass._table): + d = self._fetchDict(soClass._table, id) + if d[column] == value: + results.append((id,)) + return results + + class PickleConnection(FileConnection): + + def __init__(self, path, **kw): + self.path = path + FileConnection.__init__(self, **kw) + + def _filename(self, table, id): + return '%s-%i.pickle' % (table, id) + + def _newID(self, table): + idFilename = os.path.join(self.path, "%s-ids.txt" % table) + try: + f = open(idFilename, "r+") + except IOError: + f = open(idFilename, "w") + f.write("0") + f.close() + f = open(idFilename, "r+") + fcntl.lockf(f, fcntl.LOCK_EX) + id = int(f.read()) + 1 + # @@: Can I just close this and open it in write mode, while + # retaining this lock? + f.seek(0) + f.write(str(id)) + fcntl.lockf(f, fcntl.LOCK_UN) + f.close() + return id + + def _fetchDict(self, table, id): + f = open(self._filename(table, id), 'rb') + d = pickle.load(f) + f.close() + return d + + def _saveDict(self, table, id, d): + f = open(self._filename(table, id), 'wb') + pickle.dump(d, f) + f.close() + + def _fetchTables(self): + filename = os.path.join(self.path, "table-list.txt") + try: + f = open(filename, "r") + except IOError: + return [] + lines = [l.strip() for l in f.readlines()] + f.close() + return lines + + def _saveTables(self, tables): + filename = os.path.join(self.path, "table-list.txt") + f = open(filename, "w") + f.write('\n'.join(tables)) + f.close() + + def tableExists(self, table): + return table in self._fetchTables() + + def createTable(self, soClass): + tables = self._fetchTables() + tables.append(soClass._table) + self._saveTables(tables) + + def dropTable(self, tableName): + tables = self._fetchTables() + tables.remove(tableName) + self._saveTables(tables) + self.clearTable(tableName) + + def clearTable(self, tableName): + for filename in os.listdir(self.path): + if filename.startswith(tableName + "-"): + os.unlink(os.path.join(self.path, filename)) + + def _SO_delete(self, so): + os.unlink(self._filename(so._table, so.id)) + + + class DBMConnection(FileConnection): + + def __init__(self, path, **kw): + self.path = path + try: + self._meta = anydbm.open(os.path.join(path, "meta.db"), "w") + except anydbm.error: + self._meta = anydbm.open(os.path.join(path, "meta.db"), "c") + self._tables = {} + FileConnection.__init__(self, **kw) + + def _newID(self, table): + id = int(self._meta["%s.id" % table]) + 1 + self._meta["%s.id" % table] = str(id) + return id + + def _saveDict(self, table, id, d): + db = self._getDB(table) + db[str(id)] = pickle.dumps(d) + + def _fetchDict(self, table, id): + return pickle.loads(self._getDB(table)[str(id)]) + + def _getDB(self, table): + try: + return self._tables[table] + except KeyError: + db = self._openTable(table) + self._tables[table] = db + return db + + def _openTable(self, table): + try: + db = anydbm.open(os.path.join(self.path, "%s.db" % table), "w") + except anydbm.error: + db = anydbm.open(os.path.join(self.path, "%s.db" % table), "c") + return db + + def tableExists(self, table): + return self._meta.has_key("%s.id" % table) \ + or os.path.exists(os.path.join(self.path, table + ".db")) + + def createTable(self, soClass): + self._meta["%s.id" % soClass._table] = "1" + + def dropTable(self, tableName): + try: + del self._meta["%s.id" % tableName] + except KeyError: + pass + self.clearTable(tableName) + + def clearTable(self, tableName): + if self._tables.has_key(tableName): + del self._tables[tableName] + filename = os.path.join(self.path, "%s.db" % tableName) + if os.path.exists(filename): + os.unlink(filename) + + def _SO_delete(self, so): + db = self._getDB(so._table) + del db[str(so.id)] + + def iterSelect(self, select): + return DBMSelectResults(self, select) + + def _allIDs(self, tableName): + return self._getDB(tableName).keys() + + def _SO_createJoinTable(self, join): + pass + + def _SO_dropJoinTable(self, join): + os.unlink(os.path.join(self.path, join.intermediateTable + ".db")) + + def _SO_intermediateJoin(self, table, get, join1, id1): + db = self._openTable(table) + try: + results = db[join1 + str(id1)] + except KeyError: + return [] + if not results: + return [] + return [(int(id),) for id in results.split(',')] + + def _SO_intermediateInsert(self, table, join1, id1, join2, id2): + db = self._openTable(table) + try: + results = db[join1 + str(id1)] + except KeyError: + results = "" + if results: + db[join1 + str(id1)] = results + "," + str(id2) + else: + db[join1 + str(id1)] = str(id2) + + try: + results = db[join2 + str(id2)] + except KeyError: + results = "" + if results: + db[join2 + str(id2)] = results + "," + str(id1) + else: + db[join2 + str(id2)] = str(id1) + + def _SO_intermediateDelete(self, table, join1, id1, join2, id2): + db = self._openTable(table) + try: + results = db[join1 + str(id1)] + except KeyError: + results = "" + results = map(int, results.split(",")) + results.remove(int(id2)) + db[join1 + str(id1)] = ",".join(map(str, results)) + try: + results = db[join2 + str(id2)] + except KeyError: + results = "" + results = map(int, results.split(",")) + results.remove(int(id1)) + db[join2 + str(id2)] = ",".join(map(str, results)) + + + + + class DBMSelectResults(object): + + def __init__(self, conn, select): + self.select = select + self.conn = conn + self.tables = select.tables + self.tableDict = {} + self._results = None + for i in range(len(self.tables)): + self.tableDict[self.tables[i]] = i + self.comboIter = _iterAllCombinations( + [self.conn._getDB(table).keys() for table in self.tables]) + if select.ops.get('orderBy'): + self._maxNext = -1 + results = self.allResults() + def cmper(a, b, orderBy=select.ops['orderBy']): + return cmp(getattr(a, orderBy), + getattr(b, orderBy)) + results.sort(cmper) + self._results = results + if select.ops.get('start'): + if select.ops.get('end'): + self._results = self._results[select.ops['start']:select.ops['end']] + else: + self._results = self._results[select.ops['start']:] + elif select.ops.get('end'): + self._results = self._results[:select.ops['end']] + elif select.ops.get('start'): + for i in range(select.ops.get('start')): + self.next() + if select.ops.get('end'): + self._maxNext = select.ops['end'] - select.ops['start'] + elif select.ops.get('end'): + self._maxNext = select.ops['end'] + else: + self._maxNext = -1 + + def next(self): + if self._results is not None: + try: + return self._results.pop(0) + except IndexError: + raise StopIteration + + for idList in self.comboIter: + self.idList = idList + if SQLBuilder.execute(self.select.clause, self): + if not self._maxNext: + raise StopIteration + self._maxNext -= 1 + return self.select.sourceClass(int(idList[self.tableDict[self.select.sourceClass._table]])) + raise StopIteration + + def field(self, table, field): + return self.conn._fetchDict(table, self.idList[self.tableDict[table]])[field] + + def allResults(self): + results = [] + while 1: + try: + results.append(self.next()) + except StopIteration: + return results + + + def _iterAllCombinations(l): + if len(l) == 1: + for id in l[0]: + yield [id] + else: + for id in l[0]: + for idList in _iterAllCombinations(l[1:]): + yield [id] + idList Index: SQLBuilder.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLBuilder.py,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** SQLBuilder.py 1 Apr 2003 16:51:28 -0000 1.2 --- SQLBuilder.py 7 Apr 2003 01:13:54 -0000 1.3 *************** *** 11,15 **** it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the ! License, or (at your option) any later version. This program is distributed in the hope that it will be useful, --- 11,15 ---- it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the ! License, or (at your option any later version. This program is distributed in the hope that it will be useful, *************** *** 94,98 **** origISOStr = None DateTimeType = None ! import re def isoStr(val): --- 94,99 ---- origISOStr = None DateTimeType = None ! import re, fnmatch ! import operator def isoStr(val): *************** *** 131,134 **** --- 132,141 ---- + def execute(expr, executor): + if hasattr(expr, 'execute'): + return expr.execute(executor) + else: + return expr + ######################################## ## Expression generation *************** *** 230,236 **** return {} class SQLOp(SQLExpression): def __init__(self, op, expr1, expr2): ! self.op = op self.expr1 = expr1 self.expr2 = expr2 --- 237,257 ---- return {} + operatorMap = { + "+": operator.add, + "/": operator.div, + "-": operator.sub, + "*": operator.mul, + "<": operator.lt, + "<=": operator.le, + "=": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, + "IN": operator.contains, + } + class SQLOp(SQLExpression): def __init__(self, op, expr1, expr2): ! self.op = op.upper() self.expr1 = expr1 self.expr2 = expr2 *************** *** 239,242 **** --- 260,284 ---- def components(self): return [self.expr1, self.expr2] + def execute(self, executor): + if self.op == "AND": + return execute(self.expr1, executor) \ + and execute(self.expr2, executor) + elif self.op == "OR": + return execute(self.expr1, executor) \ + or execute(self.expr2, executor) + elif self.op == "LIKE": + if not hasattr(self, '_regex'): + # @@: Crude, not entirely accurate + dest = self.expr2 + dest = dest.replace("%%", "\001") + dest = dest.replace("*", "\002") + dest = dest.replace("%", "*") + dest = dest.replace("\001", "%") + dest = dest.replace("\002", "[*]") + self._regex = re.compile(fnmatch.translate(dest), re.I) + return self._regex.search(execute(self.expr1, executor)) + else: + return operatorMap[self.op.upper()](execute(self.expr1, executor), + execute(self.expr2, executor)) class SQLCall(SQLExpression): *************** *** 248,251 **** --- 290,295 ---- def components(self): return [self.expr] + list(self.args) + def execute(self, executor): + raise ValueError, "I don't yet know how to locally execute functions" class SQLPrefix(SQLExpression): *************** *** 257,260 **** --- 301,312 ---- def components(self): return [self.expr] + def execute(self, executor): + expr = execute(self.expr, executor) + if prefix == "+": + return expr + elif prefix == "-": + return -expr + elif prefix.upper() == "NOT": + return not expr class SQLConstant(SQLExpression): *************** *** 263,267 **** --- 315,328 ---- def sqlRepr(self): return self.const + def execute(self, executor): + raise ValueError, "I don't yet know how to execute SQL constants" + + class SQLTrueClauseClass(SQLExpression): + def sqlRepr(self): + return "1 = 1" + def execute(self, executor): + return 1 + SQLTrueClause = SQLTrueClauseClass() ######################################## *************** *** 280,283 **** --- 341,346 ---- def sqlRepr(self): return str(self.tableName) + def execute(self, executor): + raise ValueError, "Tables don't have values" class SmartTable(Table): *************** *** 285,289 **** def __getattr__(self, attr): if self._capRE.search(attr): ! attr = attr[0] + self._capRE.sub(lambda m: '_%s' % m.group(0), attr[1:]) return Table.__getattr__(self, attr) --- 348,352 ---- def __getattr__(self, attr): if self._capRE.search(attr): ! attr = attr[0] + self._capRE.sub(lambda m: '_%s' % m.group(0).lower(), attr[1:]) return Table.__getattr__(self, attr) *************** *** 296,299 **** --- 359,364 ---- def tablesUsedImmediate(self): return [self.tableName] + def execute(self, executor): + return executor.field(self.tableName, self.fieldName) class ConstantSpace: Index: SQLObject.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLObject.py,v retrieving revision 1.13 retrieving revision 1.14 diff -C2 -d -r1.13 -r1.14 *** SQLObject.py 6 Apr 2003 07:56:27 -0000 1.13 --- SQLObject.py 7 Apr 2003 01:13:54 -0000 1.14 *************** *** 576,580 **** self._connection._SO_update(self, [(self._SO_columnDict[name].dbName, ! SQLBuilder.sqlRepr(value))]) # _SO_autoInitDone implies there's a cached value we also --- 576,580 ---- self._connection._SO_update(self, [(self._SO_columnDict[name].dbName, ! value)]) # _SO_autoInitDone implies there's a cached value we also *************** *** 763,771 **** def selectBy(cls, **kw): return SelectResults(cls, ! ' '.join(['%s = %s' % ! (cls._SO_columnDict[key].dbName, ! SQLBuilder.sqlRepr(value)) ! for key, value ! in kw.items()])) selectBy = classmethod(selectBy) --- 763,767 ---- def selectBy(cls, **kw): return SelectResults(cls, ! cls._connection._SO_columnClause(cls, kw)) selectBy = classmethod(selectBy) *************** *** 987,991 **** self.sourceClass = sourceClass if isinstance(clause, str) and clause == 'all': ! clause = SQLBuilder.SQLConstant('1 = 1') self.clause = clause tablesDict = SQLBuilder.tablesUsedDict(self.clause) --- 983,987 ---- self.sourceClass = sourceClass if isinstance(clause, str) and clause == 'all': ! clause = SQLBuilder.SQLTrueClause self.clause = clause tablesDict = SQLBuilder.tablesUsedDict(self.clause) |
From: <ian...@us...> - 2003-04-07 01:14:02
|
Update of /cvsroot/sqlobject/SQLObject/tests In directory sc8-pr-cvs1:/tmp/cvs-serv1207/tests Modified Files: SQLObjectTest.py test.py Log Message: * Moved all SQL into DBConnection (_SO_* methods) * A test case for RelatedJoin * Created a DBM-based backend. Passes almost all tests! * Incomplete Pickle-based backend (like DBM, only records are kept in individual files... I'm not sure I want to use it, though). Index: SQLObjectTest.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/SQLObjectTest.py,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** SQLObjectTest.py 31 Mar 2003 05:51:28 -0000 1.3 --- SQLObjectTest.py 7 Apr 2003 01:13:56 -0000 1.4 *************** *** 8,11 **** --- 8,13 ---- debug=0) + #__connection__ = DBMConnection('data') + class SQLObjectTest(unittest.TestCase): Index: test.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/test.py,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** test.py 6 Apr 2003 07:56:06 -0000 1.4 --- test.py 7 Apr 2003 01:13:57 -0000 1.5 *************** *** 9,25 **** class Counter(SQLObject): - create = """ - CREATE TABLE IF NOT EXISTS counter ( - id INT PRIMARY KEY AUTO_INCREMENT, - number INT NOT NULL - ) - """ - - drop = """ - DROP TABLE IF EXISTS counter - """ - _columns = [ ! Col('number'), ] --- 9,14 ---- class Counter(SQLObject): _columns = [ ! IntCol('number', notNull=True), ] *************** *** 57,74 **** class Counter2(SQLObject): - create = """ - CREATE TABLE IF NOT EXISTS counter2 ( - id INT PRIMARY KEY AUTO_INCREMENT, - n1 INT NOT NULL, - n2 INT NOT NULL - ) - """ - - drop = """ - DROP TABLE IF EXISTS counter2 - """ - _columns = [ ! 'n1', 'n2', ] --- 46,52 ---- class Counter2(SQLObject): _columns = [ ! IntCol('n1', notNull=True), ! IntCol('n2', notNull=True), ] |
From: <ian...@us...> - 2003-04-06 07:56:29
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv16872/SQLObject Modified Files: DBConnection.py SQLObject.py Log Message: Moved all SQL to DBConnection Index: DBConnection.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/DBConnection.py,v retrieving revision 1.10 retrieving revision 1.11 diff -C2 -d -r1.10 -r1.11 *** DBConnection.py 2 Apr 2003 18:19:26 -0000 1.10 --- DBConnection.py 6 Apr 2003 07:56:27 -0000 1.11 *************** *** 190,211 **** return q ! def createJoinSQL(self, soClass): ! result = [] ! for join in soClass._joins: ! if not join.hasIntermediateTable(): ! continue ! # This join will show up twice, in each of the ! # classes, but we only create the table once. We ! # arbitrarily create it while we're creating the ! # alphabetically earlier class. ! if join.callingClass > join.otherClass: ! continue ! result.append('CREATE TABLE %s (\n%s %s,\n%s %s\n)' ! % (join.intermediateTable, ! join.joinColumn, ! self.joinSQLType(join), ! join.otherColumn, ! self.joinSQLType(join))) ! return result def createTable(self, soClass): --- 190,203 ---- return q ! def _SO_createJoinTable(self, join): ! self.query('CREATE TABLE %s (\n%s %s,\n%s %s\n)' % ! (join.intermediateTable, ! join.joinColumn, ! self.joinSQLType(join), ! join.otherColumn, ! self.joinSQLType(join))) ! ! def _SO_dropJoinTable(self, join): ! self.query("DROP TABLE %s" % join.intermediateTable) def createTable(self, soClass): *************** *** 231,234 **** --- 223,292 ---- # that. self.query("DELETE FROM %s" % tableName) + + # The _SO_* series of methods are sorts of "friend" methods + # with SQLObject. They grab values from the SQLObject instances + # or classes freely, but keep the SQLObject class from accessing + # the database directly. This way no SQL is actually created + # in SQLObject. + + def _SO_update(self, so, values): + self.query("UPDATE %s SET %s WHERE %s = %s" % + (so._table, + ", ".join(["%s = %s" % (dbName, SQLBuilder.sqlRepr(value)) + for dbName, value in values]), + so._idName, + SQLBuilder.sqlRepr(so.id))) + + def _SO_selectOne(self, so, columnNames): + return self.queryOne("SELECT %s FROM %s WHERE %s = %s" % + (", ".join(columnNames), + so._table, + so._idName, + SQLBuilder.sqlRepr(so.id))) + + def _SO_selectOneAlt(self, cls, columnNames, column, value): + return self.queryOne("SELECT %s FROM %s WHERE %s = %s" % + (", ".join(columnNames), + cls._table, + column, + SQLBuilder.sqlRepr(value))) + + def _SO_delete(self, so): + self.query("DELETE FROM %s WHERE %s = %s" % + (so._table, + so._idName, + SQLBuilder.sqlRepr(so.id))) + + def _SO_selectJoin(self, soClass, column, value): + return self.queryAll("SELECT %s FROM %s WHERE %s = %s" % + (soClass._idName, + soClass._table, + column, + SQLBuilder.sqlRepr(value))) + + def _SO_intermediateJoin(self, table, getColumn, joinColumn, value): + return self.queryAll("SELECT %s FROM %s WHERE %s = %s" % + (getColumn, + table, + joinColumn, + SQLBuilder.sqlRepr(value))) + + def _SO_intermediateDelete(self, table, firstColumn, firstValue, + secondColumn, secondValue): + self.query("DELETE FROM %s WHERE %s = %s AND %s = %s" % + (table, + firstColumn, + SQLBuilder.sqlRepr(firstValue), + secondColumn, + SQLBuilder.sqlRepr(secondValue))) + + def _SO_intermediateInsert(self, table, firstColumn, firstValue, + secondColumn, secondValue): + self.query("INSERT INTO %s (%s, %s) VALUES (%s, %s)" % + (table, + firstColumn, + secondColumn, + SQLBuilder.sqlRepr(firstValue), + SQLBuilder.sqlRepr(secondValue))) class Transaction(object): Index: SQLObject.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLObject.py,v retrieving revision 1.12 retrieving revision 1.13 diff -C2 -d -r1.12 -r1.13 *** SQLObject.py 31 Mar 2003 05:51:28 -0000 1.12 --- SQLObject.py 6 Apr 2003 07:56:27 -0000 1.13 *************** *** 505,509 **** # And again... ! func = eval('lambda self, obj: self._SO_joinList[%i].add(self, obj)' % (len(dict['_SO_joinList'])-1)) setattr(cls, '_SO_add' + join.addRemovePrefix, func) if not hasattr(cls, 'add' + appendMeth): --- 505,509 ---- # And again... ! func = eval('lambda self, obj: self._SO_joinList[%i].add(self, obj)' % (len(cls._SO_joinList)-1)) setattr(cls, '_SO_add' + join.addRemovePrefix, func) if not hasattr(cls, 'add' + appendMeth): *************** *** 574,579 **** # There's a write lock. Not sure if I need this... self._SO_writeLock.acquire() ! self._connection.query("UPDATE %s SET %s = %s WHERE %s = %s" % (self._table, self._SO_columnDict[name].dbName, SQLBuilder.sqlRepr(value), self._idName, SQLBuilder.sqlRepr(self.id))) ! # _SO_autoInitDone implies there's a cached value we also # have to update. --- 574,581 ---- # There's a write lock. Not sure if I need this... self._SO_writeLock.acquire() ! self._connection._SO_update(self, ! [(self._SO_columnDict[name].dbName, ! SQLBuilder.sqlRepr(value))]) ! # _SO_autoInitDone implies there's a cached value we also # have to update. *************** *** 610,618 **** setattr(self, name, value) ! # Here we construct the UPDATE, simple... ! q = "UPDATE %s SET " % (self._table) ! sets = ', '.join(["%s = %s" % (self._SO_columnDict[name].dbName, SQLBuilder.sqlRepr(value)) for name, value in toUpdate.items()]) ! clause = " WHERE %s = %s" % (self._idName, self.id) ! self._connection.query(q + sets + clause) self._SO_writeLock.release() --- 612,616 ---- setattr(self, name, value) ! self._connection._SO_update(self, [(self._SO_columnDict[name].dbName, value) for name, value in toUpdate.items()]) self._SO_writeLock.release() *************** *** 632,639 **** # Database (_-using) names: dbNames = [col.dbName for col in self._columns] ! ! q = "SELECT %s FROM %s WHERE %s = %s" % \ ! (', '.join(dbNames), self._table, self._idName, self.id) ! results = self._connection.queryOne(q) assert results, "The object %s by the ID %s does not exist" % (self.__class__.__name__, self.id) --- 630,635 ---- # Database (_-using) names: dbNames = [col.dbName for col in self._columns] ! ! results = self._connection._SO_selectOne(self, dbNames) assert results, "The object %s by the ID %s does not exist" % (self.__class__.__name__, self.id) *************** *** 654,661 **** assert not self._SO_obsolete, "%s with id %s has become obsolete" \ % (self.__class__.__name__, self.id) ! q = "SELECT %s FROM %s WHERE %s = %s" % \ ! (self._SO_columnDict[name].name, self._table, self._idName, self.id) self._SO_writeLock.acquire() ! results = self._connection.queryOne(q) self._SO_writeLock.release() return results[0] --- 650,656 ---- assert not self._SO_obsolete, "%s with id %s has become obsolete" \ % (self.__class__.__name__, self.id) ! # @@: do we really need this lock? self._SO_writeLock.acquire() ! results = self._connection._SO_selectOne(self, [self._SO_columnDict[name].dbName]) self._SO_writeLock.release() return results[0] *************** *** 743,753 **** def _SO_fetchAlternateID(cls, dbIDName, value): ! q = "SELECT %s, %s FROM %s WHERE %s = %s" % \ ! (cls._idName, ! ", ".join([col.dbName for col in cls._columns]), ! cls._table, ! dbIDName, ! SQLBuilder.sqlRepr(value)) ! result = cls._connection.queryOne(q) obj = cls(result[0]) if not obj._SO_autoInitDone: --- 738,747 ---- def _SO_fetchAlternateID(cls, dbIDName, value): ! result = cls._connection._SO_selectOneAlt( ! cls, ! [cls._idName] + ! [col.dbName for col in cls._columns], ! dbIDName, ! value) obj = cls(result[0]) if not obj._SO_autoInitDone: *************** *** 778,785 **** # 3-03 @@: Should these have a connection argument? ! def dropTable(cls, ifExists=False): if ifExists and not cls._connection.tableExists(cls._table): return cls._connection.dropTable(cls._table) dropTable = classmethod(dropTable) --- 772,781 ---- # 3-03 @@: Should these have a connection argument? ! def dropTable(cls, ifExists=False, dropJoinTables=True): if ifExists and not cls._connection.tableExists(cls._table): return cls._connection.dropTable(cls._table) + if dropJoinTables: + cls.dropJoinTables(ifExists=ifExists) dropTable = classmethod(dropTable) *************** *** 793,799 **** def createJoinTables(cls, ifExists=False): - # 3-03: This should be calling _connection.createTable, - # but right now it's making tables directly. - result = [] for join in cls._joins: if not join.hasIntermediateTable(): --- 789,792 ---- *************** *** 808,820 **** cls._connection.tableExists(join.intermediateTable): continue ! self._connection.query( ! 'CREATE TABLE %s (\n%s %s,\n%s %s\n)' ! % (join.intermediateTable, ! join.joinColumn, ! self.joinSQLType(join), ! join.otherColumn, ! self.joinSQLType(join))) createJoinTables = classmethod(createJoinTables) def clearTable(cls): # 3-03 @@: Maybe this should check the cache... but it's --- 801,821 ---- cls._connection.tableExists(join.intermediateTable): continue ! cls._connection._SO_createJoinTable(join) ! createJoinTables = classmethod(createJoinTables) + def dropJoinTables(cls, ifExists=False): + for join in cls._joins: + if not join.hasIntermediateTable(): + continue + if join.callingClass > join.otherClass: + continue + if ifExists and \ + not cls._connection.tableExists(join.intermediateTable): + continue + cls._connection._SO_dropJoinTable(join) + + dropJoinTables = classmethod(dropJoinTables) + def clearTable(cls): # 3-03 @@: Maybe this should check the cache... but it's *************** *** 827,834 **** self._SO_obsolete = True self._SO_autoInitDone = False ! self._connection.query(""" ! DELETE FROM %s ! WHERE %s = %s ! """ % (self._table, self._idName, SQLBuilder.sqlRepr(self.id))) self._connection.cache.purge(self.id) --- 828,834 ---- self._SO_obsolete = True self._SO_autoInitDone = False ! # huh? ! #self._SO_delete(self) ! self._connection._SO_delete(self) self._connection.cache.purge(self.id) *************** *** 923,932 **** # so we have to fetch the actual class definition: cls = findClass(self.otherClass) ! ids = inst._connection.queryAll( ! """SELECT %s FROM %s ! WHERE %s = %s""" % ! (cls._idName, cls._table, ! self.joinColumn, ! SQLBuilder.sqlRepr(inst.id))) return [cls(id) for (id,) in ids] --- 923,930 ---- # so we have to fetch the actual class definition: cls = findClass(self.otherClass) ! ids = inst._connection._SO_selectJoin( ! cls, ! self.joinColumn, ! inst.id) return [cls(id) for (id,) in ids] *************** *** 958,992 **** cls = findClass(self.otherClass) me = findClass(self.callingClass) ! ids = me._connection.queryAll( ! """SELECT %s FROM %s ! WHERE %s = %s""" % ! (self.otherColumn, ! self.intermediateTable, ! self.joinColumn, ! SQLBuilder.sqlRepr(inst.id))) return [cls(id) for (id,) in ids] def remove(self, inst, other): me = findClass(self.callingClass) ! me._connection.query( ! """DELETE FROM %s ! WHERE %s = %s ! AND %s = %s""" % ! (self.intermediateTable, ! self.joinColumn, ! getID(inst.id), ! self.otherColumn, ! getID(other))) def add(self, inst, other): me = findClass(self.callingClass) ! me._connection.query( ! """INSERT INTO %s (%s, %s) ! VALUES (%s, %s)""" % ! (self.intermediateTable, ! self.joinColumn, ! self.otherColumn, ! getID(inst), ! getID(other))) class SelectResults(object): --- 956,983 ---- cls = findClass(self.otherClass) me = findClass(self.callingClass) ! ids = me._connection._SO_intermediateJoin( ! self.intermediateTable, ! self.otherColumn, ! self.joinColumn, ! inst.id) return [cls(id) for (id,) in ids] def remove(self, inst, other): me = findClass(self.callingClass) ! me._connection._SO_intermediateDelete( ! self.intermediateTable, ! self.joinColumn, ! getID(inst), ! self.otherColumn, ! getID(other)) def add(self, inst, other): me = findClass(self.callingClass) ! me._connection._SO_intermediateInsert( ! self.intermediateTable, ! self.joinColumn, ! getID(inst), ! self.otherColumn, ! getID(other)) class SelectResults(object): |
From: <ian...@us...> - 2003-04-06 07:56:11
|
Update of /cvsroot/sqlobject/SQLObject/tests In directory sc8-pr-cvs1:/tmp/cvs-serv16742/tests Modified Files: test.py Log Message: Added a related join test Index: test.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/test.py,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** test.py 31 Mar 2003 05:51:28 -0000 1.3 --- test.py 6 Apr 2003 07:56:06 -0000 1.4 *************** *** 171,174 **** --- 171,220 ---- ######################################## + ## Joins + ######################################## + + class PersonJoiner(SQLObject): + + _columns = [StringCol('name', length=40, alternateID=True)] + _joins = [RelatedJoin('AddressJoiner')] + + class AddressJoiner(SQLObject): + + _columns = [StringCol('zip', length=5, alternateID=True)] + _joins = [RelatedJoin('PersonJoiner')] + + class JoinTest(SQLObjectTest): + + classes = [PersonJoiner, AddressJoiner] + + def inserts(self): + for n in ['bob', 'tim', 'jane', 'joe', 'fred', 'barb']: + PersonJoiner.new(name=n) + for z in ['11111', '22222', '33333', '44444']: + AddressJoiner.new(zip=z) + + def testJoin(self): + b = PersonJoiner.byName('bob') + self.assertEqual(b.addressJoiners, []) + z = AddressJoiner.byZip('11111') + b.addAddressJoiner(z) + self.assertZipsEqual(b.addressJoiners, ['11111']) + self.assertNamesEqual(z.personJoiners, ['bob']) + z2 = AddressJoiner.byZip('22222') + b.addAddressJoiner(z2) + self.assertZipsEqual(b.addressJoiners, ['11111', '22222']) + self.assertNamesEqual(z2.personJoiners, ['bob']) + b.removeAddressJoiner(z) + self.assertZipsEqual(b.addressJoiners, ['22222']) + self.assertNamesEqual(z.personJoiners, []) + + def assertZipsEqual(self, zips, dest): + self.assertEqual([a.zip for a in zips], dest) + + def assertNamesEqual(self, people, dest): + self.assertEqual([p.name for p in people], dest) + + + ######################################## ## Run from command-line: ######################################## |
From: <ian...@us...> - 2003-04-03 19:37:03
|
Update of /cvsroot/sqlobject/SQLObject/docs In directory sc8-pr-cvs1:/tmp/cvs-serv6240/docs Modified Files: SQLObject.txt Log Message: Added note about SQLite Index: SQLObject.txt =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/docs/SQLObject.txt,v retrieving revision 1.6 retrieving revision 1.7 diff -C2 -d -r1.6 -r1.7 *** SQLObject.txt 13 Mar 2003 20:05:28 -0000 1.6 --- SQLObject.txt 3 Apr 2003 19:36:59 -0000 1.7 *************** *** 729,732 **** --- 729,748 ---- console. + SQLite + ------ + + SQLite doesn't support a great deal of concurrency. For instance, say + you want to delete a bunch of rows:: + + for p in Person.select(Person.q.firstName == 'Bob'): + p.destroy() + + Because the ``select`` is an iterator, and keeps a connection open, + when you do the ``p.destroy()`` you'll get an exception, because + SQLite doesn't support this concurrency. Instead, do:: + + for p in list(Person.select(Person.q.firstName == 'Bob')): + p.destroy() + Exported Symbols ================ |
From: <ian...@us...> - 2003-04-02 18:19:30
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv11549/SQLObject Modified Files: DBConnection.py Log Message: Improved SQLite support (untested!) Index: DBConnection.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/DBConnection.py,v retrieving revision 1.9 retrieving revision 1.10 diff -C2 -d -r1.9 -r1.10 *** DBConnection.py 31 Mar 2003 05:51:28 -0000 1.9 --- DBConnection.py 2 Apr 2003 18:19:26 -0000 1.10 *************** *** 402,406 **** def createIDColumn(self, soClass): ! return '%s INT NOT NULL' % soClass._idName def joinSQLType(self): --- 402,406 ---- def createIDColumn(self, soClass): ! return '%s INTEGER PRIMARY KEY' % soClass._idName def joinSQLType(self): *************** *** 408,411 **** def tableExists(self, tableName): ! # @@: obvious not implemented ! return False --- 408,412 ---- def tableExists(self, tableName): ! result = self.queryOne("SELECT tbl_name FROM sqlite_master WHERE type='table' AND tbl_name = '%s'" % tableName) ! # turn it into a boolean: ! return not not result |
From: <ian...@us...> - 2003-04-01 16:51:33
|
Update of /cvsroot/sqlobject/SQLObject/SQLObject In directory sc8-pr-cvs1:/tmp/cvs-serv12462 Modified Files: SQLBuilder.py Log Message: Removed mxDateTime requirement (still used if available) Index: SQLBuilder.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/SQLObject/SQLBuilder.py,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -C2 -d -r1.1.1.1 -r1.2 *** SQLBuilder.py 26 Feb 2003 23:59:42 -0000 1.1.1.1 --- SQLBuilder.py 1 Apr 2003 16:51:28 -0000 1.2 *************** *** 87,93 **** from mx.DateTime import DateTimeType except ImportError: ! import DateTime.ISO ! origISOStr = DateTime.ISO.strGMT ! from DateTime import DateTimeType import re --- 87,97 ---- from mx.DateTime import DateTimeType except ImportError: ! try: ! import DateTime.ISO ! origISOStr = DateTime.ISO.strGMT ! from DateTime import DateTimeType ! except ImportError: ! origISOStr = None ! DateTimeType = None import re *************** *** 112,116 **** elif t is type(1.0): return repr(obj) ! elif t is DateTimeType: return "'%s'" % isoStr(obj) elif obj is None: --- 116,120 ---- elif t is type(1.0): return repr(obj) ! elif DateTimeType and t is DateTimeType: return "'%s'" % isoStr(obj) elif obj is None: |
From: <ian...@us...> - 2003-03-31 05:51:31
|
Update of /cvsroot/sqlobject/SQLObject/tests In directory sc8-pr-cvs1:/tmp/cvs-serv12426/tests Modified Files: SQLObjectTest.py test.py Log Message: Support for reading a class from a table definition (for MySQL) Index: SQLObjectTest.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/SQLObjectTest.py,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** SQLObjectTest.py 31 Mar 2003 02:00:58 -0000 1.2 --- SQLObjectTest.py 31 Mar 2003 05:51:28 -0000 1.3 *************** *** 15,28 **** def setUp(self): unittest.TestCase.setUp(self) #__connection__.debug = self.debugSQL ! for c in self.classes: if hasattr(c, 'drop'): __connection__.query(c.drop) ! else: c.dropTable(ifExists=True) if hasattr(c, 'create'): __connection__.query(c.create) ! else: c.createTable(ifExists=True) #__connection__.debug = 0 --- 15,31 ---- def setUp(self): + if self.debugSQL: + print + print '#' * 70 unittest.TestCase.setUp(self) #__connection__.debug = self.debugSQL ! for c in self.classes + [self]: if hasattr(c, 'drop'): __connection__.query(c.drop) ! elif hasattr(c, 'dropTable'): c.dropTable(ifExists=True) if hasattr(c, 'create'): __connection__.query(c.create) ! elif hasattr(c, 'createTable'): c.createTable(ifExists=True) #__connection__.debug = 0 Index: test.py =================================================================== RCS file: /cvsroot/sqlobject/SQLObject/tests/test.py,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** test.py 31 Mar 2003 02:00:58 -0000 1.2 --- test.py 31 Mar 2003 05:51:28 -0000 1.3 *************** *** 1,4 **** --- 1,5 ---- from SQLObjectTest import * from SQLObject import * + from mx import DateTime ######################################## *************** *** 119,122 **** --- 120,124 ---- self.assertEqual([p.name for p in Person.select('all')], ['bob', 'jake', 'jane', 'robert', 'tim']) + Person.delColumn(nickname, changeSchema=True) def testDynamicJoin(self): *************** *** 132,136 **** --- 134,172 ---- self.assertEqual([p.phone for p in Person.selectBy(name='tim')[0].phones], ['555-555-5555', '555-394-2930']) + Phone.delColumn(col, changeSchema=True) + Person.delJoin(join) + + ######################################## + ## Auto class generation + ######################################## + + class AutoTest(SQLObjectTest): + + create = """ + CREATE TABLE IF NOT EXISTS auto_test ( + id INT AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(100), + last_name VARCHAR(200) NOT NULL, + age INT, + created DATETIME NOT NULL, + happy char(1) DEFAULT 'Y' NOT NULL + ) + """ + + drop = """ + DROP TABLE IF EXISTS auto_test + """ + + def testClassCreate(self): + class AutoTest(SQLObject): + _fromDatabase = True + AutoTest.new(firstName='john', + lastName='doe', + age=10, + created=DateTime.now()) + AutoTest.new(firstName='jane', + lastName='doe', + happy='N') ######################################## |