sqlobject-cvs Mailing List for SQLObject (Page 169)
SQLObject is a Python ORM.
Brought to you by:
ianbicking,
phd
You can subscribe to this list here.
2003 |
Jan
|
Feb
|
Mar
(9) |
Apr
(74) |
May
(29) |
Jun
(16) |
Jul
(28) |
Aug
(10) |
Sep
(57) |
Oct
(9) |
Nov
(29) |
Dec
(12) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2004 |
Jan
(7) |
Feb
(14) |
Mar
(6) |
Apr
(3) |
May
(12) |
Jun
(34) |
Jul
(9) |
Aug
(29) |
Sep
(22) |
Oct
(2) |
Nov
(15) |
Dec
(52) |
2005 |
Jan
(47) |
Feb
(78) |
Mar
(14) |
Apr
(35) |
May
(33) |
Jun
(16) |
Jul
(26) |
Aug
(63) |
Sep
(40) |
Oct
(96) |
Nov
(96) |
Dec
(123) |
2006 |
Jan
(159) |
Feb
(144) |
Mar
(64) |
Apr
(31) |
May
(88) |
Jun
(48) |
Jul
(16) |
Aug
(64) |
Sep
(87) |
Oct
(92) |
Nov
(56) |
Dec
(76) |
2007 |
Jan
(94) |
Feb
(103) |
Mar
(126) |
Apr
(123) |
May
(85) |
Jun
(11) |
Jul
(130) |
Aug
(47) |
Sep
(65) |
Oct
(70) |
Nov
(12) |
Dec
(11) |
2008 |
Jan
(30) |
Feb
(55) |
Mar
(88) |
Apr
(20) |
May
(50) |
Jun
|
Jul
(38) |
Aug
(1) |
Sep
(9) |
Oct
(5) |
Nov
(6) |
Dec
(39) |
2009 |
Jan
(8) |
Feb
(16) |
Mar
(3) |
Apr
(33) |
May
(44) |
Jun
(1) |
Jul
(10) |
Aug
(33) |
Sep
(74) |
Oct
(22) |
Nov
|
Dec
(15) |
2010 |
Jan
(28) |
Feb
(22) |
Mar
(46) |
Apr
(29) |
May
(1) |
Jun
(1) |
Jul
(27) |
Aug
(8) |
Sep
(5) |
Oct
(33) |
Nov
(24) |
Dec
(41) |
2011 |
Jan
(4) |
Feb
(12) |
Mar
(35) |
Apr
(29) |
May
(19) |
Jun
(16) |
Jul
(32) |
Aug
(25) |
Sep
(5) |
Oct
(11) |
Nov
(21) |
Dec
(12) |
2012 |
Jan
(3) |
Feb
(4) |
Mar
(20) |
Apr
(4) |
May
(25) |
Jun
(13) |
Jul
|
Aug
|
Sep
(2) |
Oct
(25) |
Nov
(9) |
Dec
(1) |
2013 |
Jan
(6) |
Feb
(8) |
Mar
|
Apr
(10) |
May
(31) |
Jun
(7) |
Jul
(18) |
Aug
(33) |
Sep
(4) |
Oct
(16) |
Nov
|
Dec
(27) |
2014 |
Jan
(2) |
Feb
|
Mar
|
Apr
(11) |
May
(39) |
Jun
(8) |
Jul
(11) |
Aug
(4) |
Sep
|
Oct
(27) |
Nov
|
Dec
(71) |
2015 |
Jan
(17) |
Feb
(47) |
Mar
(33) |
Apr
|
May
|
Jun
(9) |
Jul
(7) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(8) |
2016 |
Jan
(4) |
Feb
(4) |
Mar
|
Apr
|
May
(12) |
Jun
(7) |
Jul
(9) |
Aug
(31) |
Sep
(8) |
Oct
(3) |
Nov
(15) |
Dec
(1) |
2017 |
Jan
(13) |
Feb
(7) |
Mar
(14) |
Apr
(8) |
May
(10) |
Jun
(4) |
Jul
(2) |
Aug
(1) |
Sep
|
Oct
(8) |
Nov
(4) |
Dec
(5) |
2018 |
Jan
(2) |
Feb
(8) |
Mar
|
Apr
(4) |
May
|
Jun
(6) |
Jul
|
Aug
(1) |
Sep
|
Oct
|
Nov
(1) |
Dec
|
2019 |
Jan
(1) |
Feb
(16) |
Mar
(1) |
Apr
(3) |
May
(5) |
Jun
(1) |
Jul
|
Aug
|
Sep
(2) |
Oct
|
Nov
(1) |
Dec
(3) |
2020 |
Jan
|
Feb
|
Mar
|
Apr
(1) |
May
(1) |
Jun
|
Jul
|
Aug
(1) |
Sep
|
Oct
(2) |
Nov
|
Dec
(2) |
2021 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(1) |
Nov
(1) |
Dec
|
2022 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
(6) |
Oct
(1) |
Nov
(1) |
Dec
(4) |
2023 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
(3) |
Sep
(2) |
Oct
(2) |
Nov
(4) |
Dec
|
2024 |
Jan
|
Feb
(2) |
Mar
|
Apr
|
May
|
Jun
|
Jul
(1) |
Aug
|
Sep
(1) |
Oct
|
Nov
|
Dec
(9) |
2025 |
Jan
|
Feb
(4) |
Mar
(2) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: <sub...@co...> - 2005-02-20 04:45:51
|
Author: ianb Date: 2005-02-20 04:45:46 +0000 (Sun, 20 Feb 2005) New Revision: 623 Added: trunk/SQLObject/sqlobject/tests/test_reparent_sqlmeta.py Log: Added test for the new softer inheritance requirements for sqlmeta Added: trunk/SQLObject/sqlobject/tests/test_reparent_sqlmeta.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_reparent_sqlmeta.py 2005-02-20 04:35:41 UTC (rev 622) +++ trunk/SQLObject/sqlobject/tests/test_reparent_sqlmeta.py 2005-02-20 04:45:46 UTC (rev 623) @@ -0,0 +1,30 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +real_sqlmeta = sqlmeta + +class Reparented1(SQLObject): + + class sqlmeta: + table = 'reparented1' + + dummy = StringCol() + +class Reparented2(SQLObject): + class sqlmeta(object): + def setClass(cls, soClass): + # Well, it's pretty hard to call the superclass method + # when it's a classmethod and it's not actually your + # *current* superclass. Sigh + real_sqlmeta.setClass.im_func(cls, soClass) + cls.worked = True + setClass = classmethod(setClass) + + dummy = StringCol() + +def test_reparented(): + setupClass([Reparented1, Reparented2]) + assert Reparented1.sqlmeta.table == 'reparented1' + assert issubclass(Reparented1.sqlmeta, real_sqlmeta) + assert issubclass(Reparented2.sqlmeta, real_sqlmeta) + assert Reparented2.sqlmeta.worked |
From: <sub...@co...> - 2005-02-20 04:35:47
|
Author: ianb Date: 2005-02-20 04:35:41 +0000 (Sun, 20 Feb 2005) New Revision: 622 Modified: trunk/SQLObject/sqlobject/main.py Log: Simplified the cleanDeprecatedAttrs method Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-20 04:29:35 UTC (rev 621) +++ trunk/SQLObject/sqlobject/main.py 2005-02-20 04:35:41 UTC (rev 622) @@ -498,31 +498,15 @@ _lazyUpdate = _sqlmeta_attr('lazyUpdate', 2) def _cleanDeprecatedAttrs(cls, new_attrs): - if new_attrs.has_key('_table'): - deprecated("'_table' is deprecated; please set the 'table' " - "attribute in sqlmeta instead", level=2) - cls.sqlmeta.table = cls._table - del cls._table + for attr in ['_table', '_lazyUpdate', '_style', '_idName']: + if new_attrs.has_key(attr): + new_name = attr[1:] + deprecated("'%s' is deprecated; please set the '%s' " + "attribute in sqlmeta instead" % + (attr, new_name), level=2) + setattr(cls.sqlmeta, new_name, new_attrs[attr]) + delattr(cls, attr) - if new_attrs.has_key('_lazyUpdate'): - deprecated("'_lazyUpdate' is deprecated; please set the " - "'lazyUpdate' attribute in sqlmeta instead", - level=2) - cls.sqlmeta.lazyUpdate = cls._lazyUpdate - del cls._lazyUpdate - - if new_attrs.has_key('_style'): - deprecated("'_style' is deprecated; please set the 'style' " - "attribute in sqlmeta instead", level=2) - cls.sqlmeta.style = cls._style - del cls._style - - if new_attrs.has_key('_idName'): - deprecated("'_idName' is deprecated; please set the 'idName' " - "attribute in sqlmeta instead", level=2) - cls.sqlmeta.idName = cls._idName - del cls._idName - _cleanDeprecatedAttrs = classmethod(_cleanDeprecatedAttrs) def get(cls, id, connection=None, selectResults=None): |
From: <sub...@co...> - 2005-02-20 04:29:41
|
Author: ianb Date: 2005-02-20 04:29:35 +0000 (Sun, 20 Feb 2005) New Revision: 621 Modified: trunk/SQLObject/sqlobject/main.py trunk/SQLObject/sqlobject/tests/test_lazy.py Log: * Moved lazyUpdate to sqlmeta * Moved the checking-and-moving of deprecated attributes into a separate method (out of __classinit__) Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-19 21:06:27 UTC (rev 620) +++ trunk/SQLObject/sqlobject/main.py 2005-02-20 04:29:35 UTC (rev 621) @@ -151,6 +151,7 @@ table = None idName = None style = None + lazyUpdate = False __metaclass__ = declarative.DeclarativeMeta @@ -258,8 +259,6 @@ # when necessary: (bad clever? maybe) _expired = False - _lazyUpdate = False - # This function is used to coerce IDs into the proper format, # so you should replace it with str, or another function, if you # aren't using integer IDs @@ -283,6 +282,7 @@ # This is true if we're initializing the SQLObject class, # instead of a subclass: is_base = cls.__bases__ == (object,) + #assert cls.__name__ != 'Reparented1' if (not new_attrs.has_key('sqlmeta') and not is_base): @@ -291,6 +291,31 @@ # subclass: #cls.sqlmeta = cls.sqlmeta.clone() cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {}) + if not issubclass(cls.sqlmeta, sqlmeta): + # We allow no superclass and an object superclass, instead + # of inheriting from sqlmeta; but in that case we replace + # the class and just move over its attributes: + assert cls.sqlmeta.__bases__ in ((), (object,)), ( + "If you do not inherit your sqlmeta class from " + "sqlobject.sqlmeta, it must not inherit from any other " + "class (your sqlmeta inherits from: %s)" + % cls.sqlmeta.__bases__) + for base in cls.__bases__: + superclass = getattr(base, 'sqlmeta', None) + if superclass: + break + else: + assert 0, ( + "No sqlmeta class could be found in any superclass " + "(while fixing up sqlmeta %r inheritance)" + % cls.sqlmeta) + values = dict(cls.sqlmeta.__dict__) + for key in values.keys(): + if key.startswith('__') and key.endswith('__'): + # Magic values shouldn't be passed through: + del values[key] + cls.sqlmeta = type('sqlmeta', (superclass,), values) + cls.sqlmeta.setClass(cls) implicitColumns = [] @@ -313,11 +338,8 @@ delattr(cls, attr) continue - if (new_attrs.has_key('_table') and not is_base): - deprecated("'_table' is deprecated; please set the 'table' " - "attribute in sqlmeta instead", level=2) - cls.sqlmeta.table = cls._table - del cls._table + if not is_base: + cls._cleanDeprecatedAttrs(new_attrs) if new_attrs.has_key('_connection'): connection = new_attrs['_connection'] @@ -402,12 +424,6 @@ if connection or not hasattr(cls, '_connection'): cls.setConnection(connection) - if (new_attrs.has_key('_style') and not is_base): - deprecated("'_style' is deprecated; please set the 'style' " - "attribute in sqlmeta instead", level=2) - cls.sqlmeta.style = cls._style - del cls._style - # plainSetters are columns that haven't been overridden by the # user, so we can contact the database directly to set them. # Note that these can't set these in the SQLObject class @@ -425,12 +441,6 @@ cls._SO_columnDict = {} cls._SO_columns = [] - if (new_attrs.has_key('_idName') and not is_base): - deprecated("'_idName' is deprecated; please set the 'idName' " - "attribute in sqlmeta instead", level=2) - cls.sqlmeta.idName = cls._idName - del cls._idName - # We have to check if there are columns in the inherited # _columns where the attribute has been set to None in this # class. If so, then we need to remove that column from @@ -485,7 +495,36 @@ _style = _sqlmeta_attr('style', 2) _table = _sqlmeta_attr('table', 2) _idName = _sqlmeta_attr('idName', 2) + _lazyUpdate = _sqlmeta_attr('lazyUpdate', 2) + def _cleanDeprecatedAttrs(cls, new_attrs): + if new_attrs.has_key('_table'): + deprecated("'_table' is deprecated; please set the 'table' " + "attribute in sqlmeta instead", level=2) + cls.sqlmeta.table = cls._table + del cls._table + + if new_attrs.has_key('_lazyUpdate'): + deprecated("'_lazyUpdate' is deprecated; please set the " + "'lazyUpdate' attribute in sqlmeta instead", + level=2) + cls.sqlmeta.lazyUpdate = cls._lazyUpdate + del cls._lazyUpdate + + if new_attrs.has_key('_style'): + deprecated("'_style' is deprecated; please set the 'style' " + "attribute in sqlmeta instead", level=2) + cls.sqlmeta.style = cls._style + del cls._style + + if new_attrs.has_key('_idName'): + deprecated("'_idName' is deprecated; please set the 'idName' " + "attribute in sqlmeta instead", level=2) + cls.sqlmeta.idName = cls._idName + del cls._idName + + _cleanDeprecatedAttrs = classmethod(_cleanDeprecatedAttrs) + def get(cls, id, connection=None, selectResults=None): assert id is not None, 'None is not a possible id for %s' % cls.__name @@ -823,7 +862,7 @@ self._SO_writeLock.release() def sync(self): - if self._lazyUpdate and self._SO_createValues: + if self.sqlmeta.lazyUpdate and self._SO_createValues: self.syncUpdate() self._SO_writeLock.acquire() try: @@ -877,7 +916,7 @@ dbValue = value if toPython: value = toPython(dbValue, self._SO_validatorState) - if self._SO_creating or self._lazyUpdate: + if self._SO_creating or self.sqlmeta.lazyUpdate: self.dirty = True self._SO_createValues[name] = dbValue setattr(self, instanceName(name), value) @@ -905,7 +944,7 @@ kw = dict(filter(f_is_column, items)) # _SO_creating is special, see _SO_setValue - if self._SO_creating or self._lazyUpdate: + if self._SO_creating or self.sqlmeta.lazyUpdate: for name, value in kw.items(): fromPython = getattr(self, '_SO_fromPython_%s' % name, None) if fromPython: @@ -1072,7 +1111,7 @@ # Doesn't have to be threadsafe because we're still in # new(), which doesn't need to be threadsafe. self.dirty = False - if not self._lazyUpdate: + if not self.sqlmeta.lazyUpdate: del self._SO_createValues else: self._SO_createValues = {} Modified: trunk/SQLObject/sqlobject/tests/test_lazy.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_lazy.py 2005-02-19 21:06:27 UTC (rev 620) +++ trunk/SQLObject/sqlobject/tests/test_lazy.py 2005-02-20 04:29:35 UTC (rev 621) @@ -7,7 +7,8 @@ class Lazy(SQLObject): - _lazyUpdate = True + class sqlmeta: + lazyUpdate = True name = StringCol() other = StringCol(default='nothing') third = StringCol(default='third') |
From: <sub...@co...> - 2005-02-19 21:06:33
|
Author: ianb Date: 2005-02-19 21:06:27 +0000 (Sat, 19 Feb 2005) New Revision: 620 Modified: trunk/SQLObject/sqlobject/main.py Log: Removed a redundant attribute Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-18 19:43:15 UTC (rev 619) +++ trunk/SQLObject/sqlobject/main.py 2005-02-19 21:06:27 UTC (rev 620) @@ -252,8 +252,6 @@ _fromDatabase = False - _style = None - _registry = None # Default is false, but we set it to true for the *instance* |
From: <sub...@co...> - 2005-02-17 16:00:29
|
Author: phd Date: 2005-02-17 16:00:19 +0000 (Thu, 17 Feb 2005) New Revision: 615 Modified: trunk/SQLObject/docs/FAQ.txt Log: Mentioned BLOBCol and PickleCol in section "Binary values". Modified: trunk/SQLObject/docs/FAQ.txt =================================================================== --- trunk/SQLObject/docs/FAQ.txt 2005-02-17 08:12:15 UTC (rev 614) +++ trunk/SQLObject/docs/FAQ.txt 2005-02-17 16:00:19 UTC (rev 615) @@ -107,9 +107,9 @@ anyway). -There is also another kind of inheritance. See Inheritance.txt_ +There is also another kind of inheritance. See Inheritance.html_ -.. _Inheritance.txt: Inheritance.txt +.. _Inheritance.html: Inheritance.html Composite/Compound Attributes @@ -188,7 +188,14 @@ have a widely-implemented way to express binaries as literals, and there's differing support in database. -A possible way to keep this data in a database is by using encoding. +The module sqlobject.col defines validators and column classes that +to some extent support binary values. There is BLOBCol that extends +StringCol and allow to store binary values; currently it works only +with PostgreSQL and MySQL. PickleCol extends BLOBCol and allows to store +any object in the column; the column, naturally, pickles the object upon +assignment and unpickles it upon retrieving the data from the DB. + +Another possible way to keep binary data in a database is by using encoding. Base 64 is a good encoding, reasonably compact but also safe. As an example, imagine you want to store images in the database: |
From: <sub...@co...> - 2005-02-17 08:12:18
|
Author: ianb Date: 2005-02-17 08:12:15 +0000 (Thu, 17 Feb 2005) New Revision: 614 Added: trunk/SQLObject/docs/SQLObjectClasses.txt trunk/SQLObject/docs/SQLObjectCol.txt trunk/SQLObject/docs/SQLObjectComparison.txt trunk/SQLObject/docs/SQLObjectCustomization.txt trunk/SQLObject/docs/SQLObjectDBConnection.txt trunk/SQLObject/docs/SQLObjectDeclaration.txt trunk/SQLObject/docs/SQLObjectDynamic.txt trunk/SQLObject/docs/SQLObjectExported.txt trunk/SQLObject/docs/SQLObjectFuture.txt trunk/SQLObject/docs/SQLObjectGeneration.txt trunk/SQLObject/docs/SQLObjectIntro.txt trunk/SQLObject/docs/SQLObjectLazy.txt trunk/SQLObject/docs/SQLObjectLegacy.txt trunk/SQLObject/docs/SQLObjectManyToMany.txt trunk/SQLObject/docs/SQLObjectOneToMany.txt trunk/SQLObject/docs/SQLObjectRelationships.txt trunk/SQLObject/docs/SQLObjectRequirements.txt trunk/SQLObject/docs/SQLObjectSelect.txt trunk/SQLObject/docs/SQLObjectTransactions.txt trunk/SQLObject/docs/SQLObjectUse.txt Modified: trunk/SQLObject/docs/SQLObject.txt Log: Split SQLObject.txt into bite-sized chunks. These chunks will become documents in the Wiki. Modified: trunk/SQLObject/docs/SQLObject.txt =================================================================== --- trunk/SQLObject/docs/SQLObject.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObject.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -4,573 +4,24 @@ .. contents:: Contents: -Author, Site, and License -========================= +.. include:: SQLObjectIntro.txt +.. include:: SQLObjectRequirements.txt +.. include:: SQLObjectComparison.txt +.. include:: SQLObjectFuture.txt -SQLObject is by Ian Bicking (ia...@co...). The website -is sqlobject.org__. - -__ http://sqlobject.org - -The code is licensed under the `Lesser General Public License`_ -(LGPL). - -.. _`Lesser General Public License`: http://www.gnu.org/copyleft/lesser.html - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -Introduction -============ - -SQLObject is an *object-relational mapper*. It allows you to -translate RDBMS table rows into Python objects, and manipulate those -objects to transparently manipulate the database. - -In using SQLObject, you will create a class definition that will -describe how the object translates to the database table. SQLObject -will produce the code to access the database, and update the database -with your changes. The generated interface looks similar to any other -interface, and callers need not be aware of the database backend. - -SQLObject also includes a novel feature to avoid generating, -textually, your SQL queries. This also allows non-SQL databases to be -used with the same query syntax. - -Requirements -============ - -Currently SQLObject supports MySQL_, PostgreSQL_ (via ``psycopg``), -SQLite_, Firebird_, Sybase_, and `MAX DB`_ (also known as SAP DB). - -.. _PostgreSQL: http://postgresql.org -.. _SQLite: http://sqlite.org -.. _Firebird: http://firebird.sourceforge.net - -Python 2.2 or higher is required. SQLObject makes extensive use of -new-style classes. - -Compared To Other Database Wrappers -=================================== - -There are several object-relational mappers (ORM) for Python. I -honestly can't comment deeply on the quality of those packages, but -I'll try to place SQLObject in perspective. - -SQLObject uses new-style classes extensively. The resultant objects -have a new-style feel as a result -- setting attributes has side -effects (it changes the database), and defining classes has side -effects (through the use of metaclasses). Attributes are generally -exposed, not marked private, knowing that they can be made dynamic -or write-only later. - -SQLObject creates objects that feel similar to normal Python objects -(with the semantics of new-style classes). An attribute attached to a -column doesn't look different than an attribute that's attached to a -file, or an attribute that is calculated. It is a specific goal that -you be able to change the database without changing the interface, -including changing the scope of the database, making it more or less -prominent as a storage mechanism. - -This is in contrast to some ORMs that provide a dictionary-like -interface to the database (for example, PyDO_). The dictionary -interface distinguishes the row from a normal Python object. I also -don't care for the use of strings where an attribute seems more -natural -- columns are limited in number and predefined, just like -attributes. (Note: newer version of PyDO apparently allow attribute -access as well) - -.. _PyDO: http://skunkweb.sourceforge.net/pydo.html - -SQLObject is, to my knowledge, unique in using metaclasses to -facilitate this seemless integration. Some other ORMs use code -generation to create an interface, expressing the schema in a CSV or -XML file (for example, MiddleKit, part of Webware_). By using -metaclasses you are able to comfortably define your schema in the -Python source code. No code generation, no weird tools, no -compilation step. - -.. _Webware: http://webware.sourceforge.net - -SQLObject provides a strong database abstraction, allowing -cross-database compatibility (so long as you don't sidestep -SQLObject). - -SQLObject has joins, one-to-many, and many-to-many, something which -many ORMs do not have. The join system is also intended to be -extensible. - -You can map between database names and Python attribute and class -names; often these two won't match, or the database style would be -inappropriate for a Python attribute. This way your database schema -does not have to be designed with SQLObject in mind, and the resulting -classes do not have to inherit the database's naming schemes. - -Future -====== - -Here are some things I plan: - -* More databases supported. There has been interest and some work in - the progress for Oracle, Sybase, and MS-SQL support. -* Better transaction support -- right now you can use transactions - for the database, but the object isn't transaction-aware, so - non-database persistence won't be able to be rolled back. -* Optimistic locking and other techniques to handle concurrency. -* Profile of SQLObject performance, so that I can identify bottlenecks. -* Increase hooks with FormEncode (unreleased) validation and form - generation package, so SQLObject classes (read: schemas) can be - published for editing more directly and easily. -* Automatic joins in select queries. -* More kinds of joins, and more powerful join results (closer to how - `select` works). - -See also the `Plan for 0.6`__. - -.. __: Plan06.html - Using SQLObject: An Introduction ================================ Let's start off quickly... -Declaring the Class -------------------- +.. include:: SQLObjectDeclaration.txt +.. include:: SQLObjectUse.txt +.. include:: SQLObjectLazy.txt +.. include:: SQLObjectOneToMany.txt +.. include:: SQLObjectManyToMany.txt +.. include:: SQLObjectSelect.txt +.. include:: SQLObjectCustomization.txt -To begin with, let's make a database connection. Choose from one of -`MySQLConnection`, `PostgresConnection`, `SQLiteConnection`, and -`FirebirdConnection`, depending on what database you use. - -.. raw:: html - :file: ../examples/snippets/connections.html - -The rest of this will be written more-or-less in a database-agnostic -manner, using the connection you define. Use `SQLite` if you don't -have another database installed or ready -- it requires PySQLite_, but -doesn't require a client/server setup. - -.. _PySQLite: http://pysqlite.sourceforge.net/ - -We'll develop a simple addressbook-like database. We could create the -tables ourselves, and just have SQLObject access those tables, but for -now we'll let SQLObject do that work. First, the class: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1.html - -Many basic table schemas won't be any more complicated than that. -The special attribute `_connection` defines the connection we should -use (you can also set a module-level variable `__connection__` which -would automatically be picked up if you don't specify `_connection`). - -.. warning:: - The `__connection__` magic variable can be a little fragile -- it - has to be defined before the class is defined. This means it - *must* be assigned above the ``class ...:`` line. - -`firstName`, `middleInitial`, and `lastName` are all columns in the -database. The general schema implied by this class definition is: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-schema-person1.html - -This is for MySQL. The schema for other databases looks slightly -different (especially the ``id`` column). You'll notice the names -were changed from mixedCase to underscore_separated -- this is done by -the `style object`_. There are a variety of ways to handle that names -that don't fit conventions (see `Irregular Naming`_). - -.. _`style object`: `Changing the Naming Style`_ - -The tables don't yet exist. We'll let SQLObject create them: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1-create.html - -We can change the type of the various columns by using something other -than `StringCol`, or using different arguments. More about this in -`Subclasses of Col`_. - -If you don't want to do table creation (you already have tables, or -you want to create the tables yourself), you can just use the vague -`Col` class. SQLObject doesn't do much type checking, allowing the -database and the adapter to handle most of the type conversion. -Databases generally do their own type coercion on inputs. - -You'll note that the ``id`` column is not given in the class -definition, it is implied. For MySQL databases it should be defined -as ``INT PRIMARY KEY AUTO_INCREMENT``, in Postgres ``SERIAL PRIMARY -KEY``, and in SQLite as ``INTEGER PRIMARY KEY``. You can `override the -name`__, but some integer primary key must exist (though `you can use -non-integer keys`_ with some extra effort). - -__ idName_ -.. _`you can use non-integer keys`: `Non-Integer Keys`_ - -Using the Class ---------------- - -Now that you have a class, how will you use it? We'll be considering -the class defined above. - -You can use the class method `.get()` to fetch instances that -already exist. So if you wanted to fetch the Person by id 10, you'd -call ``Person.get(10)``. - -.. warning:: - This is a change from SQLObject 0.5 -- before the standard - constructor fetched rows from the database, and the `.new()` - method created new rows. Now SQLObject is more like Python, where - the class constructor creates a new object/row, and the `.get()` - method fetches a row. - -To create a new object (and row), use class instantiation. In this -case you might call ``Person.new(firstName="John", lastName="Doe")``. -If you had left out ``firstName`` or ``lastName`` you would have -gotten an error, as no default was given for these columns -(``middleInitial`` has a default, so it will be set to ``NULL``, the -SQL equivalent of ``None``). - -When you create an object, it is immediately inserted into the -database. SQLObject generally uses the database as immediate storage. - -Here's an example of using the class: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1-use.html - -You'll note that columns are accessed like attributes. (This uses the -``property`` feature of Python 2.2, so that retrieving and setting -these attributes executes code). You'll also note that objects are -unique -- there is generally only one ``Person`` instance of a -particular id in memory at any one time. If you ask for 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). This isn't true if -you're using transactions_. - -To get an idea of what's happening behind the surface, I'll give the -same actions with the SQL that is sent, along with some commentary: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1-use-debug.html - -Hopefully you see that the SQL that gets sent is pretty clear and -predictable. 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 -individually, you can assign a number of them using the ``set`` -method, like: - -.. raw:: html - :file: ../examples/snippets/simpleaddress-person1-use-set.html - -This will send only one ``UPDATE`` statement. You can also use `set` -with non-database properties (there's no benefit, but it helps hide -the difference between database and non-database attributes). - -Lazy Updates ------------- - -By default SQLObject sends an ``UPDATE`` to the database for every -attribute you set, or everytime you call ``.set()``. If you want to -avoid this many updates, add ``_lazyUpdate = True`` to your class -definition. Then updates will only be written to the database when -you call ``inst.syncUpdate()`` or ``obj.sync()``: ``.sync()`` also -refetches the data from the database, which ``.syncUpdate()`` does not -do. - -When enabled instances will have a property ``dirty``, which indicates -if there are pending updates. Inserts are still done immediately. - -One-to-Many Relationships -------------------------- - -A real address book should have people, but also addresses. These -examples are in ``personaddress.py`` - -First, let's define the new address table. People can have multiple -addresses, of course: - -.. raw:: html - :file: ../examples/snippets/address-address.html - -Note the column ``person = ForeignKey("Person")``. This is a -reference to a `Person` object. We refer to other classes by name -(with a string) to avoid circular dependencies. In the database -there will be a ``person_id`` column, type ``INT``, which points to -the ``person`` column. - -Here's the `Person` class: - -.. raw:: html - :file: ../examples/snippets/address-person.html - -We get the backreference with ``addresses = MultipleJoin('Address')``. -When we access a person's `addresses` attribute, we will get back a -list of all the `Address` objects associated with that person. An -example: - -.. raw:: html - :file: ../examples/snippets/address-use1.html - -Many-to-Many Relationships --------------------------- - -For this example we will have user and role objects. The two have a -many-to-many relationship, which is represented with the -`RelatedJoin`. - -.. raw:: html - :file: ../examples/snippets/userrole-classes.html - -And usage: - -.. raw:: html - :file: ../examples/snippets/userrole-use.html - -In the process an intermediate table is created, ``role_user``, which -references both of the other classes. This table is never exposed as -a class, and its rows do not have equivalent Python objects -- this -hides some of the nuisance of a many-to-many relationship. - -You may notice that the columns have the extra keyword argument -`alternateID`. If True, this means that the column uniquely -identifies rows -- like a username uniquely identifies a user. This -identifier is in addition to the primary key (``id``), which is always -present. - -.. note:: - SQLObject has a strong requirement that the primary key be unique - and *immutable*. You cannot change the primary key through - SQLObject, and if you change it through another mechanism you can - cause inconsistency in any running SQLObject program (and in your - data). For this reason meaningless integer IDs are encouraged -- - something like a username that could change in the future may - uniquely identify a row, but it may be changed in the future. So - long as it is not used to reference the row internally, it is also - *safe* to change it in the future. - -A alternateID column creates a class method, like ``byUsername`` for a -column named ``username`` (or you can use the `alternateMethodName` -keyword argument to override this). Its use: - -.. raw:: html - :file: ../examples/snippets/userrole-use-alternate.html - - -Selecting Multiple Objects --------------------------- - -While the full power of all the kinds of joins you can do with a -database are not revealed in SQLObject, a simple ``SELECT`` is -available. - -``select`` is a class method, and you call it like (with the SQL -that's generated): - -.. raw:: html - :file: ../examples/snippets/person-select1.html - -This example returns everyone with the first name John. An expression -could be more complicated as well, like: - -.. raw:: html - :file: ../examples/snippets/person-select2.html - -You'll note that classes have an attribute ``q``, which gives access -to special objects for constructing query clauses. All attributes -under ``q`` refer to column names and if you construct logical -statements with these it'll give you the SQL for that statement. You -can also work like this: - -.. raw:: html - :file: ../examples/snippets/person-select3.html - -You may wish to use `MyClass.sqlrepr` to quote any values you use if -you create SQL manually (quoting is automatic if you use ``q``). -Tables given in `clauseTables` will be added to the ``FROM`` portion -(again, they are automatically picked up when using ``q``). The table -you're selecting is always assumed to be included, of course. - -.. _orderBy: - -You can use the keyword arguments `orderBy` to create ``ORDER BY`` in -the select statements: `orderBy` takes a string, which should be the -*database* name of the column, or a column in the form -``Person.q.firstName``. You can use ``"-colname"`` to specify -descending order, or call ``MyClass.select().reversed()``. - -You can use the special class variable `_defaultOrder` to give a -default ordering for all selects. To get an unordered result when -`_defaultOrder` is used, use ``orderBy=None``. - -Select results are generators, which are lazily evaluated. So the SQL -is only executed when you iterate over the select results, or if you -use ``list()`` to force the result to be executed. When you iterate -over the select results, rows are fetched one at a time. This way you -can iterate over large results without keeping the entire result set -in memory. You can also do things like ``.reversed()`` without -fetching and reversing the entire result -- instead, SQLObject can -change the SQL that is sent so you get equivalent results. - -You can also slice select results. The results are used in the SQL -query, so ``peeps[:10]`` will result in ``LIMIT 10`` being added to -the end of the SQL query. If the slice cannot be performed in the SQL -(e.g., peeps[:-10]), then the select is executed, and the slice is -performed on the list of results. This will only happen when you use -negative indexes. - -In certain cases, you may get a select result with an object in it -more than once, e.g., in some joins. If you don't want this, you can -add the keyword argument ``MyClass.select(..., distinct=True)``. - -You can get the length of the result without fetching all the results -by calling ``count`` on the result object, like -``MyClass.select().count()``. A ``COUNT(*)`` query is used -- the -actual objects are not fetched from the database. Together with -slicing, this makes batched queries easy to write: - -.. raw:: html - :file: ../examples/snippets/slicing-batch.html - -.. note:: - - There are several factors when considering the efficiency of this - kind of batching, and it depends very much how the batching is - being used. Consider a web application where you are showing an - average of 100 results, 10 at a time, and the results are ordered - by the date they were added to the database. While slicing will - keep the database from returning all the results (and so save some - communication time), the database will still have to scan through - the entire result set to sort the items (so it knows which the - first ten are), and depending on your query may need to scan - through the entire table (depending on your use of indexes). - Indexes are probably the most important way to improve importance - in a case like this, and you may find caching to be more effective - than slicing. - - In this case, caching would mean retrieving the *complete* results. - You can use ``list(MyClass.select(...))`` to do this. You can save - these results for some limited period of time, as the user looks - through the results page by page. This means the first page in a - search result will be slightly more expensive, but all later pages - will be very cheap. - -For more information on the where clause in the queries, see the -`SQLBuilder documentation`_. - -.. _`SQLBuilder documentation`: SQLBuilder.html - -Select-By Method -~~~~~~~~~~~~~~~~ - -An alternative to ``.select`` is ``.selectBy``. It works like: - -.. raw:: html - :file: ../examples/snippets/person-select-by.html - -Each keyword argument is a column, and all the keyword arguments -are ANDed together. The return value is a `SelectResult`, so you -can slice it, count it, order it, etc. - -Customizing the Objects ------------------------ - -While we haven't done so in the examples, you can include your own -methods in the class definition. Writing you own methods should be -obvious enough (just do so like in any other class), but there are -some other details to be aware of. - -Initializing the Objects -~~~~~~~~~~~~~~~~~~~~~~~~ - -There are two ways SQLObject instances can come into existance: they -can be fetched from the database, or they can be inserted into the -database. In both cases a new Python object is created. This makes -the place of `__init__` a little confusing. - -In general, you should not touch `__init__`. Instead use the `_init` -method, which is called after an object is fetched or inserted. This -method has the signature ``_init(self, id, connection=None, -selectResults=None)``, though you may just want to use ``_init(self, -*args, **kw)``. **Note:** don't forget to call -``SQLObject._init(self, *args, **kw)`` if you override the method! - -Adding Magic Attributes (properties) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can use all the normal techniques for defining methods in this -new-style class, including `classmethod`, `staticmethod`, and -`property`, but you can also use a shortcut. If you have a method -that's name starts with ``_set_``, ``_get_``, ``_del_``, or ``_doc_``, -it will be used to create a property. So, for instance, say you have -images stored under the ID of the person in the ``/var/people/images`` -directory: - -.. raw:: html - :file: ../examples/snippets/person_magicmethod.html - -Later, you can use the ``.image`` property just like an attribute, and -the changes will be reflected in the filesystem by calling these -methods. This is a good technique for information that is better to -keep in files as opposed to the database (such as large, opaque data -like images). - -You can also pass an ``image`` keyword argument to the `new` class -method or the `set` method, like ``Person.new(..., image=imageText)``. - -All of the methods (``_get_``, ``_set_``, etc) are optional -- you can -use any one of them without using the others (except ``_doc_``, since -having a doc string that doesn't document anything would be silly). -So you could define just a ``_get_attr`` method so that ``attr`` was -read-only. - -Overriding Column Attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -It's a little more complicated if you want to override the behavior of -an database column attribute. For instance, imagine there's special -code you want to run whenever someone's name changes -- you could make -a subclass, and then use ``Person.__setattr__(self, 'lastName', -value)`` to actually do the deed, but that's obviously very awkward -- -you have to create subclasses without a real inheritance relationship, -and the whole thing feels architecturally fragile. SQLObject creates -methods like ``_set_lastName`` for each of your columns, but again you -can't use this, since there's no superclass to reference (and you -can't write ``SQLObject._set_lastName(...)``, because the SQLObject -class doesn't know about your class's columns). You want to override -that ``_set_lastName`` method yourself. - -To deal with this, SQLObject creates two methods for each getter and -setter, for example: ``_set_lastName`` and ``_SO_set_lastName``. So -to intercept all changes to ``lastName``: - -.. raw:: html - :file: ../examples/snippets/person_magicoverride.html - -Or perhaps you want to constrain a phone numbers to be actual -digits, and of proper length, and make the formatting nice: - -.. raw:: html - :file: ../examples/snippets/phonenumber_magicoverride.html - -.. note:: - - You should be a little cautious when modifying data that gets set - in an attribute. Generally someone using your class will expect - that the value they set the attribute to will be the same value - they get back. In this example we removed some of the characters - before putting it in the database, and reformatted it on the way - out. One advantage of methods (as opposed to attribute access) is - that the programmer is more likely to expect this disconnect. - Reference ========= @@ -578,637 +29,16 @@ be useful for many situations. Now we'll show how to specify the class more completely. -Col Class: Specifying Columns ------------------------------ +.. include:: SQLObjectCol.txt +.. include:: SQLObjectClasses.txt +.. include:: SQLObjectRelationships.txt +.. include:: SQLObjectTransactions.txt +.. include:: SQLObjectGeneration.txt +.. include:: SQLObjectDynamic.txt +.. include:: SQLObjectLegacy.txt +.. include:: SQLObjectDBConnection.txt +.. include:: SQLObjectExported.txt -The list of columns is a list of `Col` objects. These objects don't -have functionality in themselves, but give you a way to specify the -column. - -`dbName`: - This is the name of the column in the database. If you don't - give a name, your Pythonic name will be converted from - mixed-case to underscore-separated. -`default`: - The default value for this column. Used when creating a new row. - If you give a callable object or function, the function will be - called, and the return value will be used. So you can give - ``DateTime.now`` to make the default value be the current time. - Or you can use ``SQLBuilder.func.NOW()`` to have the database use - the ``NOW()`` function internally. If you don't give a default - there will be an exception if this column isn't specified in the - call to `new`. -`alternateID`: - This boolean (default False) indicates if the column can be used - as an ID for the field (for instance, a username), though it is - not a primary key. If so a class method will be added, like - ``byUsername`` which will return that object. Use - `alternateMethodName` if you don't like the ``by*`` name - (e.g. ``alternateMethodName="username"``). - - The column should be declared ``UNIQUE`` in your table schema. -`unique`: - If true, when SQLObject creates a table it will declare this - column to be ``UNIQUE``. -`notNone`: - If true, None/``NULL`` is not allowed for this column. Useful if - you are using SQLObject to create your tables. -`sqlType`: - The SQL type for this column (like ``INT``, ``BOOLEAN``, etc). - You can use classes (defined below) for this, but if those don't - work it's sometimes easiest just to use `sqlType`. Only necessary - if SQLObject is creating your tables. - -Subclasses of Col -~~~~~~~~~~~~~~~~~ - -The `ForeignKey` class should be used instead of `Col` when the column -is a reference to another table/class. It is generally used like -``ForeignKey('Role')``, in this instance to create a reference to a -table `Role`. This is largely equivalent to ``Col(foreignKey='Role', -sqlType='INT')``. Two attributes will generally be created, ``role``, -which returns a `Role` instance, and ``roleID``, which returns an -integer ID for the related role. - -There are some other subclasses of `Col`. These are used to indicate -different types of columns, when SQLObject creates your tables. - -`BoolCol`: - Will create a ``BOOLEAN`` column in Postgres, or ``INT`` in other - databses. It will also convert values to ``"t"/"f"`` or ``0/1`` - according to the database backend. - -`CurrencyCol`: - Equivalent to ``DecimalCol(size=10, precision=2)``. - -`DateTimeCol`: - A date and time (usually returned as an mxDateTime object). - -`DecimalCol`: - Base-10, precise number. Uses the keyword arguments `size` for - number of digits stored, and `precision` for the number of digits - after the decimal point. - -`EnumCol`: - One of several string values -- give the possible strings as a - list, with the `enumValues` keyword argument. MySQL has a native - ``ENUM`` type, but will work with other databases too (storage - just won't be as efficient). - -`FloatCol`: - Floats. - -`ForeignKey`: - A key to another table/class. Use like ``user = - ForeignKey('User')``. - -`IntCol`: - Integers. - -`StringCol`: - A string (character) column. Extra keywords: - - `length`: - If given, the type will be something like ``VARCHAR(length)``. - If not given, then ``TEXT`` is assumed (i.e., lengthless). - `varchar`: - A boolean; if you have a length, differentiates between - ``CHAR`` and ``VARCHAR``, default True, i.e., use - ``VARCHAR``. - -`UnicodeCol`: - A subclass of `StringCol`. Also accepts a dbEncoding keyword - argument, which defaults to ``"UTF-8"``. Values coming in and - out from the database will be encoded and decoded. **Note**: - parameters in queries will not be automatically encoded, so if - you do a query matching a UnicodeCol column you must apply the - encoding yourself. - - -SQLObject Class: Specifying Your Class --------------------------------------- - -In addition to the columns, there are a number of other special -attributes you can set in your class. - -`_connection`: - The connection object to use, from `DBConnection`. You can also - set the variable `__connection__` in the enclosing module and it - will be picked up (be sure to define `__connection__` before you - class). You can also pass a connection object in at instance - creation time, as described in transactions_. - -`_table`: - The database name of the table for this class. If you don't give - a name, then the standard ``MixedCase`` to ``mixed_case`` - translation is performed. - -`_joins`: - A list of `Join` objects. This is covered below. - -`_cacheValues`: - If set to ``False`` then values for attributes from the database - won't be cached. So everytime you access an attribute in the - object the database will be queried for a value. If you want to - handle concurrent access to the database from multiple processes - then this is probably the way to do so. You should also use - it with transactions_ (it is not implied). - -.. _idName: - -`_idName`: - The name of the primary key column (default ``id``). - -`_style`: - A style object -- this object allows you to use other algorithms - for translating between Python attribute and class names, and the - database's column and table names. See `Changing the Naming - Style`_ for more. - -.. Relationships_: - -Relationships Between Classes/Tables ------------------------------------- - -You can use the `ForeignKey` to handle foreign references in a table, -but for back references and many-to-many relationships you'll use -joins. - -MultipleJoin: One-to-Many -~~~~~~~~~~~~~~~~~~~~~~~~~ - -See `One-to-Many Relationships`_ for an example of one-to-many -relationships. - -Several keyword arguments are allowed to the `MultipleJoin` constructor: - -.. _`Multiple Join Keywords`: - -`joinColumn`: - The column name of the key that points to this table. So, if you have - a table ``Product``, and another table has a column ``ProductNo`` that - points to this table, then you'd use ``joinColumn="ProductNo"``. -`orderBy`: - Like the `orderBy`_ argument to `select()`, you can specify - the order that the joined objects should be returned in. `_defaultOrder` - will be used if not specified; ``None`` forces unordered results. -`joinMethodName`: - When adding joins dynamically (using the class method `addJoin`_), - you can give the name of the accessor for the join. It can also be - created automatically, and is normally implied (i.e., ``addresses = - MultipleJoin(...)`` implies ``joinMethodName="addresses"``). - -RelatedJoin: Many-to-Many -~~~~~~~~~~~~~~~~~~~~~~~~~ - -See `Many-to-Many Relationships`_ for examples of using many-to-many joins. - -`RelatedJoin` has all the keyword arguments of `MultipleJoin`__, plus: - -__ `Multiple Join Keywords`_ - -`otherColumn`: - Similar to `joinColumn`, but referring to the joined class. -`intermediateTable`: - The name of the intermediate table which references both classes. -`addRemoveName`: - In the `user/role example`__, the methods `addRole(role)` and - `removeRole(role)` are created. The ``Role`` portion of these - method names can be changed by giving a string value here. - -__ `Many-to-Many Relationships`_ - -An example schema that requires the use of `joinColumn`, `otherColumn`, -and `intermediateTable`:: - - CREATE TABLE person ( - id SERIAL, - username VARCHAR(100) NOT NULL UNIQUE - ); - - CREATE TABLE role ( - id SERIAL, - name VARCHAR(50) NOT NULL UNIQUE - ); - - CREATE TABLE assigned_roles ( - person INT NOT NULL, - role INT NOT NULL - ); - -Then the usage in a class:: - - class Person(SQLObject): - username = StringCol(length=100, alternateID=True) - roles = RelatedJoin('Role', joinColumn='person', otherColumn='role', - intermediateTable='assigned_roles') - class Role(SQLObject): - name = StringCol(length=50, alternateID=True) - roles = RelatedJoin('Person', joinColumn='role', otherColumn='person', - intermediateTable='assigned_roles') - -Transactions ------------- - -Transaction support in SQLObject is left to the database. -Transactions can be used like: - -.. raw:: html - :file: ../examples/snippets/transactions1.html - -The ``trans`` object here is essentially a wrapper around a single -database connection, and `commit` and `rollback` just pass that -message to the `psycopg` connection. - -If you want to use transactions you should also turn `_cacheValues` -off, like: - -.. raw:: html - :file: ../examples/snippets/transactions2.html - -Automatic Schema Generation ---------------------------- - -All the connections support creating and droping tables based on the -class definition. First you have to prepare your class definition, -which means including type information in your columns. - -Columns Types -~~~~~~~~~~~~~ - -A column type is indicated by using a subclass of `Col`: - -`StringCol`: - StringCol represents ``CHAR``, ``VARCHAR``, and ``TEXT``. The - `length` keyword argument indicates the ``CHAR`` or ``VARCHAR`` - length -- if not given, then ``TEXT`` is assumed. If you use - ``varchar=False`` then ``CHAR`` will be used, otherwise - ``VARCHAR`` is the default. -`IntCol`: - The ``INT`` type. -`FloatCol`: - The ``FLOAT`` type. -`DecimalCol`: - The ``DECIMAL`` SQL type, i.e., base 10 number. The keyword - arguments `size` and `precision` indicate the scope. So - ``DecimalCol(size=5, precision=2)`` is a number like ###.##, - i.e., 5 digits, two of them past the decimal point. -`CurrencyCol`: - Like ``DecimalCol(size=10, precision=2)``. -`EnumCol`: - A MySQL ``ENUM``, i.e., one of a finite number of strings. - For other databases this will be a ``VARCHAR``. -`DateTimeCol`: - A moment in time. ``TIMESTAMP`` in Postgres, and ``DATETIME`` - in MySQL. Note the names of these columns match the *Python* - type names, not the SQL names. -`ForeignKey`: - This is a reference to another table. You typically need to - only give the name of the foreign class that is referenced. - `ForeignKey` implies an ``INT`` column. - -Indexes -~~~~~~~ - -You can also define indexes for your tables, which is only meaningful -when creating your tables through SQLObject (SQLObject relies on the -database to implement the indexes). You do this again with attribute -assignment, like:: - - firstLastIndex = DatabaseIndex('firstName', 'lastName') - -This creates an index on two columns, useful if you are selecting a -particular name. Of course, you can give a single column, and you can -give the column object (``firstName``) instead of the string name. -Note that if you use ``unique`` or ``alternateID`` (which implies -``unique``) the database may make an index for you, and primary keys -are always indexed. - -If you give the keyword argument ``unique`` to `DatabaseIndex` you'll -create a unique index -- the combination of columns must be unique. - -You can also use dictionaries in place of the column names, to add -extra options. E.g.:: - - lastNameIndex = DatabaseIndex({'expression': 'lower(last_name)'}) - -In that case, the index will be on the lower-case version of the -column. It seems that only PostgreSQL supports this. You can also -do:: - - lastNameIndex = DatabaseIndex({'column': lastName, 'length': 10}) - -Which asks the database to only pay attention to the first ten -characters. Only MySQL supports this, but it is ignored in other -databases. - -Creating and Dropping Tables -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To create a table call `createTable`. It takes two arguments: - -`ifNotExists`: - If the table already exists, then don't try to create it. Default - False. -`createJoinTables`: - If you used `Many-to-Many relationships`_, then the intermediate tables - will be created (but only for one of the two involved classes). - Default True. - -`dropTable` takes arguments `ifExists` and `dropJoinTables`, -self-explanatory. - -Dynamic Classes -=============== - -SQLObject classes can be manipulated dynamically. This leaves open -the possibility of constructing SQLObject classes from an XML file, -from database introspection, or from a graphical interface. - -Automatic Class Generation ---------------------------- - -SQLObject can read the table description from the database, and fill -in the class columns (as would normally be described in the `_columns` -attribute). Do this like:: - - class Person(SQLObject): - - _fromDatabase = True - -You can still specify columns (in `_columns`), and only missing -columns will be added. - -*This is not supported in SQLite* - -Runtime Column Changes ----------------------- - -*SQLite does not support this feature* - -You can add and remove columns to your class at runtime. Such changes -will effect all instances, since changes are made inplace to the -class. There are two methods, `addColumn` and `delColumn`, both of -which take a `Col` object (or subclass) as an argument. There's also -an option argument `changeSchema` which, if True, will add or drop the -column from the database (typically with an ``ALTER`` command). - -When adding columns, you must pass the name as part of the column -constructor, like ``StringCol("username", length=20)``. When removing -columns, you can either use the Col object (as found in `_columns`, or -which you used in `addColumn`), or you can use the column name (like -``MyClass.delColumn("username")``). - -.. _addJoin: - -You can also add Joins__, like -``MyClass.addJoin(MultipleJoin("MyOtherClass"))``, and remove joins with -`delJoin`. `delJoin` does not take strings, you have to get the join -object out of the `_joins` attribute. - -__ Relationships_: - -Legacy Database Schemas -======================= - -Often you will have a database that already exists, and does not use -the naming conventions that SQLObject expects, or does not use any -naming convention at all. - -SQLObject requirements ----------------------- - -While SQLObject tries not to make too many requirements on your -schema, some assumptions are made. Some of these may be relaxed in -the future. - -All tables that you want to turn into a class need to have an integer -primary key. That key should be defined like: - -MySQL: - ``INT PRIMARY KEY AUTO_INCREMENT`` -Postgres: - ``SERIAL PRIMARY KEY`` -SQLite: - ``INTEGER PRIMARY KEY`` - -SQLObject does not support non-integer keys (that may change). It -does not support sequences in Postgres (that will change -- ``SERIAL`` -uses an implicit sequence). It does not support primary keys made up -of multiple columns (that probably won't change). It does not -generally support tables with primary keys with business meaning -- -i.e., primary keys are assumed to be immutable (that won't change). - -At the moment foreign key column names must end in ``"ID"`` -(case-insensitive). This restriction will probably be removed in the -next release. - -Changing the Naming Style -------------------------- - -By default names in SQLObject are expected to be mixed case in Python -(like ``mixedCase``), and underscore-separated in SQL (like -``mixed_case``). This applies to table and column names. The primary -key is assumed to be simply ``id``. - -Other styles exist. A typical one is mixed case column names, and a -primary key that includes the table name, like ``ProductID``. You can -use a different "Style" object to indicate a different naming -convention. For instance: - -.. raw:: html - :file: ../examples/snippets/style1.html - -If you use ``Person.createTable()``, you'll get:: - - CREATE TABLE Person ( - PersonID INT PRIMARY KEY, - FirstName Text, - LastName Text - ) - -The `MixedCaseStyle` object handles the initial capitalization of -words, but otherwise leaves them be. By using ``longID=True``, we -indicate that the primary key should look like a normal reference -(``PersonID`` for `MixedCaseStyle`, or ``person_id`` for the default -style). - -If you wish to change the style globally, assign the style to the -connection, like: - -.. raw:: html - :file: ../examples/snippets/default-style.html - -Irregular Naming ----------------- - -While naming conventions are nice, they are not always present. You -can control most of the names that SQLObject uses, independent of the -Python names (so at least you don't have to propagate the -irregularity to your brand-spanking new Python code). - -Here's a simple example: - -.. raw:: html - :file: ../examples/snippets/style-table.html - -The attribute `_table` overrides the table name. `_idName` provides -an alternative to ``id``. The ``dbName`` keyword argument gives the -column name. - -Non-Integer Keys ----------------- - -While not strictly a legacy database issue, this fits into the -category of "irregularities". If you use non-integer keys, all -primary key management is up to you. You must create the table -yourself, and when you create instances you must pass a ``id`` keyword -argument into ``new`` (like ``Person.new(id='555-55-5555', ...)``). - -DBConnection: Database Connections -================================== - -The `DBConnection` module currently has four external classes, -`MySQLConnection`, `PostgresConnection`, `SQLiteConnection`, -`SybaseConnection`, and `MaxdbConnection`. - -You can pass the keyword argument `debug` to any connector. If set to -true, then any SQL sent to the database will also be printed to the -console. - -MySQL ------ - -`MySQLConnection` takes the keyword arguments `host`, `db`, `user`, -and `passwd`, just like `MySQLdb.connect` does. - -MySQLConnection supports all the features, though MySQL only supports -transactions_ when using the InnoDB backend, and SQLObject currently -does not have support for explicitly defining the backend when using -``createTable``. - -Postgres --------- - -`PostgresConnection` takes a single connection string, like -``"dbname=something user=some_user"``, just like `psycopg.connect`. -You can also use the same keyword arguments as for `MySQLConnection`, -and a dsn string will be constructed. - -PostgresConnection supports transactions and all other features. - -SQLite ------- - -`SQLiteConnection` takes the a single string, which is the path to the -database file. - -SQLite puts all data into one file, with a journal file that is opened -in the same directory during operation (the file is deleted when the -program quits). SQLite does not restrict the types you can put in a -column -- strings can go in integer columns, dates in integers, etc. - -SQLiteConnection doesn't support `automatic class generation`_ and -SQLite does not support `runtime column changes`_. - -SQLite may have concurrency issues, depending on your usage in a -multi-threaded environment. - -Firebird --------- - -`FirebirdConnection` takes the arguments `host`, `db`, `user` (default -``"sysdba"``), `passwd` (default ``"masterkey"``). - -Firebird supports all the features. Support is still young, so there -may be some issues, especially with concurrent access, and especially -using lazy selects. Try ``list(MyClass.select())`` to avoid -concurrent cursors if you have problems (using ``list()`` will -pre-fetch all the results of a select). - -Firebird support uses the kinterbasdb_ Python library. - -.. _kinterbasdb: http://kinterbasdb.sourceforge.net/ - -If you are using indexes and get an error like *key size exceeds -implementation restriction for index*, see `this page`_ to understand -the restrictions on your indexing. - -.. _this page: http://www.volny.cz/iprenosil/interbase/ip_ib_indexcalculator.htm - -SybaseConnection ----------------- - -`SybaseConnection` takes the arguments `host`, `db`, `user`, and -`passwd`. It also takes the extra boolean argument `locking` (default -True), which is passed through when performing a connection. You may -use a False value for `locking` if you are not using multiple threads, -for a slight performance boost. - -It uses the Sybase_ module. - -.. _Sybase: http://www.object-craft.com.au/projects/sybase/ - -MAX DB ------- - -MAX DB, also known as SAP DB, is available from a partnership of SAP -and MySQL. It takes the typical arguments: `host`, `database`, -`user`, `password`. It also takes the arguments `sqlmode` (default -``"internal"``), `isolation`, and `timeout`, which are passed through -when creating the connection to the database. - -It uses the sapdb_ module. - -.. _sapdb: http://www.sapdb.org/sapdbPython.html - -Exported Symbols -================ - -You can use ``from sqlobject import *``, though you don't have to. It -exports a minimal number of symbols. The symbols exported: - -From `sqlobject.main`: - -* `NoDefault` -* `SQLObject` -* `getID` -* `getObject` - -From `sqlobject.col`: -* `Col` -* `StringCol` -* `IntCol` -* `FloatCol` -* `KeyCol` -* `ForeignKey` -* `EnumCol` -* `DateTimeCol` -* `DecimalCol` -* `CurrencyCol` - -From `sqlobject.joins`: -* `MultipleJoin` -* `RelatedJoin` - -From `sqlobject.styles`: -* `Style` -* `MixedCaseUnderscoreStyle` -* `DefaultStyle` -* `MixedCaseStyle` - -From `sqlobject.sqlbuilder`: - -* `AND` -* `OR` -* `NOT` -* `IN` -* `LIKE` -* `DESC` -* `CONTAINSSTRING` -* `const` -* `func` - For more information on SQLBuilder, read the `SQLBuilder Documentation`_. Added: trunk/SQLObject/docs/SQLObjectClasses.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectClasses.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObjectClasses.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -0,0 +1,41 @@ +SQLObject Class: Specifying Your Class +-------------------------------------- + +In addition to the columns, there are a number of other special +attributes you can set in your class. + +`_connection`: + The connection object to use, from `DBConnection`. You can also + set the variable `__connection__` in the enclosing module and it + will be picked up (be sure to define `__connection__` before you + class). You can also pass a connection object in at instance + creation time, as described in transactions_. + +`_table`: + The database name of the table for this class. If you don't give + a name, then the standard ``MixedCase`` to ``mixed_case`` + translation is performed. + +`_joins`: + A list of `Join` objects. This is covered below. + +`_cacheValues`: + If set to ``False`` then values for attributes from the database + won't be cached. So everytime you access an attribute in the + object the database will be queried for a value. If you want to + handle concurrent access to the database from multiple processes + then this is probably the way to do so. You should also use + it with transactions_ (it is not implied). + +.. _idName: + +`_idName`: + The name of the primary key column (default ``id``). + +`_style`: + A style object -- this object allows you to use other algorithms + for translating between Python attribute and class names, and the + database's column and table names. See `Changing the Naming + Style`_ for more. + +.. Relationships_: Added: trunk/SQLObject/docs/SQLObjectCol.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectCol.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObjectCol.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -0,0 +1,105 @@ +Col Class: Specifying Columns +----------------------------- + +The list of columns is a list of `Col` objects. These objects don't +have functionality in themselves, but give you a way to specify the +column. + +`dbName`: + This is the name of the column in the database. If you don't + give a name, your Pythonic name will be converted from + mixed-case to underscore-separated. +`default`: + The default value for this column. Used when creating a new row. + If you give a callable object or function, the function will be + called, and the return value will be used. So you can give + ``DateTime.now`` to make the default value be the current time. + Or you can use ``SQLBuilder.func.NOW()`` to have the database use + the ``NOW()`` function internally. If you don't give a default + there will be an exception if this column isn't specified in the + call to `new`. +`alternateID`: + This boolean (default False) indicates if the column can be used + as an ID for the field (for instance, a username), though it is + not a primary key. If so a class method will be added, like + ``byUsername`` which will return that object. Use + `alternateMethodName` if you don't like the ``by*`` name + (e.g. ``alternateMethodName="username"``). + + The column should be declared ``UNIQUE`` in your table schema. +`unique`: + If true, when SQLObject creates a table it will declare this + column to be ``UNIQUE``. +`notNone`: + If true, None/``NULL`` is not allowed for this column. Useful if + you are using SQLObject to create your tables. +`sqlType`: + The SQL type for this column (like ``INT``, ``BOOLEAN``, etc). + You can use classes (defined below) for this, but if those don't + work it's sometimes easiest just to use `sqlType`. Only necessary + if SQLObject is creating your tables. + +Subclasses of Col +~~~~~~~~~~~~~~~~~ + +The `ForeignKey` class should be used instead of `Col` when the column +is a reference to another table/class. It is generally used like +``ForeignKey('Role')``, in this instance to create a reference to a +table `Role`. This is largely equivalent to ``Col(foreignKey='Role', +sqlType='INT')``. Two attributes will generally be created, ``role``, +which returns a `Role` instance, and ``roleID``, which returns an +integer ID for the related role. + +There are some other subclasses of `Col`. These are used to indicate +different types of columns, when SQLObject creates your tables. + +`BoolCol`: + Will create a ``BOOLEAN`` column in Postgres, or ``INT`` in other + databses. It will also convert values to ``"t"/"f"`` or ``0/1`` + according to the database backend. + +`CurrencyCol`: + Equivalent to ``DecimalCol(size=10, precision=2)``. + +`DateTimeCol`: + A date and time (usually returned as an mxDateTime object). + +`DecimalCol`: + Base-10, precise number. Uses the keyword arguments `size` for + number of digits stored, and `precision` for the number of digits + after the decimal point. + +`EnumCol`: + One of several string values -- give the possible strings as a + list, with the `enumValues` keyword argument. MySQL has a native + ``ENUM`` type, but will work with other databases too (storage + just won't be as efficient). + +`FloatCol`: + Floats. + +`ForeignKey`: + A key to another table/class. Use like ``user = + ForeignKey('User')``. + +`IntCol`: + Integers. + +`StringCol`: + A string (character) column. Extra keywords: + + `length`: + If given, the type will be something like ``VARCHAR(length)``. + If not given, then ``TEXT`` is assumed (i.e., lengthless). + `varchar`: + A boolean; if you have a length, differentiates between + ``CHAR`` and ``VARCHAR``, default True, i.e., use + ``VARCHAR``. + +`UnicodeCol`: + A subclass of `StringCol`. Also accepts a dbEncoding keyword + argument, which defaults to ``"UTF-8"``. Values coming in and + out from the database will be encoded and decoded. **Note**: + parameters in queries will not be automatically encoded, so if + you do a query matching a UnicodeCol column you must apply the + encoding yourself. Added: trunk/SQLObject/docs/SQLObjectComparison.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectComparison.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObjectComparison.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -0,0 +1,55 @@ +Compared To Other Database Wrappers +=================================== + +There are several object-relational mappers (ORM) for Python. I +honestly can't comment deeply on the quality of those packages, but +I'll try to place SQLObject in perspective. + +SQLObject uses new-style classes extensively. The resultant objects +have a new-style feel as a result -- setting attributes has side +effects (it changes the database), and defining classes has side +effects (through the use of metaclasses). Attributes are generally +exposed, not marked private, knowing that they can be made dynamic +or write-only later. + +SQLObject creates objects that feel similar to normal Python objects +(with the semantics of new-style classes). An attribute attached to a +column doesn't look different than an attribute that's attached to a +file, or an attribute that is calculated. It is a specific goal that +you be able to change the database without changing the interface, +including changing the scope of the database, making it more or less +prominent as a storage mechanism. + +This is in contrast to some ORMs that provide a dictionary-like +interface to the database (for example, PyDO_). The dictionary +interface distinguishes the row from a normal Python object. I also +don't care for the use of strings where an attribute seems more +natural -- columns are limited in number and predefined, just like +attributes. (Note: newer version of PyDO apparently allow attribute +access as well) + +.. _PyDO: http://skunkweb.sourceforge.net/pydo.html + +SQLObject is, to my knowledge, unique in using metaclasses to +facilitate this seemless integration. Some other ORMs use code +generation to create an interface, expressing the schema in a CSV or +XML file (for example, MiddleKit, part of Webware_). By using +metaclasses you are able to comfortably define your schema in the +Python source code. No code generation, no weird tools, no +compilation step. + +.. _Webware: http://webware.sourceforge.net + +SQLObject provides a strong database abstraction, allowing +cross-database compatibility (so long as you don't sidestep +SQLObject). + +SQLObject has joins, one-to-many, and many-to-many, something which +many ORMs do not have. The join system is also intended to be +extensible. + +You can map between database names and Python attribute and class +names; often these two won't match, or the database style would be +inappropriate for a Python attribute. This way your database schema +does not have to be designed with SQLObject in mind, and the resulting +classes do not have to inherit the database's naming schemes. Added: trunk/SQLObject/docs/SQLObjectCustomization.txt =================================================================== --- trunk/SQLObject/docs/SQLObjectCustomization.txt 2005-02-17 08:11:27 UTC (rev 613) +++ trunk/SQLObject/docs/SQLObjectCustomization.txt 2005-02-17 08:12:15 UTC (rev 614) @@ -0,0 +1,90 @@ +Customizing the Objects +----------------------- + +While we haven't done so in the examples, you can include your own +methods in the class definition. Writing you own methods should be +obvious enough (just do so like in any other class), but there are +some other details to be aware of. + +Initializing the Objects +~~~~~~~~~~~~~~~~~~~~~~~~ + +There are two ways SQLObject instances can come into existance: they +can be fetched from the database, or they can be inserted into the +database. In both cases a new Python object is created. This makes +the place of `__init__` a little confusing. + +In general, you should not touch `__init__`. Instead use the `_init` +method, which is called after an object is fetched or inserted. This +method has the signature ``_init(self, id, connection=None, +selectResults=None)``, though you may just want to use ``_init(self, +*args, **kw)``. **Note:** don't forget to call +``SQLObject._init(self, *args, **kw)`` if you override the method! + +Adding Magic Attributes (properties) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can use all the normal techniques for defining methods in this +new-style class, including `classmethod`, `staticmethod`, and +`property`, but you can also use a shortcut. If you have a method +that's name starts with ``_set_``, ``_get_``, ``_del_``, or ``_doc_``, +it will be used to create a property. So, for instance, say you have +images stored under the ID of the person in the ``/var/people/images`` +directory: + +.. raw:: html + :file: ../examples/snippets/person_magicmethod.html + +Later, you can use the ``.image`` property just like an attribute, and +the changes will be reflected in the filesystem by calling these +methods. This is a good technique for information that is better to +keep in files as opposed to the database (such as large, opaque data +like images). + +You can also pass an ``image`` keyword argument to the `new` class +method or the `set` method, like ``Person.new(..., image=imageText)``. + +All of the methods (``_get_``, ``_set_``, etc) are optional -- you can +use any one of them without using the others (except ``_doc_``, since +having a doc string that doesn't document anything would be silly). +So you could define just a ``_get_attr`` method so that ``attr`` was +read-only. + +Overriding Column Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's a little more complicated if you want to override the behavior of +an database column attribute. For instance, imagine there's special +code you want to run whenever someone's name changes -- you could make +a subclass, and then use ``Person.__setattr__(self, 'lastName', +value)`` to actually do the deed, but that's obviously very awkward -- +you have to create subclasses without a real inheritance relationship, +and the whole thing feels architecturally fragile. SQLObject creates +methods like ``_set_lastName`` for each of your columns, but again you +can't use this, since there's no superclass to reference (and you +can't write ``SQLObject._set_lastName(...)``, because the SQLObject +class doesn't know about your class's columns). You want to override +that... [truncated message content] |
From: <sub...@co...> - 2005-02-17 08:11:39
|
Author: ianb Date: 2005-02-17 08:11:27 +0000 (Thu, 17 Feb 2005) New Revision: 613 Modified: trunk/SQLObject/docs/default.css Log: Links in headers (typically backlinks to the ToC) won't be miscolored now (so the HTML doesn't have to be generated with special options) Modified: trunk/SQLObject/docs/default.css =================================================================== --- trunk/SQLObject/docs/default.css 2005-02-16 04:31:51 UTC (rev 612) +++ trunk/SQLObject/docs/default.css 2005-02-17 08:11:27 UTC (rev 613) @@ -130,11 +130,19 @@ border: medium solid black; } +h1 a:link, h2 a:link { + color: #ffffff; +} + h3, h4, h5, h6 { background-color: #cccccc; color: #000000; } +h3 a:link, h4 a:link, h5 a:link, h6 a:link { + color: #000000; +} + h1.title { text-align: center; background-color: #444499; |
From: <sub...@co...> - 2005-02-16 04:31:55
|
Author: ianb Date: 2005-02-16 04:31:51 +0000 (Wed, 16 Feb 2005) New Revision: 612 Modified: trunk/SQLObject/sqlobject/col.py trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/firebird/firebirdconnection.py trunk/SQLObject/sqlobject/index.py trunk/SQLObject/sqlobject/inheritance/__init__.py trunk/SQLObject/sqlobject/joins.py trunk/SQLObject/sqlobject/main.py trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py trunk/SQLObject/sqlobject/mysql/mysqlconnection.py trunk/SQLObject/sqlobject/postgres/pgconnection.py trunk/SQLObject/sqlobject/sqlbuilder.py trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py trunk/SQLObject/sqlobject/sresults.py trunk/SQLObject/sqlobject/styles.py trunk/SQLObject/sqlobject/sybase/sybaseconnection.py Log: * Replace cls._table with cls.sqlmeta.table * and cls._idName with cls.sqlmeta.idName * and cls._style with cls.sqlmeta.style * and make tests angry if old styles are used. Tests be very angry with old and deprecated styles, Hulk will smash bad tests! Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/col.py 2005-02-16 04:31:51 UTC (rev 612) @@ -159,7 +159,7 @@ # the column, we separate the mixedCase into mixed_case # and assume that. if dbName is None: - self.dbName = soClass._style.pythonAttrToDBColumn(self.name) + self.dbName = soClass.sqlmeta.style.pythonAttrToDBColumn(self.name) else: self.dbName = dbName @@ -607,7 +607,7 @@ def __init__(self, **kw): foreignKey = kw['foreignKey'] - style = kw['soClass']._style + style = kw['soClass'].sqlmeta.style if not kw.get('name'): kw['name'] = style.instanceAttrToIDAttr(style.pythonClassToAttr(foreignKey)) else: @@ -619,8 +619,8 @@ def postgresCreateSQL(self): sql = SOKeyCol.postgresCreateSQL(self) other = findClass(self.foreignKey) - tName = other._table - idName = other._idName + tName = other.sqlmeta.table + idName = other.sqlmeta.idName if self.cascade is not None: if self.cascade == 'null': action = 'ON DELETE SET NULL' @@ -644,8 +644,8 @@ def sybaseCreateSQL(self): sql = SOKeyCol.sybaseCreateSQL(self) other = findClass(self.foreignKey) - tName = other._table - idName = other._idName + tName = other.sqlmeta.table + idName = other.sqlmeta.idName reference = ('REFERENCES %(tName)s(%(idName)s) ' % {'tName':tName, 'idName':idName}) @@ -657,8 +657,8 @@ fidName = self.dbName #I assume that foreign key name is identical to the id of the reference table sql = ' '.join([fidName, self._maxdbType()]) - tName = other._table - idName = other._idName + tName = other.sqlmeta.table + idName = other.sqlmeta.idName sql=sql + ',' + '\n' sql=sql + 'FOREIGN KEY (%s) REFERENCES %s(%s)'%(fidName,tName,idName) return sql Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-16 04:31:51 UTC (rev 612) @@ -271,18 +271,18 @@ q = 'SELECT ' if ops.get('lazyColumns', 0): q += "%s.%s FROM %s WHERE " % \ - (cls._table, cls._idName, + (cls.sqlmeta.table, cls.sqlmeta.idName, ", ".join(select.tables)) else: - columns = ", ".join(["%s.%s" % (cls._table, col.dbName) + columns = ", ".join(["%s.%s" % (cls.sqlmeta.table, col.dbName) for col in cls._SO_columns]) if columns: q += "%s.%s, %s FROM %s WHERE " % \ - (cls._table, cls._idName, columns, + (cls.sqlmeta.table, cls.sqlmeta.idName, columns, ", ".join(select.tables)) else: q += "%s.%s FROM %s WHERE " % \ - (cls._table, cls._idName, + (cls.sqlmeta.table, cls.sqlmeta.idName, ", ".join(select.tables)) return self._addWhereClause(select, q) @@ -360,7 +360,7 @@ def createTableSQL(self, soClass): return ('CREATE TABLE %s (\n%s\n)' % - (soClass._table, self.createColumns(soClass))) + (soClass.sqlmeta.table, self.createColumns(soClass))) def createColumns(self, soClass): columnDefs = [self.createIDColumn(soClass)] \ @@ -396,36 +396,45 @@ def _SO_update(self, so, values): self.query("UPDATE %s SET %s WHERE %s = %s" % - (so._table, + (so.sqlmeta.table, ", ".join(["%s = %s" % (dbName, self.sqlrepr(value)) for dbName, value in values]), - so._idName, + so.sqlmeta.idName, self.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, - self.sqlrepr(so.id))) + columns = ", ".join(columnNames) + if columns: + return self.queryOne( + "SELECT %s FROM %s WHERE %s = %s" % + (columns, + so.sqlmeta.table, + so.sqlmeta.idName, + self.sqlrepr(so.id))) + else: + return self.queryOne( + "SELECT NULL FROM %s WHERE %s = %s" % + (so.sqlmeta.table, + so.sqlmeta.idName, + self.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, + cls.sqlmeta.table, column, self.sqlrepr(value))) def _SO_delete(self, so): self.query("DELETE FROM %s WHERE %s = %s" % - (so._table, - so._idName, + (so.sqlmeta.table, + so.sqlmeta.idName, self.sqlrepr(so.id))) def _SO_selectJoin(self, soClass, column, value): return self.queryAll("SELECT %s FROM %s WHERE %s = %s" % - (soClass._idName, - soClass._table, + (soClass.sqlmeta.idName, + soClass.sqlmeta.table, column, self.sqlrepr(value))) Modified: trunk/SQLObject/sqlobject/firebird/firebirdconnection.py =================================================================== --- trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/firebird/firebirdconnection.py 2005-02-16 04:31:51 UTC (rev 612) @@ -86,8 +86,8 @@ """Firebird uses 'generators' to create new ids for a table. The users needs to create a generator named GEN_<tablename> for each table this method to work.""" - table = soInstance._table - idName = soInstance._idName + table = soInstance.sqlmeta.table + idName = soInstance.sqlmeta.idName sequenceName = getattr(soInstance, '_idSequence', 'GEN_%s' % table) if id is None: @@ -122,14 +122,14 @@ def createTable(self, soClass): self.query('CREATE TABLE %s (\n%s\n)' % \ - (soClass._table, self.createColumns(soClass))) - self.query("CREATE GENERATOR GEN_%s" % soClass._table) + (soClass.sqlmeta.table, self.createColumns(soClass))) + self.query("CREATE GENERATOR GEN_%s" % soClass.sqlmeta.table) def createColumn(self, soClass, col): return col.firebirdCreateSQL() def createIDColumn(self, soClass): - return '%s INT NOT NULL PRIMARY KEY' % soClass._idName + return '%s INT NOT NULL PRIMARY KEY' % soClass.sqlmeta.idName def createIndexSQL(self, soClass, index): return index.firebirdCreateIndexSQL(soClass) @@ -188,7 +188,7 @@ if field == 'id': continue colClass, kw = self.guessClass(t, flength, fscale) - kw['name'] = soClass._style.dbColumnToPythonAttr(field) + kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field) kw['notNone'] = not nullAllowed kw['default'] = thedefault results.append(colClass(**kw)) Modified: trunk/SQLObject/sqlobject/index.py =================================================================== --- trunk/SQLObject/sqlobject/index.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/index.py 2005-02-16 04:31:51 UTC (rev 612) @@ -71,9 +71,9 @@ spec.append(desc['column'].dbName) ret = 'CREATE %s %s_%s ON %s (%s)' % \ (uniqueOrIndex, - self.soClass._table, + self.soClass.sqlmeta.table, self.name, - self.soClass._table, + self.soClass.sqlmeta.table, ', '.join(spec)) return ret @@ -94,7 +94,7 @@ spec.append(desc['column'].dbName) return 'ALTER TABLE %s ADD %s %s (%s)' % \ - (soClass._table, uniqueOrIndex, + (soClass.sqlmeta.table, uniqueOrIndex, self.name, ', '.join(spec)) Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-16 04:31:51 UTC (rev 612) @@ -19,7 +19,7 @@ if clause is None or isinstance(clause, str) and clause == 'all': clause = sqlbuilder.SQLTrueClause tablesDict = sqlbuilder.tablesUsedDict(clause) - tablesDict[sourceClass._table] = 1 + tablesDict[sourceClass.sqlmeta.table] = 1 orderBy = ops.get('orderBy') if orderBy and not isinstance(orderBy, basestring): tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) @@ -33,11 +33,11 @@ tableRegistry = {} allClasses = classregistry.registry(sourceClass._registry).allClasses() for registryClass in allClasses: - if registryClass._table in tablesDict: + if registryClass.sqlmeta.table in tablesDict: #DSM: By default, no parents are needed for the clauses tableRegistry[registryClass] = registryClass for registryClass in allClasses: - if registryClass._table in tablesDict: + if registryClass.sqlmeta.table in tablesDict: currentClass = registryClass while currentClass._parentClass: currentClass = currentClass._parentClass @@ -56,7 +56,7 @@ parentClass = currentClass._parentClass parentClause.append(currentClass.q.id == parentClass.q.id) currentClass = parentClass - tablesDict[currentClass._table] = 1 + tablesDict[currentClass.sqlmeta.table] = 1 clause = reduce(sqlbuilder.AND, parentClause, clause) super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, Modified: trunk/SQLObject/sqlobject/joins.py =================================================================== --- trunk/SQLObject/sqlobject/joins.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/joins.py 2005-02-16 04:31:51 UTC (rev 612) @@ -63,7 +63,8 @@ # Here we set up the basic join, which is # one-to-many, where the other class points to # us. - self.joinColumn = styles.getStyle(self.soClass).tableReference(self.soClass._table) + self.joinColumn = styles.getStyle( + self.soClass).tableReference(self.soClass.sqlmeta.table) def _setOtherClass(self, cls): self.otherClass = cls @@ -149,12 +150,13 @@ def _setOtherRelatedClass(self, otherClass): if not self.intermediateTable: - names = [self.soClass._table, - otherClass._table] + names = [self.soClass.sqlmeta.table, + otherClass.sqlmeta.table] names.sort() self.intermediateTable = '%s_%s' % (names[0], names[1]) if not self.otherColumn: - self.otherColumn = self.soClass._style.tableReference(otherClass._table) + self.otherColumn = self.soClass.sqlmeta.style.tableReference( + otherClass.sqlmeta.table) def hasIntermediateTable(self): Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/main.py 2005-02-16 04:31:51 UTC (rev 612) @@ -180,10 +180,15 @@ class _sqlmeta_attr(object): - def __init__(self, name): + def __init__(self, name, deprecation_level): self.name = name + self.deprecation_level = deprecation_level def __get__(self, obj, type=None): + if self.deprecation_level is not None: + deprecated( + 'Use of this attribute should be replaced with ' + '.sqlmeta.%s' % self.name, level=self.deprecation_level) return getattr((type or obj).sqlmeta, self.name) @@ -479,9 +484,9 @@ classregistry.registry(cls._registry).addClass(cls) - _style = _sqlmeta_attr('style') - _table = _sqlmeta_attr('table') - _idName = _sqlmeta_attr('idName') + _style = _sqlmeta_attr('style', 2) + _table = _sqlmeta_attr('table', 2) + _idName = _sqlmeta_attr('idName', 2) def get(cls, id, connection=None, selectResults=None): @@ -624,7 +629,7 @@ if changeSchema: conn = connection or cls._connection - conn.addColumn(cls._table, column) + conn.addColumn(cls.sqlmeta.table, column) if cls._SO_finishedClassCreation: makeProperties(cls) @@ -633,7 +638,7 @@ def addColumnsFromDatabase(cls, connection=None): conn = connection or cls._connection - for columnDef in conn.columnsFromSchema(cls._table, cls): + for columnDef in conn.columnsFromSchema(cls.sqlmeta.table, cls): alreadyExists = False for c in cls._columns: if c.name == columnDef.name: @@ -674,7 +679,7 @@ if changeSchema: conn = connection or cls._connection - conn.delColumn(cls._table, column) + conn.delColumn(cls.sqlmeta.table, column) if cls._SO_finishedClassCreation: unmakeProperties(cls) @@ -1090,7 +1095,7 @@ def _SO_fetchAlternateID(cls, dbIDName, value, connection=None): result = (connection or cls._connection)._SO_selectOneAlt( cls, - [cls._idName] + + [cls.sqlmeta.idName] + [col.dbName for col in cls._SO_columns], dbIDName, value) @@ -1139,9 +1144,9 @@ def dropTable(cls, ifExists=False, dropJoinTables=True, cascade=False, connection=None): conn = connection or cls._connection - if ifExists and not conn.tableExists(cls._table): + if ifExists and not conn.tableExists(cls.sqlmeta.table): return - conn.dropTable(cls._table, cascade) + conn.dropTable(cls.sqlmeta.table, cascade) if dropJoinTables: cls.dropJoinTables(ifExists=ifExists, connection=conn) dropTable = classmethod(dropTable) @@ -1150,7 +1155,7 @@ createIndexes=True, connection=None): conn = connection or cls._connection - if ifNotExists and conn.tableExists(cls._table): + if ifNotExists and conn.tableExists(cls.sqlmeta.table): return conn.createTable(cls) if createJoinTables: @@ -1227,7 +1232,7 @@ # 3-03 @@: Maybe this should check the cache... but it's # kind of crude anyway, so... conn = connection or cls._connection - conn.clearTable(cls._table) + conn.clearTable(cls.sqlmeta.table) clearTable = classmethod(clearTable) def destroySelf(self): Modified: trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/maxdb/maxdbconnection.py 2005-02-16 04:31:51 UTC (rev 612) @@ -119,8 +119,8 @@ return conn def _queryInsertID(self, conn, soInstance, id, names, values): - table = soInstance._table - idName = soInstance._idName + table = soInstance.sqlmeta.table + idName = soInstance.sqlmeta.idName c = conn.cursor() if id is None: c.execute('SELECT %s.NEXTVAL FROM DUAL' % (self.createSequenceName(table))) @@ -158,21 +158,21 @@ #i tried to use the transaction class but i get a recursion limit error #t=self.transaction() # t.query('CREATE TABLE %s (\n%s\n)' % \ - # (soClass._table, self.createColumns(soClass))) + # (soClass.sqlmeta.table, self.createColumns(soClass))) # - # t.query("CREATE SEQUENCE %s" % self.createSequenceName(soClass._table)) + # t.query("CREATE SEQUENCE %s" % self.createSequenceName(soClass.sqlmeta.table)) # t.commit() #so use transaction when the problem will be solved self.query('CREATE TABLE %s (\n%s\n)' % \ - (soClass._table, self.createColumns(soClass))) + (soClass.sqlmeta.table, self.createColumns(soClass))) self.query("CREATE SEQUENCE %s" - % self.createSequenceName(soClass._table)) + % self.createSequenceName(soClass.sqlmeta.table)) def createColumn(self, soClass, col): return col.maxdbCreateSQL() def createIDColumn(self, soClass): - return '%s INT PRIMARY KEY' % soClass._idName + return '%s INT PRIMARY KEY' % soClass.sqlmeta.idName def createIndexSQL(self, soClass, index): return index.maxdbCreateIndexSQL(soClass) Modified: trunk/SQLObject/sqlobject/mysql/mysqlconnection.py =================================================================== --- trunk/SQLObject/sqlobject/mysql/mysqlconnection.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/mysql/mysqlconnection.py 2005-02-16 04:31:51 UTC (rev 612) @@ -41,8 +41,8 @@ raise def _queryInsertID(self, conn, soInstance, id, names, values): - table = soInstance._table - idName = soInstance._idName + table = soInstance.sqlmeta.table + idName = soInstance.sqlmeta.idName c = conn.cursor() if id is not None: names = [idName] + names @@ -71,7 +71,7 @@ return index.mysqlCreateIndexSQL(soClass) def createIDColumn(self, soClass): - return '%s INT PRIMARY KEY AUTO_INCREMENT' % soClass._idName + return '%s INT PRIMARY KEY AUTO_INCREMENT' % soClass.sqlmeta.idName def joinSQLType(self, join): return 'INT NOT NULL' @@ -100,7 +100,7 @@ if field == 'id': continue colClass, kw = self.guessClass(t) - kw['name'] = soClass._style.dbColumnToPythonAttr(field) + kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field) kw['notNone'] = not nullAllowed kw['default'] = default # @@ skip key... Modified: trunk/SQLObject/sqlobject/postgres/pgconnection.py =================================================================== --- trunk/SQLObject/sqlobject/postgres/pgconnection.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/postgres/pgconnection.py 2005-02-16 04:31:51 UTC (rev 612) @@ -74,8 +74,8 @@ return conn def _queryInsertID(self, conn, soInstance, id, names, values): - table = soInstance._table - idName = soInstance._idName + table = soInstance.sqlmeta.table + idName = soInstance.sqlmeta.idName sequenceName = getattr(soInstance, '_idSequence', '%s_%s_seq' % (table, idName)) c = conn.cursor() @@ -106,7 +106,7 @@ return index.postgresCreateIndexSQL(soClass) def createIDColumn(self, soClass): - return '%s SERIAL PRIMARY KEY' % soClass._idName + return '%s SERIAL PRIMARY KEY' % soClass.sqlmeta.idName def dropTable(self, tableName, cascade=False): if self.server_version[:3] <= "7.2": @@ -189,7 +189,7 @@ if field == primaryKey: continue colClass, kw = self.guessClass(t) - kw['name'] = soClass._style.dbColumnToPythonAttr(field) + kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field) kw['notNone'] = notnull if defaultstr is not None: kw['default'] = getattr(sqlbuilder.const, defaultstr) Modified: trunk/SQLObject/sqlobject/sqlbuilder.py =================================================================== --- trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-16 04:31:51 UTC (rev 612) @@ -329,16 +329,16 @@ def __init__(self, soClass): self.soClass = soClass - assert soClass._table, ( + assert soClass.sqlmeta.table, ( "Bad table name in class %r: %r" - % (soClass, soClass._table)) - Table.__init__(self, soClass._table) + % (soClass, soClass.sqlmeta.table)) + Table.__init__(self, soClass.sqlmeta.table) def __getattr__(self, attr): if attr.startswith('__'): raise AttributeError if attr == 'id': - return SQLObjectField(self.tableName, self.soClass._idName, attr) + return SQLObjectField(self.tableName, self.soClass.sqlmeta.idName, attr) else: return SQLObjectField(self.tableName, self.soClass._SO_columnDict[attr].dbName, Modified: trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py =================================================================== --- trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/sqlite/sqliteconnection.py 2005-02-16 04:31:51 UTC (rev 612) @@ -42,8 +42,8 @@ return self._conn def _queryInsertID(self, conn, soInstance, id, names, values): - table = soInstance._table - idName = soInstance._idName + table = soInstance.sqlmeta.table + idName = soInstance.sqlmeta.idName c = conn.cursor() if id is not None: names = [idName] + names @@ -59,6 +59,15 @@ self.printDebug(conn, id, 'QueryIns', 'result') return id + def _insertSQL(self, table, names, values): + if not names: + assert not values + # INSERT INTO table () VALUES () isn't allowed in + # SQLite (though it is in other databases) + return ("INSERT INTO %s VALUES (NULL)" % table) + else: + return DBAPI._insertSQL(self, table, names, values) + def _queryAddLimitOffset(self, query, start, end): if not start: return "%s LIMIT %i" % (query, end) @@ -70,7 +79,7 @@ return col.sqliteCreateSQL() def createIDColumn(self, soClass): - return '%s INTEGER PRIMARY KEY' % soClass._idName + return '%s INTEGER PRIMARY KEY' % soClass.sqlmeta.idName def joinSQLType(self, join): return 'INT NOT NULL' Modified: trunk/SQLObject/sqlobject/sresults.py =================================================================== --- trunk/SQLObject/sqlobject/sresults.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/sresults.py 2005-02-16 04:31:51 UTC (rev 612) @@ -11,7 +11,7 @@ clause = sqlbuilder.SQLTrueClause self.clause = clause tablesDict = sqlbuilder.tablesUsedDict(self.clause) - tablesDict[sourceClass._table] = 1 + tablesDict[sourceClass.sqlmeta.table] = 1 if clauseTables: for table in clauseTables: tablesDict[table] = 1 Modified: trunk/SQLObject/sqlobject/styles.py =================================================================== --- trunk/SQLObject/sqlobject/styles.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/styles.py 2005-02-16 04:31:51 UTC (rev 612) @@ -115,8 +115,8 @@ if dbConnection is None: if hasattr(soClass, '_connection'): dbConnection = soClass._connection - if hasattr(soClass, '_style') and soClass._style: - return soClass._style + if hasattr(soClass.sqlmeta, 'style') and soClass.sqlmeta.style: + return soClass.sqlmeta.style elif dbConnection and dbConnection.style: return dbConnection.style else: Modified: trunk/SQLObject/sqlobject/sybase/sybaseconnection.py =================================================================== --- trunk/SQLObject/sqlobject/sybase/sybaseconnection.py 2005-02-16 04:30:22 UTC (rev 611) +++ trunk/SQLObject/sqlobject/sybase/sybaseconnection.py 2005-02-16 04:31:51 UTC (rev 612) @@ -69,8 +69,8 @@ return r is not None def _queryInsertID(self, conn, soInstance, id, names, values): - table = soInstance._table - idName = soInstance._idName + table = soInstance.sqlmeta.table + idName = soInstance.sqlmeta.idName c = conn.cursor() if id is not None: names = [idName] + names @@ -103,7 +103,7 @@ return col.sybaseCreateSQL() def createIDColumn(self, soClass): - return '%s NUMERIC(18,0) IDENTITY UNIQUE' % soClass._idName + return '%s NUMERIC(18,0) IDENTITY UNIQUE' % soClass.sqlmeta.idName def createIndexSQL(self, soClass, index): return index.sybaseCreateIndexSQL(soClass) @@ -138,7 +138,7 @@ if field == 'id': continue colClass, kw = self.guessClass(t) - kw['name'] = soClass._style.dbColumnToPythonAttr(field) + kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field) kw['notNone'] = not nullAllowed kw['default'] = default # @@ skip key... |
From: <sub...@co...> - 2005-02-16 04:30:26
|
Author: ianb Date: 2005-02-16 04:30:22 +0000 (Wed, 16 Feb 2005) New Revision: 611 Added: trunk/SQLObject/sqlobject/tests/test_create_drop.py Log: Explicitly test creation and dropping, including with ifExists and ifNotExists Added: trunk/SQLObject/sqlobject/tests/test_create_drop.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_create_drop.py 2005-02-16 04:29:41 UTC (rev 610) +++ trunk/SQLObject/sqlobject/tests/test_create_drop.py 2005-02-16 04:30:22 UTC (rev 611) @@ -0,0 +1,29 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +class TestCreateDrop(SQLObject): + class sqlmeta(sqlmeta): + idName = 'test_id_here' + table = 'test_create_drop_table' + name = StringCol() + number = IntCol() + time = DateTimeCol() + short = StringCol(length=10) + blobcol = BLOBCol() + + +def test_create_drop(): + conn = getConnection() + TestCreateDrop.setConnection(conn) + TestCreateDrop.dropTable(ifExists=True) + assert not conn.tableExists(TestCreateDrop.sqlmeta.table) + TestCreateDrop.createTable(ifNotExists=True) + assert conn.tableExists(TestCreateDrop.sqlmeta.table) + TestCreateDrop.createTable(ifNotExists=True) + assert conn.tableExists(TestCreateDrop.sqlmeta.table) + TestCreateDrop.dropTable(ifExists=True) + assert not conn.tableExists(TestCreateDrop.sqlmeta.table) + TestCreateDrop.dropTable(ifExists=True) + assert not conn.tableExists(TestCreateDrop.sqlmeta.table) + + |
From: <sub...@co...> - 2005-02-16 04:29:45
|
Author: ianb Date: 2005-02-16 04:29:41 +0000 (Wed, 16 Feb 2005) New Revision: 610 Modified: trunk/SQLObject/sqlobject/tests/test_select.py Log: Excersize the lazyColumns option Modified: trunk/SQLObject/sqlobject/tests/test_select.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_select.py 2005-02-16 04:28:29 UTC (rev 609) +++ trunk/SQLObject/sqlobject/tests/test_select.py 2005-02-16 04:29:41 UTC (rev 610) @@ -26,6 +26,13 @@ count += 1 assert count == len(names) +def test_00b_lazy(): + setupIter() + count = 0 + for test in IterTest.select(lazyColumns=True): + count += 1 + assert count == len(names) + def test_01_turn_to_list(): count = 0 for test in list(IterTest.select()): @@ -46,6 +53,7 @@ test = all[i] count += 1 assert count == len(names) + def test_04_indexed_ended_by_exception(): all = IterTest.select() |
From: <sub...@co...> - 2005-02-16 04:28:33
|
Author: ianb Date: 2005-02-16 04:28:29 +0000 (Wed, 16 Feb 2005) New Revision: 609 Modified: trunk/SQLObject/sqlobject/tests/test_auto.py Log: Fix a bunch of small issues, like badly named attributes, and names that didn't exist. Modified: trunk/SQLObject/sqlobject/tests/test_auto.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-16 04:26:43 UTC (rev 608) +++ trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-16 04:28:29 UTC (rev 609) @@ -1,6 +1,11 @@ from sqlobject import * from sqlobject.tests.dbtest import * from sqlobject import classregistry +try: + from datetime import datetime + now = datetime.now +except ImportError: + from mx.DateTime import now ######################################## ## Dynamic column tests @@ -115,29 +120,42 @@ DROP TABLE auto_test """ - _table = 'auto_test' + def setup_method(self, meth): + conn = getConnection() + dbName = conn.dbName + creator = getattr(self, dbName + 'Create', None) + if creator: + conn.query(creator) + def teardown_method(self, meth): + conn = getConnection() + dbName = conn.dbName + dropper = getattr(self, dbName + 'Drop', None) + if dropper: + conn.query(dropper) + def test_classCreate(self): if not supports('fromDatabase'): return class AutoTest(SQLObject): _fromDatabase = True - _idName = 'auto_id' - _connection = connection() + _connection = getConnection() + class sqlmeta(sqlmeta): + idName = 'auto_id' john = AutoTest(firstName='john', lastName='doe', age=10, created=now(), wannahavefun=False, - long_field='x'*1000) + longField='x'*1000) jane = AutoTest(firstName='jane', lastName='doe', happy='N', created=now(), wannahavefun=True, - long_field='x'*1000) + longField='x'*1000) assert not john.wannahavefun assert jane.wannahavefun - assert john.long_field == 'x'*1000 - assert jane.long_field == 'x'*1000 + assert john.longField == 'x'*1000 + assert jane.longField == 'x'*1000 del classregistry.registry(AutoTest._registry).classes['AutoTest'] |
From: <sub...@co...> - 2005-02-16 04:26:45
|
Author: ianb Date: 2005-02-16 04:26:43 +0000 (Wed, 16 Feb 2005) New Revision: 608 Modified: trunk/SQLObject/sqlobject/tests/dbtest.py Log: * Use new attributes * Be more careful about supports -- negation supports weren't working before (supports like '-dynamicColumns': 'dbs...') Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-16 04:25:58 UTC (rev 607) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-16 04:26:43 UTC (rev 608) @@ -96,7 +96,7 @@ reversed = list(soClasses)[:] reversed.reverse() for soClass in reversed: - table = soClass._table + table = soClass.sqlmeta.table if not soClass._connection.tableExists(table): continue items = list(cls.selectBy( @@ -115,7 +115,7 @@ else: cls.clear(soClass) for soClass in soClasses: - table = soClass._table + table = soClass.sqlmeta.table if not soClass._connection.tableExists(table): cls.install(soClass) installOrClear = classmethod(installOrClear) @@ -131,7 +131,7 @@ else: sql = soClass.createTableSQL() soClass.createTable() - cls(tableName=soClass._table, + cls(tableName=soClass.sqlmeta.table, createSQL=sql, connectionURI=soClass._connection.uri()) install = classmethod(install) @@ -158,7 +158,7 @@ """ This sets up *this* table. """ - if not cls._connection.tableExists(cls._table): + if not cls._connection.tableExists(cls.sqlmeta.table): cls.createTable() setup = classmethod(setup) @@ -216,11 +216,11 @@ notSupport = supportsMatrix.get('-' + feature, None) if support is not None and dbName in support.split(): return True - else: + elif support: return False if notSupport is not None and dbName in notSupport.split(): return False - else: + elif notSupport: return True assert notSupport is not None or support is not None, ( "The supportMatrix does not list this feature: %r" |
From: <sub...@co...> - 2005-02-16 04:26:01
|
Author: ianb Date: 2005-02-16 04:25:58 +0000 (Wed, 16 Feb 2005) New Revision: 607 Modified: trunk/SQLObject/sqlobject/tests/test_transactions.py Log: Use proper setupClass() invocations Modified: trunk/SQLObject/sqlobject/tests/test_transactions.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_transactions.py 2005-02-16 04:25:03 UTC (rev 606) +++ trunk/SQLObject/sqlobject/tests/test_transactions.py 2005-02-16 04:25:58 UTC (rev 607) @@ -10,10 +10,12 @@ name = StringCol(length=10, alternateID=True, dbName='name_col') _defaultOrderBy = 'name' -def testTransaction(): +def test_transaction(): if not supports('transactions'): return - setupClass(TestSOTrans, [d(name='bob'), d(name='tim')]) + setupClass(TestSOTrans) + TestSOTrans(name='bob') + TestSOTrans(name='tim') trans = TestSOTrans._connection.transaction() try: TestSOTrans._connection.autoCommit = 'exception' |
From: <sub...@co...> - 2005-02-16 04:25:06
|
Author: ianb Date: 2005-02-16 04:25:03 +0000 (Wed, 16 Feb 2005) New Revision: 606 Modified: trunk/SQLObject/sqlobject/tests/test_basic.py Log: Get the setupClass calls right so that it creates and destroys tables in the right order Modified: trunk/SQLObject/sqlobject/tests/test_basic.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-16 04:24:29 UTC (rev 605) +++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-16 04:25:03 UTC (rev 606) @@ -115,9 +115,7 @@ name = StringCol(length=10, dbName='name_col') def test_foreignKeyDestroySelfCascade(): - setupClass(TestSO7) - setupClass(TestSO6) - setupClass(TestSO5) + setupClass([TestSO7, TestSO6, TestSO5]) tc5 = TestSO5(name='a') tc6a = TestSO6(name='1') @@ -204,8 +202,7 @@ name = StringCol(length=10, dbName='name_col') def testForeignKeyDestroySelfRestrict(): - setupClass(TestSO9) - setupClass(TestSO8) + setupClass([TestSO9, TestSO8]) tc8a = TestSO8(name='a') tc9a = TestSO9(name='1') |
From: <sub...@co...> - 2005-02-16 04:24:36
|
Author: ianb Date: 2005-02-16 04:24:29 +0000 (Wed, 16 Feb 2005) New Revision: 605 Added: trunk/SQLObject/sqlobject/tests/test_empty.py Log: Added a test for empty classes (classes with no columns) Added: trunk/SQLObject/sqlobject/tests/test_empty.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_empty.py 2005-02-16 03:06:30 UTC (rev 604) +++ trunk/SQLObject/sqlobject/tests/test_empty.py 2005-02-16 04:24:29 UTC (rev 605) @@ -0,0 +1,17 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +class EmptyClass(SQLObject): + + pass + +def test_empty(): + setupClass(EmptyClass) + e1 = EmptyClass() + e2 = EmptyClass() + assert e1 != e2 + assert e1.id != e2.id + assert e1 in list(EmptyClass.select()) + assert e2 in list(EmptyClass.select()) + e1.destroySelf() + assert list(EmptyClass.select()) == [e2] |
From: <sub...@co...> - 2005-02-16 03:06:34
|
Author: ianb Date: 2005-02-16 03:06:30 +0000 (Wed, 16 Feb 2005) New Revision: 604 Modified: trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py Log: Renamed Person class to InherPerson, so its name doesn't conflict with the name of the table in test_auto Modified: trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-14 02:31:35 UTC (rev 603) +++ trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-16 03:06:30 UTC (rev 604) @@ -7,29 +7,29 @@ ######################################## -class Person(InheritableSQLObject): +class InherPerson(InheritableSQLObject): _inheritable = 1 # I want this class to be inherited firstName = StringCol() lastName = StringCol() -class Employee(Person): +class Employee(InherPerson): _inheritable = 0 # If I don't want this class to be inherited position = StringCol() def setup(): - setupClass(Person) + setupClass(InherPerson) setupClass(Employee) Employee(firstName='Ian', lastName='Bicking', position='Project leader') - Person(firstName='Daniel', lastName='Savard') + InherPerson(firstName='Daniel', lastName='Savard') def test_inheritance(): setup() - persons = Person.select() # all + persons = InherPerson.select() # all for person in persons: - assert isinstance(person, Person) + assert isinstance(person, InherPerson) if isinstance(person, Employee): assert not hasattr(person, "childName") else: @@ -40,7 +40,7 @@ def test_inheritance_select(): setup() - persons = Person.select(Person.q.firstName <> None) + persons = InherPerson.select(InherPerson.q.firstName <> None) assert persons.count() == 2 employees = Employee.select(Employee.q.firstName <> None) |
From: <sub...@co...> - 2005-02-11 12:24:38
|
Author: phd Date: 2005-02-11 12:24:33 +0000 (Fri, 11 Feb 2005) New Revision: 602 Modified: trunk/SQLObject/setup.py trunk/SQLObject/sqlobject/inheritance/__init__.py Log: Do not pass parent's values to the child's _create() to avoid double UPDATEing of these values. Added 'inheritance' subpackage to setup.py. Modified: trunk/SQLObject/setup.py =================================================================== --- trunk/SQLObject/setup.py 2005-02-11 12:20:51 UTC (rev 601) +++ trunk/SQLObject/setup.py 2005-02-11 12:24:33 UTC (rev 602) @@ -2,7 +2,7 @@ import warnings warnings.filterwarnings("ignore", "Unknown distribution option") -subpackages = ['firebird', 'include', 'mysql', 'postgres', +subpackages = ['firebird', 'include', 'inheritance', 'mysql', 'postgres', 'sqlite', 'sybase', 'maxdb'] import sys Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-11 12:20:51 UTC (rev 601) +++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-11 12:24:33 UTC (rev 602) @@ -178,11 +178,14 @@ #DSM: we must first create our parent if self._parentClass: parentClass = self._parentClass - parent_kw = dict( - [(name, value) for (name, value) in kw.items() - if hasattr(parentClass, name) - ] - ) + new_kw = {} + parent_kw = {} + for (name, value) in kw.items(): + if hasattr(parentClass, name): + parent_kw[name] = value + else: + new_kw[name] = value + kw = new_kw self._parent = parentClass(kw=parent_kw) self._parent.childName = self.__class__.__name__ id = self._parent.id |
From: <sub...@co...> - 2005-02-11 12:20:57
|
Author: phd Date: 2005-02-11 12:20:51 +0000 (Fri, 11 Feb 2005) New Revision: 601 Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py trunk/SQLObject/sqlobject/tests/test_unicode.py Log: Minor bugfixes for Python 2.2. Modified: trunk/SQLObject/sqlobject/inheritance/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-10 19:46:14 UTC (rev 600) +++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-11 12:20:51 UTC (rev 601) @@ -4,6 +4,13 @@ import iteration +try: + basestring +except NameError: # Python 2.2 + import types + basestring = (types.StringType, types.UnicodeType) + + class InheritableSelectResults(SelectResults): IterationClass = iteration.InheritableIteration Modified: trunk/SQLObject/sqlobject/tests/test_unicode.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_unicode.py 2005-02-10 19:46:14 UTC (rev 600) +++ trunk/SQLObject/sqlobject/tests/test_unicode.py 2005-02-11 12:20:51 UTC (rev 601) @@ -10,6 +10,15 @@ col1 = UnicodeCol() col2 = UnicodeCol(dbEncoding='latin-1') +try: + enumerate +except NameError: # Python 2.2 + def enumerate(list): + new_list = [] + for i in range(len(list)): + new_list.append((i, list[0])) + return new_list + def test_create(): setupClass(Unicode1) data = [u'\u00f0', u'test', 'ascii test'] |
From: <sub...@co...> - 2005-02-10 19:46:25
|
Author: phd Date: 2005-02-10 19:46:14 +0000 (Thu, 10 Feb 2005) New Revision: 600 Added: trunk/SQLObject/docs/Inheritance.txt trunk/SQLObject/sqlobject/inheritance/ trunk/SQLObject/sqlobject/inheritance/__init__.py trunk/SQLObject/sqlobject/inheritance/iteration.py trunk/SQLObject/sqlobject/inheritance/tests/ trunk/SQLObject/sqlobject/inheritance/tests/__init__.py trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py Modified: trunk/SQLObject/docs/Authors.txt trunk/SQLObject/docs/FAQ.txt trunk/SQLObject/sqlobject/classregistry.py trunk/SQLObject/sqlobject/col.py trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/main.py trunk/SQLObject/sqlobject/sresults.py Log: Inheritance patch! Modified: trunk/SQLObject/docs/Authors.txt =================================================================== --- trunk/SQLObject/docs/Authors.txt 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/docs/Authors.txt 2005-02-10 19:46:14 UTC (rev 600) @@ -13,4 +13,7 @@ * James Ralston <jralston at hotmail.com> * Sidnei da Silva <sidnei at awkly.org> * Brad Bollenbach <brad at bbnet.ca> +* Daniel Savard, Xsoli Inc <sqlobject at xsoli.com> +* alexander smishlajev <alex at ank-sia.com> +* Yaroslav Samchuk <yarcat at ank-sia.com> * Oleg Broytmann <phd at phd.pp.ru> Modified: trunk/SQLObject/docs/FAQ.txt =================================================================== --- trunk/SQLObject/docs/FAQ.txt 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/docs/FAQ.txt 2005-02-10 19:46:14 UTC (rev 600) @@ -107,6 +107,11 @@ anyway). +There is also another kind of inheritance. See Inheritance.txt_ + +.. _Inheritance.txt: Inheritance.txt + + Composite/Compound Attributes ----------------------------- Added: trunk/SQLObject/docs/Inheritance.txt =================================================================== --- trunk/SQLObject/docs/Inheritance.txt 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/docs/Inheritance.txt 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1,134 @@ +:Author: Daniel Savard, XSOLI Inc. + +Inheritance +----------- + +* As suggested by Ian Bicking, each child class now has the same + ID than the parent class. No more need for childID column and + parent foreignKey (with a small speed boost). +* No more need to call getSubClass as the 'latest' child will always + be returned when an instance of a class is created. +* This version now seems to works correctly with addColumn, delColumn, + addJoin and delJoin. + + +The following code:: + + class Person(SQLObject): + _inheritable = 1 # I want this class to be inherited + firstName = StringCol() + lastName = StringCol() + + class Employee(Person): + _inheritable = 0 # If I don't want this class to be inherited + position = StringCol() + +will generate the following tables:: + + CREATE TABLE person ( + id INT PRIMARY KEY, + child_name TEXT, + first_name TEXT, + last_name TEXT + ); + + CREATE TABLE employee ( + id INT PRIMARY KEY, + position TEXT + ) + + +A new class attribute ``_inheritable`` is added. When this new +attribute is set to 1, the class is marked 'inheritable' and a new +columns will automatically be added: childName (TEXT). + +Each class that inherits from a parent class will get the same ID as +the parent class. So, there is no need to keep track of parent ID and +child ID as they are the same. + +The column childName will contain the name of the child class (for +exemple 'Employee'). This will permit to a class to always return its +child class if available (a person that is also an employee will always +return an instance of the employee class). + +For exemple, the following code:: + + p = Person(firstName='John', lastName='Doe') + e = Employee(firstName='Jane', lastName='Doe', position='Chief') + p2 = Person.get(1) + +Will create the following data in the database:: + + *Person* + id + child_name + first_name + last_name + 0 + Null + John + Doe + 1 + Employee + Jane + Doe + + + *Employee* + id + position + 1 + Chief + + +You will still be able to ask for the attribute normally: +e.firstName will return Jane and setting it will write the new value in +the person table. + +If you use p2, as p2 is a person object, you will get an employee +object. +person(0) will return a Person instance and will have the following +attributes: firstName and lastName +person(1) or employee(1) will each return the same Employee instance and +will have the following attributes: firstName, lastName and position + +Also, deleting a person or an employee that are linked will destroy +both entries as one would expect. + +The SQLObject q magic also work. Using this select are valid:: + + Employee.select(AND(Employee.q.firstName == 'Jane' Employee.q.position == 'Chief')) will return Jane Doe + Employee.select(AND(Person.q.firstName == 'Jane', Employee.q.position == 'Chief')) will return Jane Doe + Employee.select(Employee.q.lastName == 'Doe') will only return Jane Doe (as Joe isn't an employee) + Person.select(Person.q.lastName == 'Doe') will return both entries. + +The SQL 'where' clause will contain additional clauses when used with +'inherited' classes. These clauses are the link between the id and the +parent id. This will look like the following request:: + + SELECT employee.id, person.first_name, person.last_name + FROM person, employee WHERE person.first_name = 'Jane' + AND employee.position = 'Chief' AND person.id = employee.id + +Some limitation or notice about this version: + +* Only simple inheritance will work. It is not possible to inherit + from multiple SQLObject classes. +* It is possible to inherit from an inherited class and this will + work well. In the above exemple, you can have a Chief class that + inherits from Employee and all parents attributes will be + available through the Chief class. +* You may not redefine columns in an inherited class (this + will raise an exception). +* If you don't want 'childName' columns in your last class (one that + will never be inherited), you must set '_inheritable' to 0 in this + class. +* I made it because I needed to be able to have automatic + inheritance with linked table. +* This version works for me, it may not works for you. I tried to do + my best but it is possible that I broke some things... So, there + is no warranty that this version will work. +* Thanks to Ian Bicking for SQLObject, this is a wonderful python + module. +* If you have suggestion, bugs, or patch to this patch, you can + contact me at <sqlobject xsoli.com> Modified: trunk/SQLObject/sqlobject/classregistry.py =================================================================== --- trunk/SQLObject/sqlobject/classregistry.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/classregistry.py 2005-02-10 19:46:14 UTC (rev 600) @@ -110,3 +110,6 @@ MasterRegistry = _MasterRegistry() registry = MasterRegistry.registry + +def findClass(name, class_registry=None): + return registry(class_registry).getClass(name) Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/col.py 2005-02-10 19:46:14 UTC (rev 600) @@ -28,6 +28,7 @@ # arguments in this module, so we rename it: import constraints as consts from include import validators +from classregistry import findClass NoDefault = sqlbuilder.NoDefault True, False = 1==1, 0==1 @@ -616,7 +617,6 @@ SOKeyCol.__init__(self, **kw) def postgresCreateSQL(self): - from main import findClass sql = SOKeyCol.postgresCreateSQL(self) other = findClass(self.foreignKey) tName = other._table @@ -642,7 +642,6 @@ return sql def sybaseCreateSQL(self): - from sqlobject.main import findClass sql = SOKeyCol.sybaseCreateSQL(self) other = findClass(self.foreignKey) tName = other._table @@ -654,7 +653,6 @@ return sql def maxdbCreateSQL(self): - from main import findClass other = findClass(self.foreignKey) fidName = self.dbName #I assume that foreign key name is identical to the id of the reference table Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-10 19:46:14 UTC (rev 600) @@ -15,6 +15,7 @@ from converters import sqlrepr import urllib import weakref +from classregistry import findClass warnings.filterwarnings("ignore", "DB-API extension cursor.lastrowid used") @@ -246,7 +247,7 @@ return self._runWithConnection(self._queryInsertID, soInstance, id, names, values) def iterSelect(self, select): - return Iteration(self, self.getConnection(), + return select.IterationClass(self, self.getConnection(), select, keepConnection=False) def accumulateSelect(self, select, expression): @@ -542,9 +543,7 @@ def __del__(self): self._cleanup() - - class Transaction(object): def __init__(self, dbConnection): @@ -581,7 +580,7 @@ # still iterating through the results. # @@: But would it be okay for psycopg, with threadsafety # level 2? - return iter(list(Iteration(self, self._connection, + return iter(list(select.IterationClass(self, self._connection, select, keepConnection=True))) def commit(self): Added: trunk/SQLObject/sqlobject/inheritance/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/inheritance/__init__.py 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1,192 @@ +from sqlobject import sqlbuilder +from sqlobject import classregistry +from sqlobject.main import SQLObject, SelectResults, True, False, makeProperties, getterName, setterName +import iteration + + +class InheritableSelectResults(SelectResults): + IterationClass = iteration.InheritableIteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + tablesDict = sqlbuilder.tablesUsedDict(clause) + tablesDict[sourceClass._table] = 1 + orderBy = ops.get('orderBy') + if orderBy and not isinstance(orderBy, basestring): + tablesDict.update(sqlbuilder.tablesUsedDict(orderBy)) + #DSM: if this class has a parent, we need to link it + #DSM: and be sure the parent is in the table list. + #DSM: The following code is before clauseTables + #DSM: because if the user uses clauseTables + #DSM: (and normal string SELECT), he must know what he wants + #DSM: and will do himself the relationship between classes. + if type(clause) is not str: + tableRegistry = {} + allClasses = classregistry.registry(sourceClass._registry).allClasses() + for registryClass in allClasses: + if registryClass._table in tablesDict: + #DSM: By default, no parents are needed for the clauses + tableRegistry[registryClass] = registryClass + for registryClass in allClasses: + if registryClass._table in tablesDict: + currentClass = registryClass + while currentClass._parentClass: + currentClass = currentClass._parentClass + if tableRegistry.has_key(currentClass): + #DSM: Must keep the last parent needed + #DSM: (to limit the number of join needed) + tableRegistry[registryClass] = currentClass + #DSM: Remove this class as it is a parent one + #DSM: of a needed children + del tableRegistry[currentClass] + #DSM: Table registry contains only the last children + #DSM: or standalone classes + parentClause = [] + for (currentClass, minParentClass) in tableRegistry.items(): + while currentClass != minParentClass and currentClass._parentClass: + parentClass = currentClass._parentClass + parentClause.append(currentClass.q.id == parentClass.q.id) + currentClass = parentClass + tablesDict[currentClass._table] = 1 + clause = reduce(sqlbuilder.AND, parentClause, clause) + + super(InheritableSelectResults, self).__init__(sourceClass, clause, clauseTables, + **ops) + + +class InheritableSQLObject(SQLObject): + SelectResultsClass = InheritableSelectResults + + def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): + + val = super(InheritableSQLObject, cls).get(id, connection, selectResults) + + #DSM: If we are updating a child, we should never return a child... + if childUpdate: return val + #DSM: If this class has a child, return the child + if hasattr(val, 'childName'): + childName = val.childName + if childName is not None: + return val._childClasses[childName].get(id, selectResults=childResults) + #DSM: Now, we know we are alone or the last child in a family... + #DSM: It's time to find our parents + inst = val + while inst._parentClass and not inst._parent: + inst._parent = inst._parentClass.get(id, childUpdate=True) + inst = inst._parent + #DSM: We can now return ourself + return val + + get = classmethod(get) + + def addColumn(cls, columnDef, changeSchema=False, connection=None, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic column to refresh the current class + if childUpdate or cls._parentClass: + for col in cls._parentClass._columns: + cname = col.name + if cname == 'childName': continue + setattr(cls, getterName(cname), eval( + 'lambda self: self._parent.%s' % cname)) + if not col.kw.has_key('immutable') or not col.kw['immutable']: + setattr(cls, setterName(cname), eval( + 'lambda self, val: setattr(self._parent, %s, val)' + % repr(cname))) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addColumn(columnDef, changeSchema, connection) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic column as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addColumn(columnDef, childUpdate=True) + + addColumn = classmethod(addColumn) + + def delColumn(cls, column, changeSchema=False, connection=None): + super(InheritableSQLObject, cls).delColumn(column, changeSchema, connection) + + #DSM: Update each child class if needed + #DSM: and delete properties for this column + for c in cls._childClasses.values(): + delattr(c, name) + + delColumn = classmethod(delColumn) + + def addJoin(cls, joinDef, childUpdate=False): + #DSM: Try to add parent properties to the current class + #DSM: Only do this once if possible at object creation and once for + #DSM: each new dynamic join to refresh the current class + if childUpdate or cls._parentClass: + for jdef in cls._parentClass._joins: + join = jdef.withClass(cls) + jname = join.joinMethodName + jarn = join.addRemoveName + setattr(cls, getterName(jname), + eval('lambda self: self._parent.%s' % jname)) + if hasattr(join, 'remove'): + setattr(cls, 'remove' + jarn, + eval('lambda self,o: self._parent.remove%s(o)' % jarn)) + if hasattr(join, 'add'): + setattr(cls, 'add' + jarn, + eval('lambda self,o: self._parent.add%s(o)' % jarn)) + if childUpdate: + makeProperties(cls) + return + + super(InheritableSQLObject, cls).addJoin(joinDef) + + #DSM: Update each child class if needed and existing (only for new + #DSM: dynamic join as no child classes exists at object creation) + for c in cls._childClasses.values(): + c.addJoin(joinDef, childUpdate=True) + + addJoin = classmethod(addJoin) + + def delJoin(cls, joinDef): + super(InheritableSQLObject, cls).delJoin(joinDef) + + #DSM: Update each child class if needed + #DSM: and delete properties for this join + for c in cls._childClasses.values(): + delattr(c, meth) + + delJoin = classmethod(delJoin) + + def _create(self, id, **kw): + + #DSM: If we were called by a children class, + #DSM: we must retreive the properties dictionary. + #DSM: Note: we can't use the ** call paremeter directly + #DSM: as we must be able to delete items from the dictionary + #DSM: (and our children must know that the items were removed!) + if kw.has_key('kw'): + kw = kw['kw'] + #DSM: If we are the children of an inheritable class, + #DSM: we must first create our parent + if self._parentClass: + parentClass = self._parentClass + parent_kw = dict( + [(name, value) for (name, value) in kw.items() + if hasattr(parentClass, name) + ] + ) + self._parent = parentClass(kw=parent_kw) + self._parent.childName = self.__class__.__name__ + id = self._parent.id + + super(InheritableSQLObject, self)._create(id, **kw) + + def destroySelf(self): + #DSM: If this object has parents, recursivly kill them + if hasattr(self, '_parent') and self._parent: + self._parent.destroySelf() + super(InheritableSQLObject, self).destroySelf() + + +__all__ = ['InheritableSQLObject'] Added: trunk/SQLObject/sqlobject/inheritance/iteration.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/inheritance/iteration.py 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1,76 @@ +from sqlobject import sqlbuilder +from sqlobject.classregistry import findClass +from sqlobject.dbconnection import Iteration + +class InheritableIteration(Iteration): + #phd: default array size for cursor.fetchmany() + defaultArraySize = 10000 + + def __init__(self, dbconn, rawconn, select, keepConnection=False): + super(InheritableIteration, self).__init__(dbconn, rawconn, select, keepConnection) + self.cursor.arraysize = self.defaultArraySize + self._results = [] + #phd: find the index of the childName column + childNameIdx = None + columns = select.sourceClass._SO_columns + for i in range(len(columns)): #phd: enumerate() is unavailable python 2.2 + if columns[i].name == "childName": + childNameIdx = i + break + self._childNameIdx = childNameIdx + + def next(self): + lazyColumns = self.select.ops.get('lazyColumns', 0) + if not self._results: + self._results = list(self.cursor.fetchmany()) + if not lazyColumns: self.fetchChildren() + if not self._results: + self._cleanup() + raise StopIteration + result = self._results[0] + del self._results[0] + if lazyColumns: + obj = self.select.sourceClass.get(result[0], connection=self.dbconn) + return obj + else: + id = result[0] + if id in self._childrenResults: + childResults = self._childrenResults[id] + del self._childrenResults[id] + else: + childResults = None + obj = self.select.sourceClass.get(id, selectResults=result[1:], + childResults=childResults, connection=self.dbconn) + return obj + + def fetchChildren(self): + """Prefetch childrens' data + + Fetch childrens' data for every subclass in one big .select() + to avoid .get() fetching it one by one. + """ + self._childrenResults = {} + if self._childNameIdx is None: + return + childIdsNames = {} + childNameIdx = self._childNameIdx + for result in self._results: + childName = result[childNameIdx+1] + if childName: + ids = childIdsNames.get(childName) + if ids is None: + ids = childIdsNames[childName] = [] + ids.append(result[0]) + dbconn = self.dbconn + rawconn = self.rawconn + cursor = rawconn.cursor() + registry = self.select.sourceClass._registry + for childName, ids in childIdsNames.items(): + klass = findClass(childName, registry) + select = klass.select(sqlbuilder.IN(sqlbuilder.SQLConstant("id"), ids)) + query = dbconn.queryForSelect(select) + if dbconn.debug: + dbconn.printDebug(rawconn, query, 'Select children of the class %s' % childName) + self.dbconn._executeRetry(rawconn, cursor, query) + for result in cursor.fetchall(): + self._childrenResults[result[0]] = result[1:] Added: trunk/SQLObject/sqlobject/inheritance/tests/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/tests/__init__.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/inheritance/tests/__init__.py 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1 @@ +# Added: trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py =================================================================== --- trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/inheritance/tests/test_inheritance.py 2005-02-10 19:46:14 UTC (rev 600) @@ -0,0 +1,50 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject.inheritance import InheritableSQLObject + +######################################## +## Inheritance +######################################## + + +class Person(InheritableSQLObject): + _inheritable = 1 # I want this class to be inherited + firstName = StringCol() + lastName = StringCol() + +class Employee(Person): + _inheritable = 0 # If I don't want this class to be inherited + position = StringCol() + +def setup(): + setupClass(Person) + setupClass(Employee) + + Employee(firstName='Ian', lastName='Bicking', position='Project leader') + Person(firstName='Daniel', lastName='Savard') + + +def test_inheritance(): + setup() + + persons = Person.select() # all + for person in persons: + assert isinstance(person, Person) + if isinstance(person, Employee): + assert not hasattr(person, "childName") + else: + assert hasattr(person, "childName") + assert not person.childName + + +def test_inheritance_select(): + setup() + + persons = Person.select(Person.q.firstName <> None) + assert persons.count() == 2 + + employees = Employee.select(Employee.q.firstName <> None) + assert employees.count() == 1 + + employees = Employee.select(Employee.q.position <> None) + assert employees.count() == 1 Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/main.py 2005-02-10 19:46:14 UTC (rev 600) @@ -4,6 +4,14 @@ SQLObject is a object-relational mapper. See SQLObject.html or SQLObject.txt for more. +Modified by + Daniel Savard, Xsoli Inc <sqlobject xsoli.com> 7 Feb 2004 + - Added support for simple table inheritance. + + Oleg Broytmann, SIA "ANK" <ph...@ph...> 3 Feb 2005 + - Split inheritance support into a number of separate modules and classes - + InheritableSQLObject at al. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the @@ -110,10 +118,6 @@ delFunc(obj, var) break -def findClass(name, registry=None): - #assert classRegistry.get(registry, {}).has_key(name), "No class by the name %s found (I have %s)" % (repr(name), ', '.join(map(str, classRegistry.keys()))) - return classregistry.registry(registry).getClass(name) - def findDependencies(name, registry=None): depends = [] for klass in classregistry.registry(registry).allClasses(): @@ -181,8 +185,8 @@ def __get__(self, obj, type=None): return getattr((type or obj).sqlmeta, self.name) - + # @@: This should become a public interface or documented or # something. Turning it on gives earlier warning about things # that will be deprecated (having this off we won't flood people @@ -260,6 +264,17 @@ sqlmeta = sqlmeta + #DSM: The _inheritable attribute controls wheter the class can by + #DSM: inherited 'logically' with a foreignKey and back reference. + _inheritable = False # Does this class is inheritable + _parentClass = None # A reference to the parent class + _parent = None # A reference to the parent instance + _childClasses = {} # Reference to child classes + childName = None # Children name (to be able to get a subclass) + + # The law of Demeter: the class should not call another classes by name + SelectResultsClass = SelectResults + def __classinit__(cls, new_attrs): # This is true if we're initializing the SQLObject class, @@ -329,6 +344,40 @@ # superclass's _columns list, so we make a copy if necessary if not new_attrs.has_key('_columns'): cls._columns = cls._columns[:] + + #DSM: Need to know very soon if the class is a child of an + #DSM: inheritable class. If so, we keep a link to our parent class. + cls._childClasses = {} + for _cls in cls.__bases__: + if hasattr(_cls, '_inheritable') and _cls._inheritable: + cls._parentClass = _cls + cls._parent = None + _cls._childClasses[cls.__name__] = cls + + #DSM: If this class is a child of a parent class, we need to do some + #DSM: attribute check and a a foreign key to the parent. + if cls._parentClass: + #DSM: First, look for invalid column name: + #DSM: reserved ones or same as a parent + parentCols = [column.name for column in cls._columns] + for column in implicitColumns: + cname = column.name + if cname in ['childName']: + raise AttributeError, \ + "The column name '%s' is reserved" % cname + if cname in parentCols: + raise AttributeError, "The column '%s' is already " \ + "defined in an inheritable parent" % cname + #DSM: Remove columns if inherited from an inheritable class + #DSM: as we don't want them. All we want is a foreign key + #DSM: that will link to our parent + cls._columns = [] + #DSM: If this is inheritable, add some default columns + # to be able to link to children + if hasattr(cls, '_inheritable') and cls._inheritable: + cls._columns.append( + col.StringCol(name='childName',default=None)) + cls._columns.extend(implicitColumns) if not new_attrs.has_key('_joins'): cls._joins = cls._joins[:] @@ -419,6 +468,15 @@ if not is_base: cls.q = sqlbuilder.SQLObjectTable(cls) + #DSM: If we are a child, get the q magic from the parent + currentClass = cls + while currentClass._parentClass: + currentClass = currentClass._parentClass + for column in currentClass._columns: + if type(column) == col.ForeignKey: continue + setattr(cls.q, column.name, + getattr(currentClass.q, column.name)) + classregistry.registry(cls._registry).addClass(cls) _style = _sqlmeta_attr('style') @@ -488,7 +546,7 @@ # Here if the _get_columnName method isn't in the # definition, we add it with the default # _SO_get_columnName definition. - if not hasattr(cls, getterName(name)): + if not hasattr(cls, getterName(name)) or (name == 'childName'): setattr(cls, getterName(name), getter) cls._SO_plainGetters[name] = 1 @@ -506,7 +564,7 @@ setattr(cls, '_SO_toPython_%s' % name, column.toPython) setattr(cls, rawSetterName(name), setter) # Then do the aliasing - if not hasattr(cls, setterName(name)): + if not hasattr(cls, setterName(name)) or (name == 'childName'): setattr(cls, setterName(name), setter) # We keep track of setters that haven't been # overridden, because we can combine these @@ -1060,7 +1118,7 @@ lazyColumns=False, reversed=False, distinct=False, connection=None): - return SelectResults(cls, clause, + return cls.SelectResultsClass(cls, clause, clauseTables=clauseTables, orderBy=orderBy, limit=limit, @@ -1072,7 +1130,7 @@ def selectBy(cls, connection=None, **kw): conn = connection or cls._connection - return SelectResults(cls, + return cls.SelectResultsClass(cls, conn._SO_columnClause(cls, kw), connection=conn) Modified: trunk/SQLObject/sqlobject/sresults.py =================================================================== --- trunk/SQLObject/sqlobject/sresults.py 2005-02-10 18:46:08 UTC (rev 599) +++ trunk/SQLObject/sqlobject/sresults.py 2005-02-10 19:46:14 UTC (rev 600) @@ -1,6 +1,8 @@ import sqlbuilder +import dbconnection class SelectResults(object): + IterationClass = dbconnection.Iteration def __init__(self, sourceClass, clause, clauseTables=None, **ops): |
From: <sub...@co...> - 2005-02-10 18:46:12
|
Author: phd Date: 2005-02-10 18:46:08 +0000 (Thu, 10 Feb 2005) New Revision: 599 Added: home/phd/SQLObject/inheritance/sqlobject/sresults.py Modified: home/phd/SQLObject/inheritance/sqlobject/main.py Log: Merged patch from revision 598: class SelectResults has been moved to sresults.py. Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-10 18:32:10 UTC (rev 598) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-10 18:46:08 UTC (rev 599) @@ -4,6 +4,14 @@ SQLObject is a object-relational mapper. See SQLObject.html or SQLObject.txt for more. +Modified by + Daniel Savard, Xsoli Inc <sqlobject xsoli.com> 7 Feb 2004 + - Added support for simple table inheritance. + + Oleg Broytmann, SIA "ANK" <ph...@ph...> 3 Feb 2005 + - Split inheritance support into a number of separate modules and classes - + InheritableSQLObject at al. + This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the @@ -31,6 +39,7 @@ import index import classregistry import declarative +from sresults import SelectResults import sys if sys.version_info[:3] < (2, 2, 0): @@ -195,164 +204,6 @@ if warnings_level is not None and warnings_level <= level: warnings.warn(message, DeprecationWarning, stacklevel=2) -class SelectResults(object): - IterationClass = dbconnection.Iteration - - def __init__(self, sourceClass, clause, clauseTables=None, - **ops): - self.sourceClass = sourceClass - if clause is None or isinstance(clause, str) and clause == 'all': - clause = sqlbuilder.SQLTrueClause - self.clause = clause - tablesDict = sqlbuilder.tablesUsedDict(self.clause) - tablesDict[sourceClass._table] = 1 - if clauseTables: - for table in clauseTables: - tablesDict[table] = 1 - self.clauseTables = clauseTables - self.tables = tablesDict.keys() - self.ops = ops - if self.ops.get('orderBy', NoDefault) is NoDefault: - self.ops['orderBy'] = sourceClass._defaultOrder - orderBy = self.ops['orderBy'] - if isinstance(orderBy, list) or isinstance(orderBy, tuple): - orderBy = map(self._mungeOrderBy, orderBy) - else: - orderBy = self._mungeOrderBy(orderBy) - #print "OUT: %r; in: %r" % (sourceClass.sqlrepr(orderBy), sourceClass.sqlrepr(self.ops['orderBy'])) - self.ops['dbOrderBy'] = orderBy - if ops.has_key('connection') and ops['connection'] is None: - del ops['connection'] - - def _mungeOrderBy(self, orderBy): - if isinstance(orderBy, str) and orderBy.startswith('-'): - orderBy = orderBy[1:] - desc = True - else: - desc = False - if isinstance(orderBy, (str, unicode)): - if self.sourceClass._SO_columnDict.has_key(orderBy): - val = self.sourceClass._SO_columnDict[orderBy].dbName - if desc: - return '-' + val - else: - return val - else: - if desc: - return '-' + orderBy - else: - return orderBy - else: - return orderBy - - def clone(self, **newOps): - ops = self.ops.copy() - ops.update(newOps) - return self.__class__(self.sourceClass, self.clause, - self.clauseTables, **ops) - - def orderBy(self, orderBy): - return self.clone(orderBy=orderBy) - - def connection(self, conn): - return self.clone(connection=conn) - - def limit(self, limit): - return self[:limit] - - def lazyColumns(self, value): - return self.clone(lazyColumns=value) - - def reversed(self): - return self.clone(reversed=not self.ops.get('reversed', False)) - - def distinct(self): - return self.clone(distinct=True) - - def __getitem__(self, value): - if type(value) is type(slice(1)): - assert not value.step, "Slices do not support steps" - if not value.start and not value.stop: - # No need to copy, I'm immutable - return self - - # Negative indexes aren't handled (and everything we - # don't handle ourselves we just create a list to - # handle) - if (value.start and value.start < 0) \ - or (value.stop and value.stop < 0): - if value.start: - if value.stop: - return list(self)[value.start:value.stop] - return list(self)[value.start:] - return list(self)[:value.stop] - - - if value.start: - assert value.start >= 0 - start = self.ops.get('start', 0) + value.start - if value.stop is not None: - assert value.stop >= 0 - if value.stop < value.start: - # an empty result: - end = start - else: - end = value.stop + self.ops.get('start', 0) - if self.ops.get('end', None) is not None \ - and value['end'] < end: - # truncated by previous slice: - end = self.ops['end'] - else: - end = self.ops.get('end', None) - else: - start = self.ops.get('start', 0) - end = value.stop + start - if self.ops.get('end', None) is not None \ - and self.ops['end'] < end: - end = self.ops['end'] - return self.clone(start=start, end=end) - else: - if value < 0: - return list(iter(self))[value] - else: - start = self.ops.get('start', 0) + value - return list(self.clone(start=start, end=start+1))[0] - - def __iter__(self): - conn = self.ops.get('connection', self.sourceClass._connection) - return conn.iterSelect(self) - - def accumulate(self, expression): - """ Use an accumulate expression to select result - using another SQL select through current - connection. - Return the accumulate result - """ - conn = self.ops.get('connection', self.sourceClass._connection) - return conn.accumulateSelect(self, expression) - - def count(self): - """ Counting elements of current select results """ - assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" - - count = self.accumulate('COUNT(*)') - if self.ops.get('start'): - count -= self.ops['start'] - if self.ops.get('end'): - count = min(self.ops['end'] - self.ops.get('start', 0), count) - return count - - def sum(self, attribute): - """ Making the sum of a given select result attribute. - `attribute` can be a column name (like 'a_column') - or a dot-q attribute (like Table.q.aColumn) - """ - if type(attribute) == type(''): - expression = 'SUM(%s)' % attribute - else: - expression = sqlbuilder.func.SUM(attribute) - return self.accumulate(expression) - # SQLObject is the superclass for all SQLObject classes, of # course. All the deeper magic is done in MetaSQLObject, and # only lesser magic is done here. All the actual work is done Added: home/phd/SQLObject/inheritance/sqlobject/sresults.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/sresults.py 2005-02-10 18:32:10 UTC (rev 598) +++ home/phd/SQLObject/inheritance/sqlobject/sresults.py 2005-02-10 18:46:08 UTC (rev 599) @@ -0,0 +1,161 @@ +import sqlbuilder +import dbconnection + +class SelectResults(object): + IterationClass = dbconnection.Iteration + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + self.sourceClass = sourceClass + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + self.clause = clause + tablesDict = sqlbuilder.tablesUsedDict(self.clause) + tablesDict[sourceClass._table] = 1 + if clauseTables: + for table in clauseTables: + tablesDict[table] = 1 + self.clauseTables = clauseTables + self.tables = tablesDict.keys() + self.ops = ops + if self.ops.get('orderBy', sqlbuilder.NoDefault) is sqlbuilder.NoDefault: + self.ops['orderBy'] = sourceClass._defaultOrder + orderBy = self.ops['orderBy'] + if isinstance(orderBy, list) or isinstance(orderBy, tuple): + orderBy = map(self._mungeOrderBy, orderBy) + else: + orderBy = self._mungeOrderBy(orderBy) + self.ops['dbOrderBy'] = orderBy + if ops.has_key('connection') and ops['connection'] is None: + del ops['connection'] + + def _mungeOrderBy(self, orderBy): + if isinstance(orderBy, str) and orderBy.startswith('-'): + orderBy = orderBy[1:] + desc = True + else: + desc = False + if isinstance(orderBy, (str, unicode)): + if self.sourceClass._SO_columnDict.has_key(orderBy): + val = self.sourceClass._SO_columnDict[orderBy].dbName + if desc: + return '-' + val + else: + return val + else: + if desc: + return '-' + orderBy + else: + return orderBy + else: + return orderBy + + def clone(self, **newOps): + ops = self.ops.copy() + ops.update(newOps) + return self.__class__(self.sourceClass, self.clause, + self.clauseTables, **ops) + + def orderBy(self, orderBy): + return self.clone(orderBy=orderBy) + + def connection(self, conn): + return self.clone(connection=conn) + + def limit(self, limit): + return self[:limit] + + def lazyColumns(self, value): + return self.clone(lazyColumns=value) + + def reversed(self): + return self.clone(reversed=not self.ops.get('reversed', False)) + + def distinct(self): + return self.clone(distinct=True) + + def __getitem__(self, value): + if type(value) is type(slice(1)): + assert not value.step, "Slices do not support steps" + if not value.start and not value.stop: + # No need to copy, I'm immutable + return self + + # Negative indexes aren't handled (and everything we + # don't handle ourselves we just create a list to + # handle) + if (value.start and value.start < 0) \ + or (value.stop and value.stop < 0): + if value.start: + if value.stop: + return list(self)[value.start:value.stop] + return list(self)[value.start:] + return list(self)[:value.stop] + + + if value.start: + assert value.start >= 0 + start = self.ops.get('start', 0) + value.start + if value.stop is not None: + assert value.stop >= 0 + if value.stop < value.start: + # an empty result: + end = start + else: + end = value.stop + self.ops.get('start', 0) + if self.ops.get('end', None) is not None \ + and value['end'] < end: + # truncated by previous slice: + end = self.ops['end'] + else: + end = self.ops.get('end', None) + else: + start = self.ops.get('start', 0) + end = value.stop + start + if self.ops.get('end', None) is not None \ + and self.ops['end'] < end: + end = self.ops['end'] + return self.clone(start=start, end=end) + else: + if value < 0: + return list(iter(self))[value] + else: + start = self.ops.get('start', 0) + value + return list(self.clone(start=start, end=start+1))[0] + + def __iter__(self): + conn = self.ops.get('connection', self.sourceClass._connection) + return conn.iterSelect(self) + + def accumulate(self, expression): + """ Use an accumulate expression to select result + using another SQL select through current + connection. + Return the accumulate result + """ + conn = self.ops.get('connection', self.sourceClass._connection) + return conn.accumulateSelect(self, expression) + + def count(self): + """ Counting elements of current select results """ + assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" + + count = self.accumulate('COUNT(*)') + if self.ops.get('start'): + count -= self.ops['start'] + if self.ops.get('end'): + count = min(self.ops['end'] - self.ops.get('start', 0), count) + return count + + def sum(self, attribute): + """ Making the sum of a given select result attribute. + `attribute` can be a column name (like 'a_column') + or a dot-q attribute (like Table.q.aColumn) + """ + if type(attribute) == type(''): + expression = 'SUM(%s)' % attribute + else: + expression = sqlbuilder.func.SUM(attribute) + return self.accumulate(expression) + +__all__ = ['SelectResults'] |
From: <sub...@co...> - 2005-02-10 18:32:14
|
Author: phd Date: 2005-02-10 18:32:10 +0000 (Thu, 10 Feb 2005) New Revision: 598 Added: trunk/SQLObject/sqlobject/sresults.py Modified: trunk/SQLObject/sqlobject/main.py Log: class SelectResults has been moved to sresults.py. Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-10 13:17:11 UTC (rev 597) +++ trunk/SQLObject/sqlobject/main.py 2005-02-10 18:32:10 UTC (rev 598) @@ -31,6 +31,7 @@ import index import classregistry import declarative +from sresults import SelectResults import sys if sys.version_info[:3] < (2, 2, 0): @@ -1256,163 +1257,6 @@ return '_SO_val_%s' % name -class SelectResults(object): - - def __init__(self, sourceClass, clause, clauseTables=None, - **ops): - self.sourceClass = sourceClass - if clause is None or isinstance(clause, str) and clause == 'all': - clause = sqlbuilder.SQLTrueClause - self.clause = clause - tablesDict = sqlbuilder.tablesUsedDict(self.clause) - tablesDict[sourceClass._table] = 1 - if clauseTables: - for table in clauseTables: - tablesDict[table] = 1 - self.clauseTables = clauseTables - self.tables = tablesDict.keys() - self.ops = ops - if self.ops.get('orderBy', NoDefault) is NoDefault: - self.ops['orderBy'] = sourceClass._defaultOrder - orderBy = self.ops['orderBy'] - if isinstance(orderBy, list) or isinstance(orderBy, tuple): - orderBy = map(self._mungeOrderBy, orderBy) - else: - orderBy = self._mungeOrderBy(orderBy) - #print "OUT: %r; in: %r" % (sourceClass.sqlrepr(orderBy), sourceClass.sqlrepr(self.ops['orderBy'])) - self.ops['dbOrderBy'] = orderBy - if ops.has_key('connection') and ops['connection'] is None: - del ops['connection'] - - def _mungeOrderBy(self, orderBy): - if isinstance(orderBy, str) and orderBy.startswith('-'): - orderBy = orderBy[1:] - desc = True - else: - desc = False - if isinstance(orderBy, (str, unicode)): - if self.sourceClass._SO_columnDict.has_key(orderBy): - val = self.sourceClass._SO_columnDict[orderBy].dbName - if desc: - return '-' + val - else: - return val - else: - if desc: - return '-' + orderBy - else: - return orderBy - else: - return orderBy - - def clone(self, **newOps): - ops = self.ops.copy() - ops.update(newOps) - return self.__class__(self.sourceClass, self.clause, - self.clauseTables, **ops) - - def orderBy(self, orderBy): - return self.clone(orderBy=orderBy) - - def connection(self, conn): - return self.clone(connection=conn) - - def limit(self, limit): - return self[:limit] - - def lazyColumns(self, value): - return self.clone(lazyColumns=value) - - def reversed(self): - return self.clone(reversed=not self.ops.get('reversed', False)) - - def distinct(self): - return self.clone(distinct=True) - - def __getitem__(self, value): - if type(value) is type(slice(1)): - assert not value.step, "Slices do not support steps" - if not value.start and not value.stop: - # No need to copy, I'm immutable - return self - - # Negative indexes aren't handled (and everything we - # don't handle ourselves we just create a list to - # handle) - if (value.start and value.start < 0) \ - or (value.stop and value.stop < 0): - if value.start: - if value.stop: - return list(self)[value.start:value.stop] - return list(self)[value.start:] - return list(self)[:value.stop] - - - if value.start: - assert value.start >= 0 - start = self.ops.get('start', 0) + value.start - if value.stop is not None: - assert value.stop >= 0 - if value.stop < value.start: - # an empty result: - end = start - else: - end = value.stop + self.ops.get('start', 0) - if self.ops.get('end', None) is not None \ - and value['end'] < end: - # truncated by previous slice: - end = self.ops['end'] - else: - end = self.ops.get('end', None) - else: - start = self.ops.get('start', 0) - end = value.stop + start - if self.ops.get('end', None) is not None \ - and self.ops['end'] < end: - end = self.ops['end'] - return self.clone(start=start, end=end) - else: - if value < 0: - return list(iter(self))[value] - else: - start = self.ops.get('start', 0) + value - return list(self.clone(start=start, end=start+1))[0] - - def __iter__(self): - conn = self.ops.get('connection', self.sourceClass._connection) - return conn.iterSelect(self) - - def accumulate(self, expression): - """ Use an accumulate expression to select result - using another SQL select through current - connection. - Return the accumulate result - """ - conn = self.ops.get('connection', self.sourceClass._connection) - return conn.accumulateSelect(self, expression) - - def count(self): - """ Counting elements of current select results """ - assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" - - count = self.accumulate('COUNT(*)') - if self.ops.get('start'): - count -= self.ops['start'] - if self.ops.get('end'): - count = min(self.ops['end'] - self.ops.get('start', 0), count) - return count - - def sum(self, attribute): - """ Making the sum of a given select result attribute. - `attribute` can be a column name (like 'a_column') - or a dot-q attribute (like Table.q.aColumn) - """ - if type(attribute) == type(''): - expression = 'SUM(%s)' % attribute - else: - expression = sqlbuilder.func.SUM(attribute) - return self.accumulate(expression) - class SQLObjectState(object): def __init__(self, soObject): Added: trunk/SQLObject/sqlobject/sresults.py =================================================================== --- trunk/SQLObject/sqlobject/sresults.py 2005-02-10 13:17:11 UTC (rev 597) +++ trunk/SQLObject/sqlobject/sresults.py 2005-02-10 18:32:10 UTC (rev 598) @@ -0,0 +1,159 @@ +import sqlbuilder + +class SelectResults(object): + + def __init__(self, sourceClass, clause, clauseTables=None, + **ops): + self.sourceClass = sourceClass + if clause is None or isinstance(clause, str) and clause == 'all': + clause = sqlbuilder.SQLTrueClause + self.clause = clause + tablesDict = sqlbuilder.tablesUsedDict(self.clause) + tablesDict[sourceClass._table] = 1 + if clauseTables: + for table in clauseTables: + tablesDict[table] = 1 + self.clauseTables = clauseTables + self.tables = tablesDict.keys() + self.ops = ops + if self.ops.get('orderBy', sqlbuilder.NoDefault) is sqlbuilder.NoDefault: + self.ops['orderBy'] = sourceClass._defaultOrder + orderBy = self.ops['orderBy'] + if isinstance(orderBy, list) or isinstance(orderBy, tuple): + orderBy = map(self._mungeOrderBy, orderBy) + else: + orderBy = self._mungeOrderBy(orderBy) + self.ops['dbOrderBy'] = orderBy + if ops.has_key('connection') and ops['connection'] is None: + del ops['connection'] + + def _mungeOrderBy(self, orderBy): + if isinstance(orderBy, str) and orderBy.startswith('-'): + orderBy = orderBy[1:] + desc = True + else: + desc = False + if isinstance(orderBy, (str, unicode)): + if self.sourceClass._SO_columnDict.has_key(orderBy): + val = self.sourceClass._SO_columnDict[orderBy].dbName + if desc: + return '-' + val + else: + return val + else: + if desc: + return '-' + orderBy + else: + return orderBy + else: + return orderBy + + def clone(self, **newOps): + ops = self.ops.copy() + ops.update(newOps) + return self.__class__(self.sourceClass, self.clause, + self.clauseTables, **ops) + + def orderBy(self, orderBy): + return self.clone(orderBy=orderBy) + + def connection(self, conn): + return self.clone(connection=conn) + + def limit(self, limit): + return self[:limit] + + def lazyColumns(self, value): + return self.clone(lazyColumns=value) + + def reversed(self): + return self.clone(reversed=not self.ops.get('reversed', False)) + + def distinct(self): + return self.clone(distinct=True) + + def __getitem__(self, value): + if type(value) is type(slice(1)): + assert not value.step, "Slices do not support steps" + if not value.start and not value.stop: + # No need to copy, I'm immutable + return self + + # Negative indexes aren't handled (and everything we + # don't handle ourselves we just create a list to + # handle) + if (value.start and value.start < 0) \ + or (value.stop and value.stop < 0): + if value.start: + if value.stop: + return list(self)[value.start:value.stop] + return list(self)[value.start:] + return list(self)[:value.stop] + + + if value.start: + assert value.start >= 0 + start = self.ops.get('start', 0) + value.start + if value.stop is not None: + assert value.stop >= 0 + if value.stop < value.start: + # an empty result: + end = start + else: + end = value.stop + self.ops.get('start', 0) + if self.ops.get('end', None) is not None \ + and value['end'] < end: + # truncated by previous slice: + end = self.ops['end'] + else: + end = self.ops.get('end', None) + else: + start = self.ops.get('start', 0) + end = value.stop + start + if self.ops.get('end', None) is not None \ + and self.ops['end'] < end: + end = self.ops['end'] + return self.clone(start=start, end=end) + else: + if value < 0: + return list(iter(self))[value] + else: + start = self.ops.get('start', 0) + value + return list(self.clone(start=start, end=start+1))[0] + + def __iter__(self): + conn = self.ops.get('connection', self.sourceClass._connection) + return conn.iterSelect(self) + + def accumulate(self, expression): + """ Use an accumulate expression to select result + using another SQL select through current + connection. + Return the accumulate result + """ + conn = self.ops.get('connection', self.sourceClass._connection) + return conn.accumulateSelect(self, expression) + + def count(self): + """ Counting elements of current select results """ + assert not self.ops.get('distinct'), "It is not currently supported to count distinct objects" + + count = self.accumulate('COUNT(*)') + if self.ops.get('start'): + count -= self.ops['start'] + if self.ops.get('end'): + count = min(self.ops['end'] - self.ops.get('start', 0), count) + return count + + def sum(self, attribute): + """ Making the sum of a given select result attribute. + `attribute` can be a column name (like 'a_column') + or a dot-q attribute (like Table.q.aColumn) + """ + if type(attribute) == type(''): + expression = 'SUM(%s)' % attribute + else: + expression = sqlbuilder.func.SUM(attribute) + return self.accumulate(expression) + +__all__ = ['SelectResults'] |
From: <sub...@co...> - 2005-02-10 13:17:17
|
Author: phd Date: 2005-02-10 13:17:11 +0000 (Thu, 10 Feb 2005) New Revision: 597 Modified: home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/test_inheritance.py home/phd/SQLObject/inheritance/sqlobject/main.py Log: Fixed a bug with inhereted .q magic attribute. Added more inheritance tests. Modified: home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/test_inheritance.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/test_inheritance.py 2005-02-10 12:18:05 UTC (rev 596) +++ home/phd/SQLObject/inheritance/sqlobject/inheritance/tests/test_inheritance.py 2005-02-10 13:17:11 UTC (rev 597) @@ -16,14 +16,17 @@ _inheritable = 0 # If I don't want this class to be inherited position = StringCol() - -def test_inheritance(): +def setup(): setupClass(Person) setupClass(Employee) Employee(firstName='Ian', lastName='Bicking', position='Project leader') Person(firstName='Daniel', lastName='Savard') + +def test_inheritance(): + setup() + persons = Person.select() # all for person in persons: assert isinstance(person, Person) @@ -32,3 +35,16 @@ else: assert hasattr(person, "childName") assert not person.childName + + +def test_inheritance_select(): + setup() + + persons = Person.select(Person.q.firstName <> None) + assert persons.count() == 2 + + employees = Employee.select(Employee.q.firstName <> None) + assert employees.count() == 1 + + employees = Employee.select(Employee.q.position <> None) + assert employees.count() == 1 Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-10 12:18:05 UTC (rev 596) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-10 13:17:11 UTC (rev 597) @@ -577,15 +577,6 @@ cls.sqlmeta.idName = cls._idName del cls._idName - #DSM: If we are a child, get the q magic from the parent - currentClass = cls - while currentClass._parentClass: - currentClass = currentClass._parentClass - for column in currentClass._columns: - if type(column) == col.ForeignKey: continue - setattr(cls.q, column.name, - getattr(currentClass.q, column.name)) - # We have to check if there are columns in the inherited # _columns where the attribute has been set to None in this # class. If so, then we need to remove that column from @@ -626,6 +617,15 @@ if not is_base: cls.q = sqlbuilder.SQLObjectTable(cls) + #DSM: If we are a child, get the q magic from the parent + currentClass = cls + while currentClass._parentClass: + currentClass = currentClass._parentClass + for column in currentClass._columns: + if type(column) == col.ForeignKey: continue + setattr(cls.q, column.name, + getattr(currentClass.q, column.name)) + classregistry.registry(cls._registry).addClass(cls) _style = _sqlmeta_attr('style') |
From: <sub...@co...> - 2005-02-10 12:18:11
|
Author: phd Date: 2005-02-10 12:18:05 +0000 (Thu, 10 Feb 2005) New Revision: 596 Added: home/phd/SQLObject/inheritance/sqlobject/tests/test_basic_old.py home/phd/SQLObject/inheritance/sqlobject/tests/test_sorting_old.py home/phd/SQLObject/inheritance/sqlobject/tests/test_stringid_old.py home/phd/SQLObject/inheritance/sqlobject/tests/test_style_old.py Modified: home/phd/SQLObject/inheritance/sqlobject/classregistry.py home/phd/SQLObject/inheritance/sqlobject/main.py home/phd/SQLObject/inheritance/sqlobject/tests/dbtest.py home/phd/SQLObject/inheritance/sqlobject/tests/test_basic.py home/phd/SQLObject/inheritance/sqlobject/tests/test_sorting.py home/phd/SQLObject/inheritance/sqlobject/tests/test_stringid.py home/phd/SQLObject/inheritance/sqlobject/tests/test_style.py Log: Merged patches from revisions 591:595 from the trunk Modified: home/phd/SQLObject/inheritance/sqlobject/classregistry.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/classregistry.py 2005-02-10 04:53:14 UTC (rev 595) +++ home/phd/SQLObject/inheritance/sqlobject/classregistry.py 2005-02-10 12:18:05 UTC (rev 596) @@ -74,9 +74,11 @@ "%r, from the module %s in %s)" % (cls.__name__, other, other.__module__, - sys.modules[other.__module__].__file__, + getattr(sys.modules.get(other.__module__), + '__file__', '(unknown)'), cls, cls.__module__, - sys.modules[cls.__module__].__file__)) + getattr(sys.modules.get(cls.__module__), + '__file__', '(unknown)'))) self.classes[cls.__name__] = cls if self.callbacks.has_key(cls.__name__): for callback, args, kw in self.callbacks[cls.__name__]: Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-10 04:53:14 UTC (rev 595) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-02-10 12:18:05 UTC (rev 596) @@ -30,7 +30,6 @@ import joins import index import classregistry -findClass = classregistry.findClass # for those who imported findClass from sqlobject.main import declarative import sys @@ -110,8 +109,6 @@ delFunc(obj, var) break -#phd: findClass has been moved to classregistry to avoid circular import - def findDependencies(name, registry=None): depends = [] for klass in classregistry.registry(registry).allClasses(): @@ -161,9 +158,6 @@ def setClass(cls, soClass): cls.soClass = soClass - setClass = classmethod(setClass) - - def finishClass(cls): if not cls.style: if cls.soClass._connection and cls.soClass._connection.style: cls.style = cls.soClass._connection.style @@ -173,7 +167,7 @@ cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__) if cls.idName is None: cls.idName = cls.style.idForTable(cls.table) - finishClass = classmethod(finishClass) + setClass = classmethod(setClass) class _sqlmeta_attr(object): @@ -182,17 +176,24 @@ def __get__(self, obj, type=None): return getattr((type or obj).sqlmeta, self.name) - + # @@: This should become a public interface or documented or # something. Turning it on gives earlier warning about things # that will be deprecated (having this off we won't flood people # with warnings right away). -strict_warnings = False +warnings_level = 1 +exception_level = None +# Current levels: +# 1) Actively deprecated in version after 0.6.1 (0.7?); removed after +# 2) Deprecated after 1 (0.8?) +# 3) Deprecated after 2 (0.9?) -def deprecated(message): - if strict_warnings: - warnings.warn(message, DeprecationWarning, stacklevel=1) +def deprecated(message, level=1): + if exception_level is not None and exception_level <= level: + raise NotImplementedError(message) + if warnings_level is not None and warnings_level <= level: + warnings.warn(message, DeprecationWarning, stacklevel=2) class SelectResults(object): IterationClass = dbconnection.Iteration @@ -437,8 +438,6 @@ #cls.sqlmeta = cls.sqlmeta.clone() cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {}) cls.sqlmeta.setClass(cls) - cls.sqlmeta.finishClass() - implicitColumns = [] implicitJoins = [] @@ -462,7 +461,7 @@ if (new_attrs.has_key('_table') and not is_base): deprecated("'_table' is deprecated; please set the 'table' " - "attribute in sqlmeta instead") + "attribute in sqlmeta instead", level=2) cls.sqlmeta.table = cls._table del cls._table @@ -551,7 +550,7 @@ if (new_attrs.has_key('_style') and not is_base): deprecated("'_style' is deprecated; please set the 'style' " - "attribute in sqlmeta instead") + "attribute in sqlmeta instead", level=2) cls.sqlmeta.style = cls._style del cls._style @@ -574,7 +573,7 @@ if (new_attrs.has_key('_idName') and not is_base): deprecated("'_idName' is deprecated; please set the 'idName' " - "attribute in sqlmeta instead") + "attribute in sqlmeta instead", level=2) cls.sqlmeta.idName = cls._idName del cls._idName @@ -1504,6 +1503,6 @@ else: return obj -__all__ = ['NoDefault', 'SQLObject', +__all__ = ['NoDefault', 'SQLObject', 'sqlmeta', 'getID', 'getObject', 'SQLObjectNotFound'] Modified: home/phd/SQLObject/inheritance/sqlobject/tests/dbtest.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/dbtest.py 2005-02-10 04:53:14 UTC (rev 595) +++ home/phd/SQLObject/inheritance/sqlobject/tests/dbtest.py 2005-02-10 12:18:05 UTC (rev 596) @@ -229,5 +229,27 @@ # To avoid name clashes: _inserts = inserts +def deprecated_module(): + sqlobject.main.warnings_level = None + sqlobject.main.exception_level = None + +def setup_module(mod): + # modules with '_old' test backward compatible methods, so they + # don't get warnings or errors. + mod_name = str(mod.__name__) + if mod_name.endswith('/py'): + mod_name = mod_name[:-3] + if mod_name.endswith('_old'): + sqlobject.main.warnings_level = None + sqlobject.main.exception_level = None + else: + sqlobject.main.warnings_level = None + sqlobject.main.exception_level = 0 + +def teardown_module(mod=None): + sqlobject.main.warnings_level = None + sqlobject.main.exception_level = 0 + __all__ = ['getConnection', 'setupClass', 'Dummy', 'raises', - 'd', 'inserts', 'supports'] + 'd', 'inserts', 'supports', 'deprecated_module', + 'setup_module', 'teardown_module'] Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_basic.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_basic.py 2005-02-10 04:53:14 UTC (rev 595) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_basic.py 2005-02-10 12:18:05 UTC (rev 596) @@ -5,9 +5,7 @@ name = StringCol(length=50, dbName='name_col') _cacheValues = False - _columns = [ - StringCol('passwd', length=10), - ] + passwd = StringCol(length=10) def _set_passwd(self, passwd): self._SO_set_passwd(passwd.encode('rot13')) Copied: home/phd/SQLObject/inheritance/sqlobject/tests/test_basic_old.py (from rev 595, trunk/SQLObject/sqlobject/tests/test_basic_old.py) Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_sorting.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_sorting.py 2005-02-10 04:53:14 UTC (rev 595) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_sorting.py 2005-02-10 12:18:05 UTC (rev 596) @@ -3,7 +3,8 @@ class Names(SQLObject): - _table = 'names_table' + class sqlmeta(sqlmeta): + table = 'names_table' firstName = StringCol(length=30) lastName = StringCol(length=30) Copied: home/phd/SQLObject/inheritance/sqlobject/tests/test_sorting_old.py (from rev 595, trunk/SQLObject/sqlobject/tests/test_sorting_old.py) Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_stringid.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_stringid.py 2005-02-10 04:53:14 UTC (rev 595) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_stringid.py 2005-02-10 12:18:05 UTC (rev 596) @@ -7,7 +7,8 @@ class SOStringID(SQLObject): - _table = 'so_string_id' + class sqlmeta(sqlmeta): + table = 'so_string_id' _idType = str val = StringCol(alternateID=True) Copied: home/phd/SQLObject/inheritance/sqlobject/tests/test_stringid_old.py (from rev 595, trunk/SQLObject/sqlobject/tests/test_stringid_old.py) Modified: home/phd/SQLObject/inheritance/sqlobject/tests/test_style.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/tests/test_style.py 2005-02-10 04:53:14 UTC (rev 595) +++ home/phd/SQLObject/inheritance/sqlobject/tests/test_style.py 2005-02-10 12:18:05 UTC (rev 596) @@ -12,11 +12,13 @@ class SOStyleTest1(SQLObject): a = StringCol() st2 = ForeignKey('SOStyleTest2') - _style = AnotherStyle() + class sqlmeta(sqlmeta): + style = AnotherStyle() class SOStyleTest2(SQLObject): b = StringCol() - _style = AnotherStyle() + class sqlmeta(sqlmeta): + style = AnotherStyle() def test_style(): setupClass([SOStyleTest2, SOStyleTest1]) Copied: home/phd/SQLObject/inheritance/sqlobject/tests/test_style_old.py (from rev 595, trunk/SQLObject/sqlobject/tests/test_style_old.py) |
From: <sub...@co...> - 2005-02-10 04:53:21
|
Author: ianb Date: 2005-02-10 04:53:14 +0000 (Thu, 10 Feb 2005) New Revision: 595 Modified: trunk/SQLObject/sqlobject/main.py Log: Removed sqlmeta.finishClass method (merged with setClass) Modified: trunk/SQLObject/sqlobject/main.py =================================================================== --- trunk/SQLObject/sqlobject/main.py 2005-02-10 04:50:41 UTC (rev 594) +++ trunk/SQLObject/sqlobject/main.py 2005-02-10 04:53:14 UTC (rev 595) @@ -162,9 +162,6 @@ def setClass(cls, soClass): cls.soClass = soClass - setClass = classmethod(setClass) - - def finishClass(cls): if not cls.style: if cls.soClass._connection and cls.soClass._connection.style: cls.style = cls.soClass._connection.style @@ -174,7 +171,7 @@ cls.table = cls.style.pythonClassToDBTable(cls.soClass.__name__) if cls.idName is None: cls.idName = cls.style.idForTable(cls.table) - finishClass = classmethod(finishClass) + setClass = classmethod(setClass) class _sqlmeta_attr(object): @@ -276,8 +273,6 @@ #cls.sqlmeta = cls.sqlmeta.clone() cls.sqlmeta = type('sqlmeta', (cls.sqlmeta,), {}) cls.sqlmeta.setClass(cls) - cls.sqlmeta.finishClass() - implicitColumns = [] implicitJoins = [] |
From: <sub...@co...> - 2005-02-10 04:50:44
|
Author: ianb Date: 2005-02-10 04:50:41 +0000 (Thu, 10 Feb 2005) New Revision: 594 Added: trunk/SQLObject/sqlobject/tests/test_basic_old.py trunk/SQLObject/sqlobject/tests/test_sorting_old.py trunk/SQLObject/sqlobject/tests/test_stringid_old.py trunk/SQLObject/sqlobject/tests/test_style_old.py Modified: trunk/SQLObject/sqlobject/tests/dbtest.py trunk/SQLObject/sqlobject/tests/test_basic.py trunk/SQLObject/sqlobject/tests/test_sorting.py trunk/SQLObject/sqlobject/tests/test_stringid.py trunk/SQLObject/sqlobject/tests/test_style.py Log: Updated tests to use new style when applicable; added modules that test the backward compatible style when that occurred Modified: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-10 04:50:00 UTC (rev 593) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-10 04:50:41 UTC (rev 594) @@ -229,5 +229,27 @@ # To avoid name clashes: _inserts = inserts +def deprecated_module(): + sqlobject.main.warnings_level = None + sqlobject.main.exception_level = None + +def setup_module(mod): + # modules with '_old' test backward compatible methods, so they + # don't get warnings or errors. + mod_name = str(mod.__name__) + if mod_name.endswith('/py'): + mod_name = mod_name[:-3] + if mod_name.endswith('_old'): + sqlobject.main.warnings_level = None + sqlobject.main.exception_level = None + else: + sqlobject.main.warnings_level = None + sqlobject.main.exception_level = 0 + +def teardown_module(mod=None): + sqlobject.main.warnings_level = None + sqlobject.main.exception_level = 0 + __all__ = ['getConnection', 'setupClass', 'Dummy', 'raises', - 'd', 'inserts', 'supports'] + 'd', 'inserts', 'supports', 'deprecated_module', + 'setup_module', 'teardown_module'] Modified: trunk/SQLObject/sqlobject/tests/test_basic.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-10 04:50:00 UTC (rev 593) +++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-10 04:50:41 UTC (rev 594) @@ -5,9 +5,7 @@ name = StringCol(length=50, dbName='name_col') _cacheValues = False - _columns = [ - StringCol('passwd', length=10), - ] + passwd = StringCol(length=10) def _set_passwd(self, passwd): self._SO_set_passwd(passwd.encode('rot13')) Copied: trunk/SQLObject/sqlobject/tests/test_basic_old.py (from rev 580, trunk/SQLObject/sqlobject/tests/test_basic.py) =================================================================== --- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-09 04:50:46 UTC (rev 580) +++ trunk/SQLObject/sqlobject/tests/test_basic_old.py 2005-02-10 04:50:41 UTC (rev 594) @@ -0,0 +1,50 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +deprecated_module() + +class OldTestSO1(SQLObject): + + name = StringCol(length=50, dbName='name_col') + _cacheValues = False + _columns = [ + StringCol('passwd', length=10), + ] + + def _set_passwd(self, passwd): + self._SO_set_passwd(passwd.encode('rot13')) + +def setupGetters(cls): + setupClass(cls) + inserts(cls, [('bob', 'god'), ('sally', 'sordid'), + ('dave', 'dremel'), ('fred', 'forgo')], + 'name passwd') + +def test_case1(): + setupGetters(OldTestSO1) + bob = OldTestSO1.selectBy(name='bob')[0] + assert bob.name == 'bob' + assert bob.passwd == 'god'.encode('rot13') + +def test_newline(): + setupGetters(OldTestSO1) + bob = OldTestSO1.selectBy(name='bob')[0] + testString = 'hey\nyou\\can\'t you see me?\t' + bob.name = testString + bob.expire() + assert bob.name == testString + +def test_count(): + setupGetters(OldTestSO1) + assert OldTestSO1.selectBy(name='bob').count() == 1 + assert OldTestSO1.select(OldTestSO1.q.name == 'bob').count() == 1 + assert OldTestSO1.select().count() == len(list(OldTestSO1.select())) + +def test_getset(): + setupGetters(OldTestSO1) + bob = OldTestSO1.selectBy(name='bob')[0] + assert bob.name == 'bob' + bob.name = 'joe' + assert bob.name == 'joe' + +teardown_module() Modified: trunk/SQLObject/sqlobject/tests/test_sorting.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_sorting.py 2005-02-10 04:50:00 UTC (rev 593) +++ trunk/SQLObject/sqlobject/tests/test_sorting.py 2005-02-10 04:50:41 UTC (rev 594) @@ -3,7 +3,8 @@ class Names(SQLObject): - _table = 'names_table' + class sqlmeta(sqlmeta): + table = 'names_table' firstName = StringCol(length=30) lastName = StringCol(length=30) Added: trunk/SQLObject/sqlobject/tests/test_sorting_old.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_sorting_old.py 2005-02-10 04:50:00 UTC (rev 593) +++ trunk/SQLObject/sqlobject/tests/test_sorting_old.py 2005-02-10 04:50:41 UTC (rev 594) @@ -0,0 +1,36 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +deprecated_module() + +class OldNames(SQLObject): + + _table = 'names_table' + + firstName = StringCol(length=30) + lastName = StringCol(length=30) + + _defaultOrder = ['lastName', 'firstName'] + +def setupNames(): + setupClass(OldNames) + inserts(OldNames, [('aj', 'baker'), ('joe', 'robbins'), + ('tim', 'jackson'), ('joe', 'baker'), + ('zoe', 'robbins')], + schema='firstName lastName') + +def nameList(names): + result = [] + for name in names: + result.append('%s %s' % (name.firstName, name.lastName)) + return result + +def firstList(names): + return [n.firstName for n in names] + +def test_defaultOrder(): + setupNames() + assert nameList(OldNames.select()) == \ + ['aj baker', 'joe baker', + 'tim jackson', 'joe robbins', + 'zoe robbins'] Modified: trunk/SQLObject/sqlobject/tests/test_stringid.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_stringid.py 2005-02-10 04:50:00 UTC (rev 593) +++ trunk/SQLObject/sqlobject/tests/test_stringid.py 2005-02-10 04:50:41 UTC (rev 594) @@ -7,7 +7,8 @@ class SOStringID(SQLObject): - _table = 'so_string_id' + class sqlmeta(sqlmeta): + table = 'so_string_id' _idType = str val = StringCol(alternateID=True) Added: trunk/SQLObject/sqlobject/tests/test_stringid_old.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_stringid_old.py 2005-02-10 04:50:00 UTC (rev 593) +++ trunk/SQLObject/sqlobject/tests/test_stringid_old.py 2005-02-10 04:50:41 UTC (rev 594) @@ -0,0 +1,65 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +deprecated_module() + +######################################## +## String ID test +######################################## + +class OldSOStringID(SQLObject): + + _table = 'so_string_id' + _idType = str + val = StringCol(alternateID=True) + + mysqlCreate = """ + CREATE TABLE IF NOT EXISTS so_string_id ( + id VARCHAR(50) PRIMARY KEY, + val TEXT + ) + """ + + postgresCreate = """ + CREATE TABLE so_string_id ( + id VARCHAR(50) PRIMARY KEY, + val TEXT + ) + """ + + sybaseCreate = """ + CREATE TABLE so_string_id ( + id VARCHAR(50) UNIQUE, + val VARCHAR(50) NULL + ) + """ + + firebirdCreate = """ + CREATE TABLE so_string_id ( + id VARCHAR(50) NOT NULL PRIMARY KEY, + val BLOB SUB_TYPE TEXT + ) + """ + + sqliteCreate = postgresCreate + + mysqlDrop = """ + DROP TABLE IF EXISTS so_string_id + """ + + postgresDrop = """ + DROP TABLE so_string_id + """ + + sqliteDrop = postgresDrop + firebirdDrop = postgresDrop + + +def test_stringID(): + setupClass(OldSOStringID) + t = OldSOStringID(id='hey', val='whatever') + t2 = OldSOStringID.byVal('whatever') + assert t == t2 + t3 = OldSOStringID(id='you', val='nowhere') + t4 = OldSOStringID.get('you') + assert t3 == t4 Modified: trunk/SQLObject/sqlobject/tests/test_style.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-10 04:50:00 UTC (rev 593) +++ trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-10 04:50:41 UTC (rev 594) @@ -12,11 +12,13 @@ class SOStyleTest1(SQLObject): a = StringCol() st2 = ForeignKey('SOStyleTest2') - _style = AnotherStyle() + class sqlmeta(sqlmeta): + style = AnotherStyle() class SOStyleTest2(SQLObject): b = StringCol() - _style = AnotherStyle() + class sqlmeta(sqlmeta): + style = AnotherStyle() def test_style(): setupClass([SOStyleTest2, SOStyleTest1]) Added: trunk/SQLObject/sqlobject/tests/test_style_old.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_style_old.py 2005-02-10 04:50:00 UTC (rev 593) +++ trunk/SQLObject/sqlobject/tests/test_style_old.py 2005-02-10 04:50:41 UTC (rev 594) @@ -0,0 +1,31 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject import styles + +deprecated_module() + +class AnotherStyle(styles.MixedCaseUnderscoreStyle): + def pythonAttrToDBColumn(self, attr): + if attr.lower().endswith('id'): + return 'id'+styles.MixedCaseUnderscoreStyle.pythonAttrToDBColumn(self, attr[:-2]) + else: + return styles.MixedCaseUnderscoreStyle.pythonAttrToDBColumn(self, attr) + +class OldSOStyleTest1(SQLObject): + a = StringCol() + st2 = ForeignKey('OldSOStyleTest2') + _style = AnotherStyle() + +class OldSOStyleTest2(SQLObject): + b = StringCol() + _style = AnotherStyle() + +def test_style(): + setupClass([OldSOStyleTest2, OldSOStyleTest1]) + st1 = OldSOStyleTest1(a='something', st2=None) + st2 = OldSOStyleTest2(b='whatever') + st1.st2 = st2 + assert st1._SO_columnDict['st2ID'].dbName == 'idst2' + assert st1.st2 == st2 + +teardown_module() |