sqlobject-cvs Mailing List for SQLObject (Page 171)
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-03 12:13:33
|
Author: phd Date: 2005-02-03 12:13:26 +0000 (Thu, 03 Feb 2005) New Revision: 568 Modified: home/phd/SQLObject/inheritance/sqlobject/postgres/pgconnection.py Log: Merged binary support from the trunk. Modified: home/phd/SQLObject/inheritance/sqlobject/postgres/pgconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/postgres/pgconnection.py 2005-02-03 12:06:16 UTC (rev 567) +++ home/phd/SQLObject/inheritance/sqlobject/postgres/pgconnection.py 2005-02-03 12:13:26 UTC (rev 568) @@ -29,6 +29,10 @@ registerConverter(type(psycopg.Binary('')), PsycoBinaryConverter) + self.user = user + self.host = host + self.db = db + self.password = passwd if dsn is None: dsn = [] if db: |
From: <sub...@co...> - 2005-02-03 12:06:35
|
Author: phd Date: 2005-02-03 12:06:16 +0000 (Thu, 03 Feb 2005) New Revision: 567 Removed: home/phd/SQLObject/inheritance/docs/Plan06.txt Modified: home/phd/SQLObject/inheritance/docs/News.txt home/phd/SQLObject/inheritance/sqlobject/col.py home/phd/SQLObject/inheritance/sqlobject/dbconnection.py Log: Merged changes from the trunk, version 0.6.1. Modified: home/phd/SQLObject/inheritance/docs/News.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/News.txt 2005-02-01 08:01:49 UTC (rev 566) +++ home/phd/SQLObject/inheritance/docs/News.txt 2005-02-03 12:06:16 UTC (rev 567) @@ -7,8 +7,8 @@ .. _start: -SVN trunk -========= +SQLObject 0.6.1 +=============== Interface Changes ----------------- @@ -75,7 +75,7 @@ rows (like class instantiation used to do). * We're now using a Subversion repository instead of CVS. It is - located at svn://colorstudy.com/trunk/SQLObject + located at http://svn.colorstudy.com/trunk/SQLObject * If you pass ``forceDBName=True`` to the ``*Col`` constructors, then your column name doesn't have to be restricted to a-z, 0-9, and _. Deleted: home/phd/SQLObject/inheritance/docs/Plan06.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/Plan06.txt 2005-02-01 08:01:49 UTC (rev 566) +++ home/phd/SQLObject/inheritance/docs/Plan06.txt 2005-02-03 12:06:16 UTC (rev 567) @@ -1,306 +0,0 @@ -SQLObject 0.6 -============= - -*A tentative plan, 20 Jan 2004* - -Introduction ------------- - -During vacation I thought about some changes that I might like to make -to SQLObject. Several of these change the API, but not too -drastically, and I think they change the API for the better. And we'd -not at 1.0 yet, changes are still allowed! Here's my ideas... - -Editing Context ---------------- - -Taken from Modeling, the "editing context" is essentially a -transaction, though it also encompasses some other features. -Typically it is used to distinguish between separate contexts in a -multi-threaded program. - -This is intended to separate several distinct concepts: - -* The database backend (MySQL, PostgreSQL, etc), coupled with the - driver (MySQLdb, psycopg, etc). (Should the driver be part of the - connection parameters?) -* The connection parameters. Typically these are the server host, - username, and password, but they could also be a filename or other - path. Perhaps this could be represented with a URI, ala PEAK, but - I also dislike taking structured data and destructuring it (i.e., - packing it into a string). OTOH, URLs are structured, even if they - require some parsing. Serialization of URLs is free and highly - transparent. Python syntax is well structured and - *programmatically* considerably more transparent (in a robust - fashion), but also programmatically fairly read-only (because it is - embedded in the structure of Python source code). We can also have - both. -* The database transactional context. -* The application transactional context (preferably these two would - be seemless, but they still represent somewhat distinct entities, - and a portability layer might be nice). The application's - transactional context may include other transactions -- e.g., - multiple databases, a ZODB transaction, etc. -* The cache policy. There are many different kinds of caches - potentially involved, include write batching, and per-object and - per-table caches, connection pooling, and so on. -* Classes, which on the database side are typically tables. (This - proposal does not attempt to de-couple classes and tables) - -Example:: - - from SQLObject import EditingContext - ec = EditingContext() - # every editing context automatically picks up all the SQLObject - # classes, all magic like. - person = ec.Person.get(1) # by ID - ec2 = EditingContext() # separate transaction - person2 = ec.Person.get(1) - assert person is not person2 - assert person.id == person2.id - assert person.fname == 'Guy' - person.fname = 'Gerald' - assert person2.fname == 'Guy' - ec.commit() # SQL is not sent to server - assert person2.fname == 'Guy' # Doesn't see changes - person2.fname = 'Norm' - # raises exception if locking is turned on; overwrites if locking - # is not turned on. (Locking enabled on a per-class level) - -I'm not at all sure about that example. Mostly the confusing parts -relate to locking and when the database lookup occurs (and how late a -conflict exception may be raised). - -Somewhere in here, process-level transactions might fit in. That is, -even on a backend that doesn't support transactions, we can still -delay SQL statements until a commit/rollback is performed. In turn, -we can create temporary "memory" objects, which is any object which -hasn't been committed to the database in any way. To do this we'll -need sequences -- to preallocate IDs -- which MySQL and SQLite don't -really provide :( - -Nested transactions...? Maybe they'd fall out of this fairly easily, -especially if we define a global context, with global caches etc., -then further levels of context will come for free. - -We still need to think about an auto-commit mode. Maybe the global -context would be auto-commit. - -Caching -------- - -Really doing transactions right means making caching significantly -more complex. If the cache is purely transaction-specific, then we'll -really be limiting the effectiveness of the cache. With that in mind, -a copy-on-write style of object is really called for -- when you fetch -an object in a transaction, you can use the globally cached instance -until you write to the object. - -Really this isn't copy-on-write, it's more like a proxy object. Until -the object is changed, it can delegate all its columns to its global -object for which it is a proxy. Of course, traversal via foreign keys -or joins must also return proxied objects. As the object is changed --- perhaps on a column-by-column basis, or as a whole on the first -change -- the object takes on the personality of a full SQLObject -instance. - -When the transaction is committed, this transactional object copies -itself to the global object, and becomes a full proxy. These -transactional caches themselves should be pooled -- so that when -another transaction comes along you have a potentially useful set of -proxy objects already created for you. This is a common use case for -web applications, which have lots of short transactions, which are -often very repetitive. - -In addition to this, there should be more cache control. This means -explicit ways to control things like: - -1. Caching of instances: - - * Application/process-global definition. - * Database-level definition. - * Transaction/EditingContext-level definition. - * Class-level definition. - -2. Caching of columns: - - * Class-level. - -3. Cache sweep frequency: - - * Application/process-global. - * Database-level. - * Class-level. - * Doesn't need to be as complete as 1; maybe on the class level you - could only indicate that a certain class should not be sweeped. - * Sweep during a fetch (e.g., every 100 fetches), by time or fetch - frequency, or sweep with an explicit call (e.g., to do sweeps in - a separate thread). - -4. Cache sweep policy: - - * Maximum age. - * Least-recently-used (actually, least-recently-fetched). - * Random (the current policy). - * Multi-level (randomly move objects to a lower-priority cache, - raise level when the object is fetched again). - * Target cache size (keep trimming until the cache is small - enough). - * Simple policy (if enough objects qualify, cache can be of any - size). - * Percentage culling (e.g., kill 33% of objects for each sweep; - this is the current policy). - -5. Batching of updates (whether updates should immediately go to the - database, or whether it would be batched until a commit or other - signal). - -6. Natural expiring of objects. Even if an object must persist - because there are still references, we could expire it so that - future accesses re-query the database. To avoid stale data. - -Expose some methods of the cache, like getting all objects currently -in memory. These would probably be exposed on a class level, e.g., -all the Addresses currently in memory via -``Address.cache.current()`` or something. What about when there's a -cached instance in the parent context, but not in the present -transaction? - -Columns as Descriptors ----------------------- - -Each column will become a descriptor. That is, ``Col`` and subclasses -will return an object with ``__get__`` and ``__set__`` methods. The -metaclass will not itself generate methods. - -A metaclass will still be used so that the descriptor can be tied to -its name, e.g., that with ``fname = StringCol()``, the resultant -descriptor will know that it is bound to ``fname``. - -By using descriptors, introspection should become a bit easier -- or -at least more uniform with respect to other new-style classes. -Various class-wide indexes of columns will still be necessary, but -these should be able to remain mostly private. - -To customize getters or setters (which you currently do by defining a -``_get_columnName`` or ``_set_columnName`` method), you will pass -arguments to the ``Col`` object, like:: - - def _get_name(self, dbGetter): - return dbGetter().strip() - - name = StringCol(getter=_get_name) - -This gets rid of ``_SO_get_columnName`` as well. We can -transitionally add something to the metaclass to signal an error if a -spurious ``_get_columnName`` method is sitting around. - -Construction and Fetching -------------------------- - -Currently you fetch an object with class instantiation, e.g., -``Address(1)``. This may or may not create a new instance, and does -not create a table row. If you want to create a table row, you do -something like ``Address.new(city='New York', ...)``. This is -somewhat in contrast to normal Python, where class instantiation -(calling a class) will create a new object, while objects are fetched -otherwise (with no particular standard interface). - -To make SQLObject classes more normal in this case, ``new`` will -become ``__init__`` (more or less), and classes will have a ``get`` -method that gets an already-existant row. E.g., ``Address.get(1)`` -vs. ``Address(city='New York', ...)``. This is perhaps the most -significant change in SQLObject usage. Because of the different -signatures, if you forget to make a change someplace you will get an -immediate exception, so updating code should not be too hard. - -Extra Table Information ------------------------ - -People have increasingly used SQLObject to create tables, and while it -can make a significant number of schemas, there are several extensions -of table generation that people occasionally want. Since these occur -later in development, it would be convenient if SQLObject could grow -as the complexity of the programs using it grow. Some of these -extensions are: - -* Table name (``_table``). -* Table type for MySQL (e.g., MyISAM vs. InnoDB). -* Multi-column unique constraints. (Other constraints?) -* Indexes. (Function or multi-column indexes?) -* Primary key type. (Primary key generation?) -* Primary key sequence names (for Postgres, Firebird, Oracle, etc). -* Multi-column primary keys. -* Naming scheme. -* Permissions. -* Locking (e.g., optimistic locking). -* Inheritance (see Daniel Savard's recent patch). -* Anything else? - -Some of these may be globally defined, or defined for an entire -database. For example, typically you'll want to use a common MySQL -table type for your entire database, even though its defined on a -per-table basis. And while MySQL allows global permission -declarations, Postgres does not and requires tedious repetitions of -the permissions for each table -- so while it's applied on a per-table -basis, it's likely that (at least to some degree) a per-database -declaration is called for. Naming schemes are also usually -database-wide. - -As these accumulate -- and by partitioning this list differently, the -list could be even longer -- it's messy to do these all as special -class variables (``_idName``, etc). It also makes the class logic and -its database implementation details difficult to distinguish. Some -of these can be handled elegantly like ``id = StringCol()`` or ``id -= ("fname", "lname")``. But the others perhaps should be put into a -single instance variable, perhaps itself a class:: - - class Address(SQLObject): - class SQLMeta: - mysqlType = 'InnoDB' - naming = Underscore - permission = {'bob': ['select', 'insert'], - 'joe': ['select', 'insert', 'update'], - 'public': ['select']} - street = StringCol() - .... - -The metadata is found by its name (``SQLMeta``), and is simply a -container. The class syntax is easier to write and read than a -dictionary-like syntax. Or, it could be a proper class/instance and -provide a partitioned way to handle introspection. E.g., -``Address.SQLMeta.permission.get('bob')`` or -``Address.SQLMeta.columns``. In this case values that weren't -overridden would be calculated from defaults (like the default naming -scheme and so on). - -I'm not at all certain about how this should look, or if there are -other things that should go into the class-meta-data object. - -Joins, Foreign Keys -------------------- - -First, the poorly-named ``MultipleJoin`` and ``RelatedJoin`` (which -are rather ambiguous) will be renamed ``ManyToOneJoin`` and -``ManyToManyJoin``. ``OneToOneJoin`` will also be added, while -``ForeignKey`` remains the related column type. (Many2Many? -Many2many? many2many?) - -ForeignKey will be driven by a special validator/converter. (But will -this make ID access more difficult?) - -Joins will return smart objects which can be iterated across. These -smart objects will be related to ``SelectResults``, and allow the -same features like ordering. In both cases, an option to retrieve -IDs instead of objects will be allowed. - -These smarter objects will allow, in the case of ManyToManyJoin, -``Set`` like operations to relate (or unrelate) objects. For -ManyToOneJoin the list/set operations are not really appropriate, -because they would reassign the relation, not just add or remove -relations. - -It would be nice to make the Join protocol more explicit and public, -so other kinds of joins (e.g., three-way) could be more accessible. - - Modified: home/phd/SQLObject/inheritance/sqlobject/col.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-02-01 08:01:49 UTC (rev 566) +++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-02-03 12:06:16 UTC (rev 567) @@ -52,11 +52,10 @@ else: mxdatetime_available = True -if datetime_available: - DATETIME_IMPLEMENTATION = "datetime" +DATETIME_IMPLEMENTATION = "datetime" +MXDATETIME_IMPLEMENTATION = "mxDateTime" if mxdatetime_available: - MXDATETIME_IMPLEMENTATION = "mxDateTime" DateTimeType = type(DateTime.now()) if datetime_available: @@ -716,7 +715,7 @@ def fromPython(self, value, state): if value is None: return None - if isinstance(value, (datetime.date, datetime.datetime)): + if isinstance(value, (datetime.date, datetime.datetime, sqlbuilder.SQLExpression)): return value if hasattr(value, "strftime"): return value.strftime(self.format) @@ -726,7 +725,7 @@ def toPython(self, value, state): if value is None: return None - if isinstance(value, (datetime.date, datetime.datetime)): + if isinstance(value, (datetime.date, datetime.datetime, sqlbuilder.SQLExpression)): return value if mxdatetime_available and isinstance(value, DateTimeType): # convert mxDateTime instance to datetime @@ -748,7 +747,7 @@ def fromPython(self, value, state): if value is None: return None - if isinstance(value, DateTimeType): + if isinstance(value, (DateTimeType, sqlbuilder.SQLExpression)): return value if hasattr(value, "strftime"): return value.strftime(self.format) @@ -758,7 +757,7 @@ def toPython(self, value, state): if value is None: return None - if isinstance(value, DateTimeType): + if isinstance(value, (DateTimeType, sqlbuilder.SQLExpression)): return value if datetime_available: # convert datetime instance to mxDateTime if isinstance(value, datetime.datetime): Modified: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-01 08:01:49 UTC (rev 566) +++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-02-03 12:06:16 UTC (rev 567) @@ -132,17 +132,17 @@ self._poolLock.acquire() try: if not self._pool: - newConn = self.makeConnection() - self._pool.append(newConn) - self._connectionNumbers[id(newConn)] = self._connectionCount + conn = self.makeConnection() + self._connectionNumbers[id(conn)] = self._connectionCount self._connectionCount += 1 - val = self._pool.pop() + else: + conn = self._pool.pop() if self.debug: s = 'ACQUIRE' if self._pool is not None: s += ' pool=[%s]' % ', '.join([str(self._connectionNumbers[id(v)]) for v in self._pool]) - self.printDebug(val, s, 'Pool') - return val + self.printDebug(conn, s, 'Pool') + return conn finally: self._poolLock.release() @@ -177,6 +177,8 @@ # the __del__ in Iteration (unfortunately, not sure why # it happens) self._pool.append(conn) + else: + conn.close() def printDebug(self, conn, s, name, type='query'): if type == 'query': @@ -479,12 +481,21 @@ self.close() def close(self): - if self._pool: - for conn in self._pool: + if not self._pool: + return + self._poolLock.acquire() + try: + conns = self._pool[:] + self._pool[:] = [] + for conn in conns: try: conn.close() except self.module.Error: pass + del conn + del conns + finally: + self._poolLock.release() class Iteration(object): #phd: default array size for cursor.fetchmany() |
From: <sub...@co...> - 2005-02-01 08:01:54
|
Author: ianb Date: 2005-02-01 08:01:49 +0000 (Tue, 01 Feb 2005) New Revision: 566 Removed: trunk/SQLObject/test trunk/SQLObject/tests/ Log: Removed tests that are redundant with the new py.test tests Deleted: trunk/SQLObject/test =================================================================== --- trunk/SQLObject/test 2005-02-01 07:59:43 UTC (rev 565) +++ trunk/SQLObject/test 2005-02-01 08:01:49 UTC (rev 566) @@ -1,18 +0,0 @@ -#!/bin/sh -VERSION="" -if [ "$1" = "2.3" ] ; then - VERSION=2.3 - shift -elif [ "$1" = "2.2" ] ; then - VERSION=2.2 - shift -fi - -if [ "$1" = "cover" ] ; then - shift - sudo python$VERSION setup.py -q install - python$VERSION ./tests/coverage.py -x tests/test_sqlobject.py $* - ./tests/coverage.py -a `find tests SQLObject -name '*.py'` -else - sudo python$VERSION setup.py -q install && python$VERSION tests/test_sqlobject.py $* -fi |
From: <sub...@co...> - 2005-02-01 07:59:45
|
Author: ianb Date: 2005-02-01 07:59:43 +0000 (Tue, 01 Feb 2005) New Revision: 565 Added: trunk/SQLObject/sqlobject/tests/ trunk/SQLObject/sqlobject/tests/__init__.py trunk/SQLObject/sqlobject/tests/dbtest.py trunk/SQLObject/sqlobject/tests/test_auto.py trunk/SQLObject/sqlobject/tests/test_basic.py trunk/SQLObject/sqlobject/tests/test_blob.py trunk/SQLObject/sqlobject/tests/test_cache.py trunk/SQLObject/sqlobject/tests/test_constraints.py trunk/SQLObject/sqlobject/tests/test_converters.py trunk/SQLObject/sqlobject/tests/test_datetime.py trunk/SQLObject/sqlobject/tests/test_delete.py trunk/SQLObject/sqlobject/tests/test_distinct.py trunk/SQLObject/sqlobject/tests/test_enum.py trunk/SQLObject/sqlobject/tests/test_expire.py trunk/SQLObject/sqlobject/tests/test_indexes.py trunk/SQLObject/sqlobject/tests/test_inheritance.py trunk/SQLObject/sqlobject/tests/test_joins.py trunk/SQLObject/sqlobject/tests/test_lazy.py trunk/SQLObject/sqlobject/tests/test_picklecol.py trunk/SQLObject/sqlobject/tests/test_select.py trunk/SQLObject/sqlobject/tests/test_slice.py trunk/SQLObject/sqlobject/tests/test_sorting.py trunk/SQLObject/sqlobject/tests/test_stringid.py trunk/SQLObject/sqlobject/tests/test_style.py trunk/SQLObject/sqlobject/tests/test_transactions.py trunk/SQLObject/sqlobject/tests/test_unicode.py trunk/SQLObject/sqlobject/tests/test_validation.py Modified: trunk/SQLObject/docs/DeveloperGuide.txt Log: Refactored tests into py.test framework; included documentation about how to use the new tests. Modified: trunk/SQLObject/docs/DeveloperGuide.txt =================================================================== --- trunk/SQLObject/docs/DeveloperGuide.txt 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/docs/DeveloperGuide.txt 2005-02-01 07:59:43 UTC (rev 565) @@ -26,16 +26,19 @@ * I don't stress too much on line length. But try to break lines up by grouping with parenthesis instead of with backslashes (if you - can). + can). Do asserts like:: + assert some_condition(a, b), ( + "Some condition failed, %r isn't right!" % a) + * But if you are having problems with line length, maybe you should just break the expression up into multiple statements. * Blank lines between methods, unless they are very small and closely bound to each other. -* Never use the form ``condition and trueValue or falseValue``. Break - it out and use a variable. +* *Never* use the form ``condition and trueValue or falseValue``. + Break it out and use a variable. * Careful of namespace pollution. SQLObject does allow for ``from sqlobject import *`` so names should be fairly distinct, or they @@ -122,27 +125,42 @@ Tests are important. Tests keep everything from falling apart. All new additions should have tests. -Right now all the tests are in one big file ``tests.py``. Which is -unfortunate, but that's the way it is. They may seem complex, but -they aren't so bad really. They all subclass from ``SQLObjectTest``. +Testing uses `py.test`__, an alternative to ``unittest``. It is +available via subversion at http://codespeak.net/svn/py/dist. Read +its `getting started`_ document for more. -The ``classes`` attribute is special in a test class. This is a list -of `SQLObject` subclasses that this test uses. `SQLObjectTest` will -create the tables before the tests are run, and destroy them after. +.. __: http://codespeak.net/py/current/doc/test.html +.. _getting started: http://codespeak.net/py/current/doc/getting_started.html -You may also define an ``.inserts()`` method. This method sets up the -basic data. When doing verbose input (``-vv``) you won't see these -inserts, since they may be overwhelming. Use the command-line options -``--inserts`` to show them (as well as the create statement. +To actually run the test, you have to give it a database to connect +to. You do this with the ``TESTDB`` environmental variable (right now +py.test doesn't have a better way to add custom options). You can +give the complete URI to access your test database, or you can give it +a shortcut like ``mysql`` (these shortcuts are defined in the top of +``tests/dbtest.py``. -When running tests, use ``-ddbname`` to test with ``dbname`` (e.g., -``-dmysql``, ``-dpostgres``, etc), or ``-dall`` to use Postgres, -MySQL, Firebird, and SQLite (all the core supported databases; -everything I have installed on my computer). Please try to test as -many databases as you can. At least SQLite and one other should be -easy, though if you can test both Postgres and MySQL that would be -much better. +All the tests are modules in ``sqlobject/tests``. Each module tests +one kind of feature, more or less. If you are testing a module, call +the test module ``tests/test_modulename.py`` -- only modules that +start with ``test_`` will be picked up by py.test. +The "framework" for testing is in ``tests/dbtest``. There's a couple +important functions: + +``setupClass(soClass)`` creates the tables for the class. It tries to +avoid recreating tables if not necessary. + +``supports(featureName)`` checks if the database backend supports the +named feature. What backends support what is defined at the top of +``dbtest``. + +If you ``import *`` you'll also get py.test's version of raises_, an +``inserts`` function that can create instances for you, and a couple +miscellaneous functions. + +.. _raises: http://codespeak.net/py/current/doc/test.html#id4 + + If you submit a patch or implement a feature without a test, I'll be forced to write the test. That's no fun for me, to just be writing tests. So please, write tests; everything at least needs to be Added: trunk/SQLObject/sqlobject/tests/__init__.py =================================================================== --- trunk/SQLObject/sqlobject/tests/__init__.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/__init__.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1 @@ +# Added: trunk/SQLObject/sqlobject/tests/dbtest.py =================================================================== --- trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/dbtest.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,218 @@ +""" +The framework for making database tests. +""" + +import os +import re +import sqlobject +from py.test import raises + +connectionShortcuts = { + 'mysql': 'mysql://test@localhost/test', + 'dbm': 'dbm:///data', + 'postgres': 'postgres:///test', + 'postgresql': 'postgres:///test', + 'pygresql': 'pygresql://localhost/test', + 'sqlite': 'sqlite:///%s/data/sqlite.data' % os.getcwd(), + 'sybase': 'sybase://test:test123@sybase/test?autoCommit=0', + 'firebird': 'firebird://sysdba:masterkey@localhost/var/lib/firebird/data/test.gdb', + } + +""" +supportsMatrix defines what database backends support what features. +Each feature has a name, if you see a key like '+featureName' then +only the databases listed support the feature. Conversely, +'-featureName' means all databases *except* the ones listed support +the feature. The databases are given by their SQLObject string name, +separated by spaces. + +The function supports(featureName) returns True or False based on this, +and you can use it like:: + + def test_featureX(): + if not supports('featureX'): + return +""" +supportsMatrix = { + '+restrictedEnum': 'postgres', + '-transactions': 'mysql', + '-dropTableCascade': 'sybase', + '-dynamicColumn': 'sqlite sybase', + '-fromDatabase': 'sqlite sybase firebird', + '-expressionIndex': 'mysql sqlite firebird', + } + + +def setupClass(soClass): + """ + Makes sure the class has a corresponding and correct table. + This won't recreate the table if it already exists. It will + check that the table is properly defined (in case you change + your table definition). + """ + connection = getConnection() + soClass._connection = connection + installOrClear(soClass) + return soClass + +installedDBFilename = os.path.join(os.getcwd(), 'dbs_data.tmp') + +installedDBTracker = sqlobject.connectionForURI( + 'sqlite:///' + installedDBFilename) + +def getConnection(): + name = os.environ.get('TESTDB') + assert name, 'You must set $TESTDB to do database operations' + if connectionShortcuts.has_key(name): + name = connectionShortcuts[name] + return sqlobject.connectionForURI(name) + +connection = getConnection() + +class InstalledTestDatabase(sqlobject.SQLObject): + """ + This table is set up in SQLite (always, regardless of $TESTDB) and + tracks what tables have been set up in the 'real' database. This + way we don't keep recreating the tables over and over when there + are multiple tests that use a table. + """ + + _connection = installedDBTracker + tableName = sqlobject.StringCol(notNull=True) + createSQL = sqlobject.StringCol(notNull=True) + connectionURI = sqlobject.StringCol(notNull=True) + + def installOrClear(cls, soClass): + cls.setup() + table = soClass._table + if not soClass._connection.tableExists(table): + cls.install(soClass) + items = list(cls.selectBy( + tableName=table, + connectionURI=soClass._connection.uri())) + if items: + instance = items[0] + sql = instance.createSQL + else: + sql = None + newSQL = soClass.createTableSQL() + if sql != newSQL: + if sql is not None: + instance.destroySelf() + cls.drop(soClass) + cls.install(soClass) + else: + cls.clear(soClass) + installOrClear = classmethod(installOrClear) + + def install(cls, soClass): + """ + Creates the given table in its database. + """ + sql = getattr(soClass, soClass._connection.dbName + 'Create', + None) + if sql: + soClass._connection.query(sql) + else: + sql = soClass.createTableSQL() + soClass.createTable() + cls(tableName=soClass._table, + createSQL=sql, + connectionURI=soClass._connection.uri()) + install = classmethod(install) + + def drop(cls, soClass): + """ + Drops a the given table from its database + """ + sql = getattr(soClass, soClass._connection.dbName + 'Drop', None) + if sql: + soClass._connection.query(sql) + else: + soClass.dropTable() + drop = classmethod(drop) + + def clear(cls, soClass): + """ + Removes all the rows from a table. + """ + soClass.clearTable() + clear = classmethod(clear) + + def setup(cls): + """ + This sets up *this* table. + """ + if not cls._connection.tableExists(cls._table): + cls.createTable() + setup = classmethod(setup) + +installOrClear = InstalledTestDatabase.installOrClear + +class Dummy(object): + + """ + Used for creating fake objects; a really poor 'mock object'. + """ + + def __init__(self, **kw): + for name, value in kw.items(): + setattr(self, name, value) + +def d(**kw): + """ + Because dict(**kw) doesn't work in Python 2.2, this is a + replacement. + """ + return kw + +def inserts(cls, data, schema=None): + """ + Creates a bunch of rows. You can use it like:: + + inserts(Person, [{'fname': 'blah', 'lname': 'doe'}, ...]) + + Or:: + + inserts(Person, [('blah', 'doe')], schema= + ['fname', 'lname']) + + If you give a single string for the `schema` then it'll split + that string to get the list of column names. + """ + if schema: + if isinstance(schema, str): + schema = schema.split() + keywordData = [] + for item in data: + itemDict = {} + for name, value in zip(schema, item): + itemDict[name] = value + keywordData.append(itemDict) + data = keywordData + results = [] + for args in data: + results.append(cls(**args)) + return results + +def supports(feature): + dbName = connection.dbName + support = supportsMatrix.get('+' + feature, None) + notSupport = supportsMatrix.get('-' + feature, None) + if support is not None and dbName in support.split(): + return True + else: + return False + if notSupport is not None and dbName in notSupport.split(): + return False + else: + return True + assert notSupport is not None or support is not None, ( + "The supportMatrix does not list this feature: %r" + % feature) + +# To avoid name clashes: +_inserts = inserts + +__all__ = ['getConnection', 'setupClass', 'Dummy', 'raises', + 'd', 'inserts', 'supports'] Added: trunk/SQLObject/sqlobject/tests/test_auto.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_auto.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,136 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject import classregistry + +######################################## +## Dynamic column tests +######################################## + +class Person(SQLObject): + + _columns = [StringCol('name', length=100, dbName='name_col')] + _defaultOrder = 'name' + +class Phone(SQLObject): + + _columns = [StringCol('phone', length=12)] + _defaultOrder = 'phone' + +class TestPeople: + + def setup_method(self, meth): + setupClass(Person) + setupClass(Phone) + for n in ['jane', 'tim', 'bob', 'jake']: + Person(name=n) + for p in ['555-555-5555', '555-394-2930', + '444-382-4854']: + Phone(phone=p) + + def test_defaultOrder(self): + assert (list(Person.select('all')) == + list(Person.select('all', orderBy=Person._defaultOrder))) + + def test_dynamicColumn(self): + if not supports('dynamicColumn'): + return + nickname = StringCol('nickname', length=10) + Person.addColumn(nickname, changeSchema=True) + n = Person(name='robert', nickname='bob') + assert ([p.name for p in Person.select('all')] + == ['bob', 'jake', 'jane', 'robert', 'tim']) + Person.delColumn(nickname, changeSchema=True) + + def test_dynamicJoin(self): + if not supports('dynamicColumn'): + return + col = KeyCol('personID', foreignKey='Person') + Phone.addColumn(col, changeSchema=True) + join = MultipleJoin('Phone') + Person.addJoin(join) + for phone in Phone.select('all'): + if phone.phone.startswith('555'): + phone.person = Person.selectBy(name='tim')[0] + else: + phone.person = Person.selectBy(name='bob')[0] + l = [p.phone for p in Person.selectBy(name='tim')[0].phones] + l.sort() + assert l == ['555-394-2930', '555-555-5555'] + Phone.delColumn(col, changeSchema=True) + Person.delJoin(join) + +######################################## +## Auto class generation +######################################## + +class TestAuto: + + mysqlCreate = """ + CREATE TABLE IF NOT EXISTS auto_test ( + auto_id INT AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(100), + last_name VARCHAR(200) NOT NULL, + age INT DEFAULT NULL, + created DATETIME NOT NULL, + happy char(1) DEFAULT 'Y' NOT NULL, + wannahavefun TINYINT DEFAULT 0 NOT NULL + ) + """ + + postgresCreate = """ + CREATE TABLE auto_test ( + auto_id SERIAL PRIMARY KEY, + first_name VARCHAR(100), + last_name VARCHAR(200) NOT NULL, + age INT DEFAULT 0, + created VARCHAR(40) NOT NULL, + happy char(1) DEFAULT 'Y' NOT NULL, + wannahavefun BOOL DEFAULT FALSE NOT NULL + ) + """ + + sybaseCreate = """ + CREATE TABLE auto_test ( + auto_id integer, + first_name VARCHAR(100), + last_name VARCHAR(200) NOT NULL, + age INT DEFAULT 0, + created VARCHAR(40) NOT NULL, + happy char(1) DEFAULT 'Y' NOT NULL + ) + """ + + mysqlDrop = """ + DROP TABLE IF EXISTS auto_test + """ + + postgresDrop = """ + DROP TABLE auto_test + """ + + sybaseDrop = """ + DROP TABLE auto_test + """ + + _table = 'auto_test' + + def test_classCreate(self): + if not supports('fromDatabase'): + return + class AutoTest(SQLObject): + _fromDatabase = True + _idName = 'auto_id' + _connection = connection() + john = AutoTest(firstName='john', + lastName='doe', + age=10, + created=now(), + wannahavefun=False) + jane = AutoTest(firstName='jane', + lastName='doe', + happy='N', + created=now(), + wannahavefun=True) + assert not john.wannahavefun + assert jane.wannahavefun + del classregistry.registry(AutoTest._registry).classes['AutoTest'] Added: trunk/SQLObject/sqlobject/tests/test_basic.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_basic.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,230 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +class TestSO1(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(TestSO1) + bob = TestSO1.selectBy(name='bob')[0] + assert bob.name == 'bob' + assert bob.passwd == 'god'.encode('rot13') + +def test_newline(): + setupGetters(TestSO1) + bob = TestSO1.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(TestSO1) + assert TestSO1.selectBy(name='bob').count() == 1 + assert TestSO1.select(TestSO1.q.name == 'bob').count() == 1 + assert TestSO1.select().count() == len(list(TestSO1.select())) + +def test_getset(): + setupGetters(TestSO1) + bob = TestSO1.selectBy(name='bob')[0] + assert bob.name == 'bob' + bob.name = 'joe' + assert bob.name == 'joe' + +class TestSO2(SQLObject): + name = StringCol(length=50, dbName='name_col') + passwd = StringCol(length=10) + + def _set_passwd(self, passwd): + self._SO_set_passwd(passwd.encode('rot13')) + +def test_case2(): + setupGetters(TestSO2) + bob = TestSO2.selectBy(name='bob')[0] + assert bob.name == 'bob' + assert bob.passwd == 'god'.encode('rot13') + +class Student(SQLObject): + is_smart = BoolCol() + +def test_boolCol(): + setupClass(Student) + student = Student(is_smart=False) + assert student.is_smart == False + student2 = Student(is_smart='false') + assert student2.is_smart == True + +class TestSO3(SQLObject): + name = StringCol(length=10, dbName='name_col') + other = ForeignKey('TestSO4', default=None) + other2 = KeyCol(foreignKey='TestSO4', default=None) + +class TestSO4(SQLObject): + me = StringCol(length=10) + +def test_foreignKey(): + setupClass(TestSO3) + setupClass(TestSO4) + tc3 = TestSO3(name='a') + assert tc3.other is None + assert tc3.other2 is None + assert tc3.otherID is None + assert tc3.other2ID is None + tc4a = TestSO4(me='1') + tc3.other = tc4a + assert tc3.other == tc4a + assert tc3.otherID == tc4a.id + tc4b = TestSO4(me='2') + tc3.other = tc4b.id + assert tc3.other == tc4b + assert tc3.otherID == tc4b.id + tc4c = TestSO4(me='3') + tc3.other2 = tc4c + assert tc3.other2 == tc4c + assert tc3.other2ID == tc4c.id + tc4d = TestSO4(me='4') + tc3.other2 = tc4d.id + assert tc3.other2 == tc4d + assert tc3.other2ID == tc4d.id + tcc = TestSO3(name='b', other=tc4a) + assert tcc.other == tc4a + tcc2 = TestSO3(name='c', other=tc4a.id) + assert tcc2.other == tc4a + +class TestSO5(SQLObject): + name = StringCol(length=10, dbName='name_col') + other = ForeignKey('TestSO6', default=None, cascade=True) + another = ForeignKey('TestSO7', default=None, cascade=True) + +class TestSO6(SQLObject): + name = StringCol(length=10, dbName='name_col') + other = ForeignKey('TestSO7', default=None, cascade=True) + +class TestSO7(SQLObject): + name = StringCol(length=10, dbName='name_col') + +def test_foreignKeyDestroySelfCascade(): + setupClass(TestSO7) + setupClass(TestSO6) + setupClass(TestSO5) + + tc5 = TestSO5(name='a') + tc6a = TestSO6(name='1') + tc5.other = tc6a + tc7a = TestSO7(name='2') + tc6a.other = tc7a + tc5.another = tc7a + assert tc5.other == tc6a + assert tc5.otherID == tc6a.id + assert tc6a.other == tc7a + assert tc6a.otherID == tc7a.id + assert tc5.other.other == tc7a + assert tc5.other.otherID == tc7a.id + assert tc5.another == tc7a + assert tc5.anotherID == tc7a.id + assert tc5.other.other == tc5.another + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 1 + assert TestSO7.select().count() == 1 + tc6b = TestSO6(name='3') + tc6c = TestSO6(name='4') + tc7b = TestSO7(name='5') + tc6b.other = tc7b + tc6c.other = tc7b + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 3 + assert TestSO7.select().count() == 2 + tc6b.destroySelf() + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 2 + assert TestSO7.select().count() == 2 + tc7b.destroySelf() + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 1 + assert TestSO7.select().count() == 1 + tc7a.destroySelf() + assert TestSO5.select().count() == 0 + assert TestSO6.select().count() == 0 + assert TestSO7.select().count() == 0 + +def testForeignKeyDropTableCascade(): + if not supports('dropTableCascade'): + return + setupClass(TestSO7) + setupClass(TestSO6) + setupClass(TestSO5) + + tc5a = TestSO5(name='a') + tc6a = TestSO6(name='1') + tc5a.other = tc6a + tc7a = TestSO7(name='2') + tc6a.other = tc7a + tc5a.another = tc7a + tc5b = TestSO5(name='b') + tc5c = TestSO5(name='c') + tc6b = TestSO6(name='3') + tc5c.other = tc6b + assert TestSO5.select().count() == 3 + assert TestSO6.select().count() == 2 + assert TestSO7.select().count() == 1 + TestSO7.dropTable(cascade=True) + assert TestSO5.select().count() == 3 + assert TestSO6.select().count() == 2 + tc6a.destroySelf() + assert TestSO5.select().count() == 2 + assert TestSO6.select().count() == 1 + tc6b.destroySelf() + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 0 + assert iter(TestSO5.select()).next() == tc5b + tc6c = TestSO6(name='3') + tc5b.other = tc6c + assert TestSO5.select().count() == 1 + assert TestSO6.select().count() == 1 + tc6c.destroySelf() + assert TestSO5.select().count() == 0 + assert TestSO6.select().count() == 0 + +class TestSO8(SQLObject): + name = StringCol(length=10, dbName='name_col') + other = ForeignKey('TestSO9', default=None, cascade=False) + +class TestSO9(SQLObject): + name = StringCol(length=10, dbName='name_col') + +def testForeignKeyDestroySelfRestrict(): + setupClass(TestSO9) + setupClass(TestSO8) + + tc8a = TestSO8(name='a') + tc9a = TestSO9(name='1') + tc8a.other = tc9a + tc8b = TestSO8(name='b') + tc9b = TestSO9(name='2') + assert tc8a.other == tc9a + assert tc8a.otherID == tc9a.id + assert TestSO8.select().count() == 2 + assert TestSO9.select().count() == 2 + raises(Exception, tc9a.destroySelf) + tc9b.destroySelf() + assert TestSO8.select().count() == 2 + assert TestSO9.select().count() == 1 + tc8a.destroySelf() + tc8b.destroySelf() + tc9a.destroySelf() + assert TestSO8.select().count() == 0 + assert TestSO9.select().count() == 0 Added: trunk/SQLObject/sqlobject/tests/test_blob.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_blob.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_blob.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,22 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## BLOB columns +######################################## + +class ImageData(SQLObject): + image = BLOBCol(default='emptydata', length=65535) + +def test_BLOBCol(): + setupClass(ImageData) + data = ''.join([chr(x) for x in range(256)]) + + prof = ImageData() + prof.image = data + iid = prof.id + + ImageData._connection.cache.clear() + + prof2 = ImageData.get(iid) + assert prof2.image == data Copied: trunk/SQLObject/sqlobject/tests/test_cache.py (from rev 560, trunk/SQLObject/tests/test_cache.py) =================================================================== --- trunk/SQLObject/tests/test_cache.py 2005-01-31 22:57:35 UTC (rev 560) +++ trunk/SQLObject/sqlobject/tests/test_cache.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,21 @@ +from sqlobject.cache import CacheSet + +class Something(object): + pass + +def test_purge1(): + x = CacheSet() + y = Something() + obj = x.get(1, y.__class__) + assert obj is None + x.put(1, y.__class__, y) + x.finishPut(y.__class__) + j = x.get(1, y.__class__) + assert j == y + x.expire(1, y.__class__) + j = x.get(1, y.__class__) + assert j == None + x.finishPut(y.__class__) + j = x.get(1, y.__class__) + assert j == None + x.finishPut(y.__class__) Added: trunk/SQLObject/sqlobject/tests/test_constraints.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_constraints.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_constraints.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,34 @@ +from sqlobject.constraints import * +from sqlobject.tests.dbtest import * + +def test_constraints(): + obj = 'Test object' + col = Dummy(name='col') + isString(obj, col, 'blah') + raises(BadValue, isString, obj, col, 1) + # @@: Should this really be an error? + raises(BadValue, isString, obj, col, u'test!') + #isString(obj, col, u'test!') + + raises(BadValue, notNull, obj, col, None) + raises(BadValue, isInt, obj, col, 1.1) + isInt(obj, col, 1) + isInt(obj, col, 1L) + isFloat(obj, col, 1) + isFloat(obj, col, 1L) + isFloat(obj, col, 1.2) + raises(BadValue, isFloat, obj, col, '1.0') + + # @@: Should test isBool, but I don't think isBool is right + + lst = InList(('a', 'b', 'c')) + lst(obj, col, 'a') + raises(BadValue, lst, obj, col, ('a', 'b', 'c')) + raises(BadValue, lst, obj, col, 'A') + + maxlen = MaxLength(2) + raises(BadValue, maxlen, obj, col, '123') + maxlen(obj, col, '12') + maxlen(obj, col, (1,)) + raises(BadValue, maxlen, obj, col, 1) + Copied: trunk/SQLObject/sqlobject/tests/test_converters.py (from rev 560, trunk/SQLObject/tests/test_converters.py) =================================================================== --- trunk/SQLObject/tests/test_converters.py 2005-01-31 22:57:35 UTC (rev 560) +++ trunk/SQLObject/sqlobject/tests/test_converters.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,139 @@ +from sqlobject.sqlbuilder import sqlrepr, TRUE, FALSE +from sqlobject.sqlbuilder import SQLExpression, SQLObjectField, \ + Select, Insert, Update, Delete, Replace, \ + SQLTrueClauseClass, SQLConstant, SQLPrefix, SQLCall, SQLOp +from sqlobject.converters import registerConverter + +class TestClass: + + def __repr__(self): + return '<TestClass>' + +def TestClassConverter(value, db): + return repr(value) + +registerConverter(TestClass, TestClassConverter) + +class NewTestClass: + + __metaclass__ = type + + def __repr__(self): + return '<NewTestClass>' + +def NewTestClassConverter(value, db): + return repr(value) + +registerConverter(NewTestClass, NewTestClassConverter) + +def _sqlrepr(self, db): + return '<%s>' % self.__class__.__name__ + +SQLExpression.__sqlrepr__ = _sqlrepr + +############################################################ +## Tests +############################################################ + +def test_simple_string(): + assert sqlrepr('A String', 'firebird') == "'A String'" + +def test_string_newline(): + assert sqlrepr('A String\nAnother', 'postgres') == "'A String\\nAnother'" + assert sqlrepr('A String\nAnother', 'sqlite') == "'A String\nAnother'" + +def test_string_tab(): + assert sqlrepr('A String\tAnother', 'postgres') == "'A String\\tAnother'" + +def test_string_r(): + assert sqlrepr('A String\rAnother', 'postgres') == "'A String\\rAnother'" + +def test_string_b(): + assert sqlrepr('A String\bAnother', 'postgres') == "'A String\\bAnother'" + +def test_string_000(): + assert sqlrepr('A String\000Another', 'postgres') == "'A String\\0Another'" + +def test_string_(): + assert sqlrepr('A String\'Another', 'postgres') == "'A String\\'Another'" + assert sqlrepr('A String\'Another', 'firebird') == "'A String''Another'" + +def test_simple_unicode(): + assert sqlrepr(u'A String', 'postgres') == "'A String'" + +def test_integer(): + assert sqlrepr(10) == "10" + +def test_float(): + assert sqlrepr(10.01) == "10.01" + +def test_none(): + assert sqlrepr(None) == "NULL" + +def test_list(): + assert sqlrepr(['one','two','three'], 'postgres') == "('one', 'two', 'three')" + +def test_tuple(): + assert sqlrepr(('one','two','three'), 'postgres') == "('one', 'two', 'three')" + +def test_bool(): + assert sqlrepr(TRUE, 'postgres') == "'t'" + assert sqlrepr(FALSE, 'postgres') == "'f'" + assert sqlrepr(TRUE, 'mysql') == "1" + assert sqlrepr(FALSE, 'mysql') == "0" + +def test_instance(): + instance = TestClass() + assert sqlrepr(instance) == repr(instance) + +def test_newstyle(): + instance = NewTestClass() + assert sqlrepr(instance) == repr(instance) + +def test_sqlexpr(): + instance = SQLExpression() + assert sqlrepr(instance) == repr(instance) + +def test_sqlobjectfield(): + instance = SQLObjectField('test', 'test', 'test') + assert sqlrepr(instance) == repr(instance) + +def test_select(): + instance = Select('test') + assert sqlrepr(instance, 'mysql') == "SELECT 'test'" + +def test_insert(): + instance = Insert('test', [('test',)]) + assert sqlrepr(instance, 'mysql') == "INSERT INTO test VALUES ('test')" + +def test_update(): + instance = Update('test', {'test':'test'}) + assert sqlrepr(instance, 'mysql') == "UPDATE test SET test='test'" + +def test_delete(): + instance = Delete('test', None) + assert sqlrepr(instance, 'mysql') == "DELETE FROM test" + +def test_replace(): + instance = Replace('test', {'test':'test'}) + assert sqlrepr(instance, 'mysql') == "REPLACE test SET test='test'" + +def test_trueclause(): + instance = SQLTrueClauseClass() + assert sqlrepr(instance) == repr(instance) + +def test_op(): + instance = SQLOp('and', 'this', 'that') + assert sqlrepr(instance, 'mysql') == "('this' AND 'that')" + +def test_call(): + instance = SQLCall('test', ('test',)) + assert sqlrepr(instance, 'mysql') == "'test'('test')" + +def test_constant(): + instance = SQLConstant('test') + assert sqlrepr(instance) == repr(instance) + +def test_prefix(): + instance = SQLPrefix('test', 'test') + assert sqlrepr(instance, 'mysql') == "test 'test'" Added: trunk/SQLObject/sqlobject/tests/test_datetime.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_datetime.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_datetime.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,52 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject import col +if default_datetime_implementation == DATETIME_IMPLEMENTATION: + from datetime import datetime + now = datetime.now +elif default_datetime_implementation == MXDATETIME_IMPLEMENTATION: + from mx.DateTime import now + +######################################## +## Date/time columns +######################################## + +if datetime_available: + col.default_datetime_implementation = DATETIME_IMPLEMENTATION + from datetime import date, datetime + + class DateTime1(SQLObject): + col1 = DateCol() + col2 = DateTimeCol() + + def test_dateTime(): + setupClass(DateTime1) + _now = now() + dt1 = DateTime1(col1=_now, col2=_now) + assert isinstance(dt1.col1, date) + assert isinstance(dt1.col2, datetime) + + today_str = _now.strftime("%Y-%m-%d") + now_str = _now.strftime("%Y-%m-%d %T") + assert str(dt1.col1) == today_str + assert str(dt1.col2) == now_str + +if mxdatetime_available: + col.default_datetime_implementation = MXDATETIME_IMPLEMENTATION + + class DateTime2(SQLObject): + col1 = DateCol() + col2 = DateTimeCol() + + def test_mxDateTime(): + setupClass(DateTime2) + _now = now() + dt2 = DateTime2(col1=_now, col2=_now) + assert isinstance(dt2.col1, col.DateTimeType) + assert isinstance(dt2.col2, col.DateTimeType) + + today_str = _now.strftime("%Y-%m-%d 00:00:00.00") + now_str = _now.strftime("%Y-%m-%d %T.00") + assert str(dt2.col1) == today_str + assert str(dt2.col2) == now_str + Added: trunk/SQLObject/sqlobject/tests/test_delete.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_delete.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_delete.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,29 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from test_basic import TestSO1, setupGetters + +######################################## +## Delete during select +######################################## + +def testSelect(): + setupGetters(TestSO1) + for obj in TestSO1.select('all'): + obj.destroySelf() + assert list(TestSO1.select('all')) == [] + +######################################## +## Delete without caching +######################################## + +class NoCache(SQLObject): + name = StringCol() + +def testDestroySelf(): + setupClass(NoCache) + old = NoCache._connection.cache + NoCache._connection.cache = cache.CacheSet(cache=False) + value = NoCache(name='test') + value.destroySelf() + NoCache._connection.cache = old + Added: trunk/SQLObject/sqlobject/tests/test_distinct.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_distinct.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_distinct.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,32 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Distinct +######################################## + +class Distinct1(SQLObject): + n = IntCol() + +class Distinct2(SQLObject): + other = ForeignKey('Distinct1') + +def count(select): + result = {} + for ob in select: + result[int(ob.n)] = result.get(int(ob.n), 0)+1 + return result + +def test_distinct(): + setupClass(Distinct1) + setupClass(Distinct2) + obs = [Distinct1(n=i) for i in range(3)] + Distinct2(other=obs[0]) + Distinct2(other=obs[0]) + Distinct2(other=obs[1]) + + query = (Distinct2.q.otherID==Distinct1.q.id) + sel = Distinct1.select(query) + assert count(sel) == {0: 2, 1: 1} + sel = Distinct1.select(query, distinct=True) + assert count(sel) == {0: 1, 1:1} Added: trunk/SQLObject/sqlobject/tests/test_enum.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_enum.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_enum.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,19 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Enum test +######################################## + +class Enum1(SQLObject): + + _columns = [ + EnumCol('l', enumValues=['a', 'bcd', 'e']), + ] + +def testBad(): + setupClass(Enum1) + for l in ['a', 'bcd', 'a', 'e']: + Enum1(l=l) + if supports('restrictedEnum'): + raises(Enum1._connection.module.IntegrityError, Enum1, l='b') Added: trunk/SQLObject/sqlobject/tests/test_expire.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_expire.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_expire.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,26 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Expiring, syncing +######################################## + +class SyncTest(SQLObject): + name = StringCol(length=50, alternateID=True, dbName='name_col') + +def test_expire(): + setupClass(SyncTest) + SyncTest(name='bob') + SyncTest(name='tim') + + conn = SyncTest._connection + b = SyncTest.byName('bob') + conn.query("UPDATE sync_test SET name_col = 'robert' WHERE id = %i" + % b.id) + assert b.name == 'bob' + b.expire() + assert b.name == 'robert' + conn.query("UPDATE sync_test SET name_col = 'bobby' WHERE id = %i" + % b.id) + b.sync() + assert b.name == 'bobby' Added: trunk/SQLObject/sqlobject/tests/test_indexes.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_indexes.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_indexes.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,42 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Indexes +######################################## + +class SOIndex1(SQLObject): + name = StringCol(length=100) + number = IntCol() + + nameIndex = DatabaseIndex('name', unique=True) + nameIndex2 = DatabaseIndex(name, number) + nameIndex3 = DatabaseIndex({'column': name, + 'length': 3}) + +class SOIndex2(SQLObject): + + name = StringCol() + + nameIndex = DatabaseIndex({'expression': 'lower(name)'}) + +def test_1(): + setupClass(SOIndex1) + n = 0 + for name in 'blah blech boring yep yort snort'.split(): + n += 1 + SOIndex1(name=name, number=n) + mod = SOIndex1._connection.module + try: + SOIndex1(name='blah', number=0) + except (mod.ProgrammingError, mod.IntegrityError): + # expected + pass + else: + assert 0, "Exception expected." + +def test_2(): + if not supports('expressionIndex'): + return + setupClass(SOIndex2) + SOIndex2(name='') Added: trunk/SQLObject/sqlobject/tests/test_inheritance.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_inheritance.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_inheritance.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,31 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Inheritance +######################################## + +class Super(SQLObject): + + _columns = [StringCol('name', length=10)] + +class Sub(Super): + + _columns = Super._columns + [StringCol('name2', length=10)] + +def test_super(): + setupClass(Super) + setupClass(Sub) + s1 = Super(name='one') + s2 = Super(name='two') + s3 = Super.get(s1.id) + assert s1 == s3 + +def test_sub(): + setupClass(Super) + setupClass(Sub) + s1 = Sub(name='one', name2='1') + s2 = Sub(name='two', name2='2') + s3 = Sub.get(s1.id) + assert s1 == s3 + Added: trunk/SQLObject/sqlobject/tests/test_joins.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_joins.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_joins.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,106 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Joins +######################################## + +class PersonJoiner(SQLObject): + + _columns = [StringCol('name', length=40, alternateID=True)] + _joins = [RelatedJoin('AddressJoiner')] + +class AddressJoiner(SQLObject): + + _columns = [StringCol('zip', length=5, alternateID=True)] + _joins = [RelatedJoin('PersonJoiner')] + +class ImplicitJoiningSO(SQLObject): + foo = RelatedJoin('Bar') + +class ExplicitJoiningSO(SQLObject): + _joins = [MultipleJoin('Bar', joinMethodName='foo')] + +class TestJoin: + + def setup_method(self, meth): + setupClass(PersonJoiner) + setupClass(AddressJoiner) + for n in ['bob', 'tim', 'jane', 'joe', 'fred', 'barb']: + PersonJoiner(name=n) + for z in ['11111', '22222', '33333', '44444']: + AddressJoiner(zip=z) + + def test_join(self): + b = PersonJoiner.byName('bob') + assert b.addressJoiners == [] + z = AddressJoiner.byZip('11111') + b.addAddressJoiner(z) + self.assertZipsEqual(b.addressJoiners, ['11111']) + self.assertNamesEqual(z.personJoiners, ['bob']) + z2 = AddressJoiner.byZip('22222') + b.addAddressJoiner(z2) + self.assertZipsEqual(b.addressJoiners, ['11111', '22222']) + self.assertNamesEqual(z2.personJoiners, ['bob']) + b.removeAddressJoiner(z) + self.assertZipsEqual(b.addressJoiners, ['22222']) + self.assertNamesEqual(z.personJoiners, []) + + def assertZipsEqual(self, zips, dest): + assert [a.zip for a in zips] == dest + + def assertNamesEqual(self, people, dest): + assert [p.name for p in people] == dest + + def test_joinAttributeWithUnderscores(self): + # Make sure that the implicit setting of joinMethodName works + assert hasattr(ImplicitJoiningSO, 'foo') + assert not hasattr(ImplicitJoiningSO, 'bars') + + # And make sure explicit setting also works + assert hasattr(ExplicitJoiningSO, 'foo') + assert not hasattr(ExplicitJoiningSO, 'bars') + +class PersonJoiner2(SQLObject): + + _columns = [StringCol('name', length=40, alternateID=True)] + _joins = [MultipleJoin('AddressJoiner2')] + +class AddressJoiner2(SQLObject): + + _columns = [StringCol('zip', length=5), + StringCol('plus4', length=4, default=None), + ForeignKey('PersonJoiner2')] + _defaultOrder = ['-zip', 'plus4'] + +class TestJoin2: + + def setup_method(self, meth): + setupClass(PersonJoiner2) + setupClass(AddressJoiner2) + p1 = PersonJoiner2(name='bob') + p2 = PersonJoiner2(name='sally') + for z in ['11111', '22222', '33333']: + a = AddressJoiner2(zip=z, personJoiner2=p1) + #p1.addAddressJoiner2(a) + AddressJoiner2(zip='00000', personJoiner2=p2) + + def test_basic(self): + bob = PersonJoiner2.byName('bob') + sally = PersonJoiner2.byName('sally') + assert len(bob.addressJoiner2s) == 3 + assert len(sally.addressJoiner2s) == 1 + bob.addressJoiner2s[0].destroySelf() + assert len(bob.addressJoiner2s) == 2 + z = bob.addressJoiner2s[0] + z.zip = 'xxxxx' + id = z.id + del z + z = AddressJoiner2.get(id) + assert z.zip == 'xxxxx' + + def test_defaultOrder(self): + p1 = PersonJoiner2.byName('bob') + assert ([i.zip for i in p1.addressJoiner2s] + == ['33333', '22222', '11111']) + Added: trunk/SQLObject/sqlobject/tests/test_lazy.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_lazy.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_lazy.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,161 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Lazy updates +######################################## + +class Lazy(SQLObject): + + _lazyUpdate = True + name = StringCol() + other = StringCol(default='nothing') + third = StringCol(default='third') + +class TestLazyTest: + + def setup_method(self, meth): + # All this stuff is so that we can track when the connection + # does an actual update; we put in a new _SO_update method + # that calls the original and sets an instance variable that + # we can later check. + setupClass(Lazy) + self.conn = Lazy._connection + self.conn.didUpdate = False + self._oldUpdate = self.conn._SO_update + newUpdate = ( + lambda so, values, s=self, c=self.conn, o=self._oldUpdate: + self._alternateUpdate(so, values, c, o)) + self.conn._SO_update = newUpdate + + def teardown_method(self, meth): + self.conn._SO_update = self._oldUpdate + del self._oldUpdate + + def _alternateUpdate(self, so, values, conn, oldUpdate): + conn.didUpdate = True + return oldUpdate(so, values) + + def test_lazy(self): + assert not self.conn.didUpdate + obj = Lazy(name='tim') + # We just did an insert, but not an update: + assert not self.conn.didUpdate + obj.set(name='joe') + assert obj.dirty + assert obj.name == 'joe' + assert not self.conn.didUpdate + obj.syncUpdate() + assert obj.name == 'joe' + assert self.conn.didUpdate + assert not obj.dirty + assert obj.name == 'joe' + self.conn.didUpdate = False + + obj = Lazy(name='frank') + obj.name = 'joe' + assert not self.conn.didUpdate + assert obj.dirty + assert obj.name == 'joe' + obj.name = 'joe2' + assert not self.conn.didUpdate + assert obj.dirty + assert obj.name == 'joe2' + obj.syncUpdate() + assert obj.name == 'joe2' + assert not obj.dirty + assert self.conn.didUpdate + self.conn.didUpdate = False + + obj = Lazy(name='loaded') + assert not obj.dirty + assert not self.conn.didUpdate + assert obj.name == 'loaded' + obj.name = 'unloaded' + assert obj.dirty + assert obj.name == 'unloaded' + assert not self.conn.didUpdate + obj.sync() + assert not obj.dirty + assert obj.name == 'unloaded' + assert self.conn.didUpdate + self.conn.didUpdate = False + obj.name = 'whatever' + assert obj.dirty + assert obj.name == 'whatever' + assert not self.conn.didUpdate + obj._SO_loadValue('name') + assert obj.dirty + assert obj.name == 'whatever' + assert not self.conn.didUpdate + obj._SO_loadValue('other') + assert obj.name == 'whatever' + assert not self.conn.didUpdate + obj.syncUpdate() + assert self.conn.didUpdate + self.conn.didUpdate = False + + # Now, check that get() doesn't screw + # cached objects' validator state. + obj_id = obj.id + old_state = obj._SO_validatorState + obj = Lazy.get(obj_id) + assert not obj.dirty + assert not self.conn.didUpdate + assert obj._SO_validatorState is old_state + assert obj.name == 'whatever' + obj.name = 'unloaded' + assert obj.name == 'unloaded' + assert obj.dirty + assert not self.conn.didUpdate + # Fetch the object again with get() and + # make sure dirty is still set, as the + # object should come from the cache. + obj = Lazy.get(obj_id) + assert obj.dirty + assert not self.conn.didUpdate + assert obj.name == 'unloaded' + obj.syncUpdate() + assert self.conn.didUpdate + assert not obj.dirty + self.conn.didUpdate = False + + # Then clear the cache, and try a get() + # again, to make sure stuf like _SO_createdValues + # is properly initialized. + self.conn.cache.clear() + obj = Lazy.get(obj_id) + assert not obj.dirty + assert not self.conn.didUpdate + assert obj.name == 'unloaded' + obj.name = 'spongebob' + assert obj.name == 'spongebob' + assert obj.dirty + assert not self.conn.didUpdate + obj.syncUpdate() + assert self.conn.didUpdate + assert not obj.dirty + self.conn.didUpdate = False + + obj = Lazy(name='last') + assert not obj.dirty + obj.syncUpdate() + assert not self.conn.didUpdate + assert not obj.dirty + # Check that setting multiple values + # actually works. This was broken + # and just worked because we were testing + # only one value at a time, so 'name' + # had the right value after the for loop *wink* + # Also, check that passing a name that is not + # a valid column doesn't break, but instead + # just does a plain setattr. + obj.set(name='first', other='who', third='yes') + assert obj.name == 'first' + assert obj.other == 'who' + assert obj.third == 'yes' + assert obj.dirty + assert not self.conn.didUpdate + obj.syncUpdate() + assert self.conn.didUpdate + assert not obj.dirty Added: trunk/SQLObject/sqlobject/tests/test_picklecol.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_picklecol.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_picklecol.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,36 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Pickle columns +######################################## + +class PickleData: + pi = 3.14156 + def __init__(self): + self.question = 'The Ulimate Question of Life, the Universe and Everything' + self.answer = 42 + +class PickleContainer(SQLObject): + pickledata = PickleCol(default=None, length=65535) + +def test_pickleCol(): + setupClass(PickleContainer) + mypickledata = PickleData() + + ctnr = PickleContainer(pickledata=mypickledata) + iid = ctnr.id + + PickleContainer._connection.cache.clear() + + ctnr2 = PickleContainer.get(iid) + s2 = ctnr2.pickledata + + assert isinstance(s2, PickleData) + assert isinstance(s2.pi, float) + assert isinstance(s2.question, str) + assert isinstance(s2.answer, int) + assert s2.pi == mypickledata.pi + assert s2.question == mypickledata.question + assert s2.answer == mypickledata.answer + Added: trunk/SQLObject/sqlobject/tests/test_select.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_select.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_select.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,101 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +try: + enumerate +except NameError: + def enumerate(iterable): + i = 0 + for obj in iterable: + yield i, obj + i += 1 + +class IterTest(SQLObject): + name = StringCol(dbName='name_col') + +names = ('a', 'b', 'c') +def setupIter(): + setupClass(IterTest) + for n in names: + IterTest(name=n) + +def test_00_normal(): + setupIter() + count = 0 + for test in IterTest.select(): + count += 1 + assert count == len(names) + +def test_01_turn_to_list(): + count = 0 + for test in list(IterTest.select()): + count += 1 + assert count == len(names) + +def test_02_generator(): + all = IterTest.select() + count = 0 + for i, test in enumerate(all): + count += 1 + assert count == len(names) + +def test_03_ranged_indexed(): + all = IterTest.select() + count = 0 + for i in range(all.count()): + test = all[i] + count += 1 + assert count == len(names) + +def test_04_indexed_ended_by_exception(): + all = IterTest.select() + count = 0 + try: + while 1: + test = all[count] + count = count+1 + # Stop the test if it's gone on too long + if count > len(names): + break + except IndexError: + pass + assert count == len(names) + + + + +class Counter2(SQLObject): + + _columns = [ + IntCol('n1', notNull=True), + IntCol('n2', notNull=True), + ] + +class TestSelect: + + def setup_method(self, meth): + setupClass(Counter2) + for i in range(10): + for j in range(10): + Counter2(n1=i, n2=j) + + def counterEqual(self, counters, value): + assert [(c.n1, c.n2) for c in counters] == value + + def accumulateEqual(self, func, counters, value): + assert func([c.n1 for c in counters]) == value + + def test_1(self): + try: + sum + except NameError: + def sum(sequence): + s = 0 + for item in sequence: + s = s + item + return s + self.accumulateEqual(sum,Counter2.select(orderBy='n1'), + sum(range(10)) * 10) + + def test_2(self): + self.accumulateEqual(len,Counter2.select('all'), 100) Added: trunk/SQLObject/sqlobject/tests/test_slice.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_slice.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_slice.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,51 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## Slicing tests +######################################## + +class Counter(SQLObject): + + _columns = [ + IntCol('number', notNull=True), + ] + +class TestSlice: + + def setup_method(self, meth): + setupClass(Counter) + for i in range(100): + Counter(number=i) + + def counterEqual(self, counters, value): + assert [c.number for c in counters] == value + + def test_1(self): + self.counterEqual( + Counter.select(None, orderBy='number'), range(100)) + + def test_2(self): + self.counterEqual( + Counter.select(None, orderBy='number')[10:20], + range(10, 20)) + + def test_3(self): + self.counterEqual( + Counter.select(None, orderBy='number')[20:30][:5], + range(20, 25)) + + def test_4(self): + self.counterEqual( + Counter.select(None, orderBy='number')[:-10], + range(0, 90)) + + def test_5(self): + self.counterEqual( + Counter.select(None, orderBy='number', reversed=True), + range(99, -1, -1)) + + def test_6(self): + self.counterEqual( + Counter.select(None, orderBy='-number'), + range(99, -1, -1)) Added: trunk/SQLObject/sqlobject/tests/test_sorting.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_sorting.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_sorting.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,62 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +class Names(SQLObject): + + _table = 'names_table' + + firstName = StringCol(length=30) + lastName = StringCol(length=30) + + _defaultOrder = ['lastName', 'firstName'] + +def setupNames(): + setupClass(Names) + inserts(Names, [('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(Names.select()) == \ + ['aj baker', 'joe baker', + 'tim jackson', 'joe robbins', + 'zoe robbins'] + +def test_otherOrder(): + setupNames() + assert nameList(Names.select().orderBy(['firstName', 'lastName'])) == \ + ['aj baker', 'joe baker', + 'joe robbins', 'tim jackson', + 'zoe robbins'] + +def test_untranslatedColumnOrder(): + setupNames() + assert nameList(Names.select().orderBy(['first_name', 'last_name'])) == \ + ['aj baker', 'joe baker', + 'joe robbins', 'tim jackson', + 'zoe robbins'] + +def test_singleUntranslatedColumnOrder(): + setupNames() + assert firstList(Names.select().orderBy('firstName')) == \ + ['aj', 'joe', 'joe', 'tim', 'zoe'] + assert firstList(Names.select().orderBy('first_name')) == \ + ['aj', 'joe', 'joe', 'tim', 'zoe'] + assert firstList(Names.select().orderBy('-firstName')) == \ + ['zoe', 'tim', 'joe', 'joe', 'aj'] + assert firstList(Names.select().orderBy('-first_name')) == \ + ['zoe', 'tim', 'joe', 'joe', 'aj'] + assert firstList(Names.select().orderBy(Names.q.firstName)) == \ + ['aj', 'joe', 'joe', 'tim', 'zoe'] + Added: trunk/SQLObject/sqlobject/tests/test_stringid.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_stringid.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_stringid.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,63 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * + +######################################## +## String ID test +######################################## + +class SOStringID(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(SOStringID) + t = SOStringID(id='hey', val='whatever') + t2 = SOStringID.byVal('whatever') + assert t == t2 + t3 = SOStringID(id='you', val='nowhere') + t4 = SOStringID.get('you') + assert t3 == t4 Added: trunk/SQLObject/sqlobject/tests/test_style.py =================================================================== --- trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-01 07:58:02 UTC (rev 564) +++ trunk/SQLObject/sqlobject/tests/test_style.py 2005-02-01 07:59:43 UTC (rev 565) @@ -0,0 +1,28 @@ +from sqlobject import * +from sqlobject.tests.dbtest import * +from sqlobject import styles + +class Another... [truncated message content] |
From: <sub...@co...> - 2005-02-01 07:58:08
|
Author: ianb Date: 2005-02-01 07:58:02 +0000 (Tue, 01 Feb 2005) New Revision: 564 Modified: trunk/SQLObject/docs/FAQ.txt Log: Noted what to do with Python keywords for columns Modified: trunk/SQLObject/docs/FAQ.txt =================================================================== --- trunk/SQLObject/docs/FAQ.txt 2005-02-01 06:45:18 UTC (rev 563) +++ trunk/SQLObject/docs/FAQ.txt 2005-02-01 07:58:02 UTC (rev 564) @@ -226,3 +226,12 @@ For this reason and several others, reloading modules is highly error-prone and difficult to support. + +Python Keywords +--------------- + +If you have a table column that is a Python keyword, you should know +that the Python attribute doesn't have to match the name of the +column. See `Irregular Naming`_ in the documentation. + +.. _Irregular Naming: SQLObject.html#irregular-naming |
From: <sub...@co...> - 2005-02-01 06:45:27
|
Author: ianb Date: 2005-02-01 06:45:18 +0000 (Tue, 01 Feb 2005) New Revision: 563 Modified: trunk/SQLObject/sqlobject/dbconnection.py trunk/SQLObject/sqlobject/postgres/pgconnection.py Log: * Add attributes like user, host, db to postgres connections, so that we can recreate the URI * Fixed an assert in dbconnection (NameError) Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-02-01 06:19:19 UTC (rev 562) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-01 06:45:18 UTC (rev 563) @@ -50,7 +50,7 @@ auth = auth + '@' + self.password auth = auth + ':' else: - assert not password, 'URIs cannot express passwords without usernames' + assert not self.password, 'URIs cannot express passwords without usernames' uri = '%s://%s' % (self.dbName, auth) if self.host: uri += self.host + '/' Modified: trunk/SQLObject/sqlobject/postgres/pgconnection.py =================================================================== --- trunk/SQLObject/sqlobject/postgres/pgconnection.py 2005-02-01 06:19:19 UTC (rev 562) +++ trunk/SQLObject/sqlobject/postgres/pgconnection.py 2005-02-01 06:45:18 UTC (rev 563) @@ -29,6 +29,10 @@ registerConverter(type(psycopg.Binary('')), PsycoBinaryConverter) + self.user = user + self.host = host + self.db = db + self.password = passwd if dsn is None: dsn = [] if db: |
From: <sub...@co...> - 2005-02-01 06:19:25
|
Author: ianb Date: 2005-02-01 06:19:19 +0000 (Tue, 01 Feb 2005) New Revision: 562 Modified: trunk/SQLObject/sqlobject/sqlbuilder.py Log: Make its __repr__ more robust (so it doesn't raise exceptions when a value is database-dependent) Modified: trunk/SQLObject/sqlobject/sqlbuilder.py =================================================================== --- trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-01 05:59:29 UTC (rev 561) +++ trunk/SQLObject/sqlobject/sqlbuilder.py 2005-02-01 06:19:19 UTC (rev 562) @@ -152,9 +152,14 @@ return SQLCall(self, args) def __repr__(self): - return self.__sqlrepr__(None) + try: + return self.__sqlrepr__(None) + except AssertionError: + return '<%s %s>' % ( + self.__class__.__name__, hex(id(self))[2:]) + def __str__(self): - return self.__sqlrepr__(None) + return repr(self) def __cmp__(self, other): raise VersionError, "Python 2.1+ required" |
From: <sub...@co...> - 2005-02-01 05:59:34
|
Author: ianb Date: 2005-02-01 05:59:29 +0000 (Tue, 01 Feb 2005) New Revision: 561 Modified: trunk/SQLObject/sqlobject/dbconnection.py Log: Be careful when __del__ happens not to throw an exception Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-01-31 22:57:35 UTC (rev 560) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-02-01 05:59:29 UTC (rev 561) @@ -480,6 +480,10 @@ self.close() def close(self): + if not hasattr(self, '_pool'): + # Probably there was an exception while creating this + # instance, so it is incomplete. + return if not self._pool: return self._poolLock.acquire() |
From: <sub...@co...> - 2005-01-31 06:39:35
|
Author: phd Date: 2005-01-31 06:39:21 +0000 (Mon, 31 Jan 2005) New Revision: 559 Modified: trunk/SQLObject/sqlobject/col.py Log: Fixed a bug in date/time validators: allow sqlbuilder.SQLExpression. Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-01-28 21:01:13 UTC (rev 558) +++ trunk/SQLObject/sqlobject/col.py 2005-01-31 06:39:21 UTC (rev 559) @@ -717,7 +717,7 @@ def fromPython(self, value, state): if value is None: return None - if isinstance(value, (datetime.date, datetime.datetime)): + if isinstance(value, (datetime.date, datetime.datetime, sqlbuilder.SQLExpression)): return value if hasattr(value, "strftime"): return value.strftime(self.format) @@ -727,7 +727,7 @@ def toPython(self, value, state): if value is None: return None - if isinstance(value, (datetime.date, datetime.datetime)): + if isinstance(value, (datetime.date, datetime.datetime, sqlbuilder.SQLExpression)): return value if mxdatetime_available and isinstance(value, DateTimeType): # convert mxDateTime instance to datetime @@ -749,7 +749,7 @@ def fromPython(self, value, state): if value is None: return None - if isinstance(value, DateTimeType): + if isinstance(value, (DateTimeType, sqlbuilder.SQLExpression)): return value if hasattr(value, "strftime"): return value.strftime(self.format) @@ -759,7 +759,7 @@ def toPython(self, value, state): if value is None: return None - if isinstance(value, DateTimeType): + if isinstance(value, (DateTimeType, sqlbuilder.SQLExpression)): return value if datetime_available: # convert datetime instance to mxDateTime if isinstance(value, datetime.datetime): |
From: <sub...@co...> - 2005-01-28 21:01:23
|
Author: ianb Date: 2005-01-28 21:01:13 +0000 (Fri, 28 Jan 2005) New Revision: 558 Modified: trunk/SQLObject/sqlobject/dbconnection.py Log: Clean up getConnection a little -- if we need to make a connection, don't put it in the pool just to take it out again. Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-01-28 20:57:05 UTC (rev 557) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-01-28 21:01:13 UTC (rev 558) @@ -131,17 +131,17 @@ self._poolLock.acquire() try: if not self._pool: - newConn = self.makeConnection() - self._pool.append(newConn) - self._connectionNumbers[id(newConn)] = self._connectionCount + conn = self.makeConnection() + self._connectionNumbers[id(conn)] = self._connectionCount self._connectionCount += 1 - val = self._pool.pop() + else: + conn = self._pool.pop() if self.debug: s = 'ACQUIRE' if self._pool is not None: s += ' pool=[%s]' % ', '.join([str(self._connectionNumbers[id(v)]) for v in self._pool]) - self.printDebug(val, s, 'Pool') - return val + self.printDebug(conn, s, 'Pool') + return conn finally: self._poolLock.release() |
From: <sub...@co...> - 2005-01-28 20:58:00
|
Author: ianb Date: 2005-01-28 20:57:05 +0000 (Fri, 28 Jan 2005) New Revision: 557 Modified: trunk/SQLObject/sqlobject/col.py Log: Handle case where datetime isn't defined (it will result in a NameError otherwise, as there's a test against DATETIME_IMPLEMENTATION which isn't defined if datetime isn't importable) Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-01-28 20:48:00 UTC (rev 556) +++ trunk/SQLObject/sqlobject/col.py 2005-01-28 20:57:05 UTC (rev 557) @@ -51,11 +51,10 @@ else: mxdatetime_available = True -if datetime_available: - DATETIME_IMPLEMENTATION = "datetime" +DATETIME_IMPLEMENTATION = "datetime" +MXDATETIME_IMPLEMENTATION = "mxDateTime" if mxdatetime_available: - MXDATETIME_IMPLEMENTATION = "mxDateTime" DateTimeType = type(DateTime.now()) if datetime_available: |
From: <sub...@co...> - 2005-01-28 20:48:30
|
Author: ianb Date: 2005-01-28 20:48:00 +0000 (Fri, 28 Jan 2005) New Revision: 556 Modified: trunk/SQLObject/sqlobject/dbconnection.py Log: * Be a little more robust about closing a DBConnection instance, and closing all its direct connections * When there's no pooling (typically in a transaction) make sure you explicitly close connections that are released. Modified: trunk/SQLObject/sqlobject/dbconnection.py =================================================================== --- trunk/SQLObject/sqlobject/dbconnection.py 2005-01-26 06:07:52 UTC (rev 555) +++ trunk/SQLObject/sqlobject/dbconnection.py 2005-01-28 20:48:00 UTC (rev 556) @@ -176,6 +176,8 @@ # the __del__ in Iteration (unfortunately, not sure why # it happens) self._pool.append(conn) + else: + conn.close() def printDebug(self, conn, s, name, type='query'): if type == 'query': @@ -478,12 +480,21 @@ self.close() def close(self): - if self._pool: - for conn in self._pool: + if not self._pool: + return + self._poolLock.acquire() + try: + conns = self._pool[:] + self._pool[:] = [] + for conn in conns: try: conn.close() except self.module.Error: pass + del conn + del conns + finally: + self._poolLock.release() class Iteration(object): |
From: <sub...@co...> - 2005-01-26 06:07:55
|
Author: ianb Date: 2005-01-26 06:07:52 +0000 (Wed, 26 Jan 2005) New Revision: 555 Removed: trunk/SQLObject/docs/Plan06.txt Log: Doesn't really apply anymore; historically relevant, but not current. Deleted: trunk/SQLObject/docs/Plan06.txt =================================================================== --- trunk/SQLObject/docs/Plan06.txt 2005-01-26 05:49:13 UTC (rev 554) +++ trunk/SQLObject/docs/Plan06.txt 2005-01-26 06:07:52 UTC (rev 555) @@ -1,306 +0,0 @@ -SQLObject 0.6 -============= - -*A tentative plan, 20 Jan 2004* - -Introduction ------------- - -During vacation I thought about some changes that I might like to make -to SQLObject. Several of these change the API, but not too -drastically, and I think they change the API for the better. And we'd -not at 1.0 yet, changes are still allowed! Here's my ideas... - -Editing Context ---------------- - -Taken from Modeling, the "editing context" is essentially a -transaction, though it also encompasses some other features. -Typically it is used to distinguish between separate contexts in a -multi-threaded program. - -This is intended to separate several distinct concepts: - -* The database backend (MySQL, PostgreSQL, etc), coupled with the - driver (MySQLdb, psycopg, etc). (Should the driver be part of the - connection parameters?) -* The connection parameters. Typically these are the server host, - username, and password, but they could also be a filename or other - path. Perhaps this could be represented with a URI, ala PEAK, but - I also dislike taking structured data and destructuring it (i.e., - packing it into a string). OTOH, URLs are structured, even if they - require some parsing. Serialization of URLs is free and highly - transparent. Python syntax is well structured and - *programmatically* considerably more transparent (in a robust - fashion), but also programmatically fairly read-only (because it is - embedded in the structure of Python source code). We can also have - both. -* The database transactional context. -* The application transactional context (preferably these two would - be seemless, but they still represent somewhat distinct entities, - and a portability layer might be nice). The application's - transactional context may include other transactions -- e.g., - multiple databases, a ZODB transaction, etc. -* The cache policy. There are many different kinds of caches - potentially involved, include write batching, and per-object and - per-table caches, connection pooling, and so on. -* Classes, which on the database side are typically tables. (This - proposal does not attempt to de-couple classes and tables) - -Example:: - - from SQLObject import EditingContext - ec = EditingContext() - # every editing context automatically picks up all the SQLObject - # classes, all magic like. - person = ec.Person.get(1) # by ID - ec2 = EditingContext() # separate transaction - person2 = ec.Person.get(1) - assert person is not person2 - assert person.id == person2.id - assert person.fname == 'Guy' - person.fname = 'Gerald' - assert person2.fname == 'Guy' - ec.commit() # SQL is not sent to server - assert person2.fname == 'Guy' # Doesn't see changes - person2.fname = 'Norm' - # raises exception if locking is turned on; overwrites if locking - # is not turned on. (Locking enabled on a per-class level) - -I'm not at all sure about that example. Mostly the confusing parts -relate to locking and when the database lookup occurs (and how late a -conflict exception may be raised). - -Somewhere in here, process-level transactions might fit in. That is, -even on a backend that doesn't support transactions, we can still -delay SQL statements until a commit/rollback is performed. In turn, -we can create temporary "memory" objects, which is any object which -hasn't been committed to the database in any way. To do this we'll -need sequences -- to preallocate IDs -- which MySQL and SQLite don't -really provide :( - -Nested transactions...? Maybe they'd fall out of this fairly easily, -especially if we define a global context, with global caches etc., -then further levels of context will come for free. - -We still need to think about an auto-commit mode. Maybe the global -context would be auto-commit. - -Caching -------- - -Really doing transactions right means making caching significantly -more complex. If the cache is purely transaction-specific, then we'll -really be limiting the effectiveness of the cache. With that in mind, -a copy-on-write style of object is really called for -- when you fetch -an object in a transaction, you can use the globally cached instance -until you write to the object. - -Really this isn't copy-on-write, it's more like a proxy object. Until -the object is changed, it can delegate all its columns to its global -object for which it is a proxy. Of course, traversal via foreign keys -or joins must also return proxied objects. As the object is changed --- perhaps on a column-by-column basis, or as a whole on the first -change -- the object takes on the personality of a full SQLObject -instance. - -When the transaction is committed, this transactional object copies -itself to the global object, and becomes a full proxy. These -transactional caches themselves should be pooled -- so that when -another transaction comes along you have a potentially useful set of -proxy objects already created for you. This is a common use case for -web applications, which have lots of short transactions, which are -often very repetitive. - -In addition to this, there should be more cache control. This means -explicit ways to control things like: - -1. Caching of instances: - - * Application/process-global definition. - * Database-level definition. - * Transaction/EditingContext-level definition. - * Class-level definition. - -2. Caching of columns: - - * Class-level. - -3. Cache sweep frequency: - - * Application/process-global. - * Database-level. - * Class-level. - * Doesn't need to be as complete as 1; maybe on the class level you - could only indicate that a certain class should not be sweeped. - * Sweep during a fetch (e.g., every 100 fetches), by time or fetch - frequency, or sweep with an explicit call (e.g., to do sweeps in - a separate thread). - -4. Cache sweep policy: - - * Maximum age. - * Least-recently-used (actually, least-recently-fetched). - * Random (the current policy). - * Multi-level (randomly move objects to a lower-priority cache, - raise level when the object is fetched again). - * Target cache size (keep trimming until the cache is small - enough). - * Simple policy (if enough objects qualify, cache can be of any - size). - * Percentage culling (e.g., kill 33% of objects for each sweep; - this is the current policy). - -5. Batching of updates (whether updates should immediately go to the - database, or whether it would be batched until a commit or other - signal). - -6. Natural expiring of objects. Even if an object must persist - because there are still references, we could expire it so that - future accesses re-query the database. To avoid stale data. - -Expose some methods of the cache, like getting all objects currently -in memory. These would probably be exposed on a class level, e.g., -all the Addresses currently in memory via -``Address.cache.current()`` or something. What about when there's a -cached instance in the parent context, but not in the present -transaction? - -Columns as Descriptors ----------------------- - -Each column will become a descriptor. That is, ``Col`` and subclasses -will return an object with ``__get__`` and ``__set__`` methods. The -metaclass will not itself generate methods. - -A metaclass will still be used so that the descriptor can be tied to -its name, e.g., that with ``fname = StringCol()``, the resultant -descriptor will know that it is bound to ``fname``. - -By using descriptors, introspection should become a bit easier -- or -at least more uniform with respect to other new-style classes. -Various class-wide indexes of columns will still be necessary, but -these should be able to remain mostly private. - -To customize getters or setters (which you currently do by defining a -``_get_columnName`` or ``_set_columnName`` method), you will pass -arguments to the ``Col`` object, like:: - - def _get_name(self, dbGetter): - return dbGetter().strip() - - name = StringCol(getter=_get_name) - -This gets rid of ``_SO_get_columnName`` as well. We can -transitionally add something to the metaclass to signal an error if a -spurious ``_get_columnName`` method is sitting around. - -Construction and Fetching -------------------------- - -Currently you fetch an object with class instantiation, e.g., -``Address(1)``. This may or may not create a new instance, and does -not create a table row. If you want to create a table row, you do -something like ``Address.new(city='New York', ...)``. This is -somewhat in contrast to normal Python, where class instantiation -(calling a class) will create a new object, while objects are fetched -otherwise (with no particular standard interface). - -To make SQLObject classes more normal in this case, ``new`` will -become ``__init__`` (more or less), and classes will have a ``get`` -method that gets an already-existant row. E.g., ``Address.get(1)`` -vs. ``Address(city='New York', ...)``. This is perhaps the most -significant change in SQLObject usage. Because of the different -signatures, if you forget to make a change someplace you will get an -immediate exception, so updating code should not be too hard. - -Extra Table Information ------------------------ - -People have increasingly used SQLObject to create tables, and while it -can make a significant number of schemas, there are several extensions -of table generation that people occasionally want. Since these occur -later in development, it would be convenient if SQLObject could grow -as the complexity of the programs using it grow. Some of these -extensions are: - -* Table name (``_table``). -* Table type for MySQL (e.g., MyISAM vs. InnoDB). -* Multi-column unique constraints. (Other constraints?) -* Indexes. (Function or multi-column indexes?) -* Primary key type. (Primary key generation?) -* Primary key sequence names (for Postgres, Firebird, Oracle, etc). -* Multi-column primary keys. -* Naming scheme. -* Permissions. -* Locking (e.g., optimistic locking). -* Inheritance (see Daniel Savard's recent patch). -* Anything else? - -Some of these may be globally defined, or defined for an entire -database. For example, typically you'll want to use a common MySQL -table type for your entire database, even though its defined on a -per-table basis. And while MySQL allows global permission -declarations, Postgres does not and requires tedious repetitions of -the permissions for each table -- so while it's applied on a per-table -basis, it's likely that (at least to some degree) a per-database -declaration is called for. Naming schemes are also usually -database-wide. - -As these accumulate -- and by partitioning this list differently, the -list could be even longer -- it's messy to do these all as special -class variables (``_idName``, etc). It also makes the class logic and -its database implementation details difficult to distinguish. Some -of these can be handled elegantly like ``id = StringCol()`` or ``id -= ("fname", "lname")``. But the others perhaps should be put into a -single instance variable, perhaps itself a class:: - - class Address(SQLObject): - class SQLMeta: - mysqlType = 'InnoDB' - naming = Underscore - permission = {'bob': ['select', 'insert'], - 'joe': ['select', 'insert', 'update'], - 'public': ['select']} - street = StringCol() - .... - -The metadata is found by its name (``SQLMeta``), and is simply a -container. The class syntax is easier to write and read than a -dictionary-like syntax. Or, it could be a proper class/instance and -provide a partitioned way to handle introspection. E.g., -``Address.SQLMeta.permission.get('bob')`` or -``Address.SQLMeta.columns``. In this case values that weren't -overridden would be calculated from defaults (like the default naming -scheme and so on). - -I'm not at all certain about how this should look, or if there are -other things that should go into the class-meta-data object. - -Joins, Foreign Keys -------------------- - -First, the poorly-named ``MultipleJoin`` and ``RelatedJoin`` (which -are rather ambiguous) will be renamed ``ManyToOneJoin`` and -``ManyToManyJoin``. ``OneToOneJoin`` will also be added, while -``ForeignKey`` remains the related column type. (Many2Many? -Many2many? many2many?) - -ForeignKey will be driven by a special validator/converter. (But will -this make ID access more difficult?) - -Joins will return smart objects which can be iterated across. These -smart objects will be related to ``SelectResults``, and allow the -same features like ordering. In both cases, an option to retrieve -IDs instead of objects will be allowed. - -These smarter objects will allow, in the case of ManyToManyJoin, -``Set`` like operations to relate (or unrelate) objects. For -ManyToOneJoin the list/set operations are not really appropriate, -because they would reassign the relation, not just add or remove -relations. - -It would be nice to make the Join protocol more explicit and public, -so other kinds of joins (e.g., three-way) could be more accessible. - - |
From: <sub...@co...> - 2005-01-26 05:48:51
|
Author: ianb Date: 2005-01-26 05:48:44 +0000 (Wed, 26 Jan 2005) New Revision: 553 Modified: trunk/SQLObject/docs/News.txt Log: Changed version numbers Modified: trunk/SQLObject/docs/News.txt =================================================================== --- trunk/SQLObject/docs/News.txt 2005-01-24 12:10:39 UTC (rev 552) +++ trunk/SQLObject/docs/News.txt 2005-01-26 05:48:44 UTC (rev 553) @@ -7,8 +7,8 @@ .. _start: -SVN trunk -========= +SQLObject 0.6.1 +=============== Interface Changes ----------------- @@ -75,7 +75,7 @@ rows (like class instantiation used to do). * We're now using a Subversion repository instead of CVS. It is - located at svn://colorstudy.com/trunk/SQLObject + located at http://svn.colorstudy.com/trunk/SQLObject * If you pass ``forceDBName=True`` to the ``*Col`` constructors, then your column name doesn't have to be restricted to a-z, 0-9, and _. |
From: <sub...@co...> - 2005-01-24 12:10:49
|
Author: phd Date: 2005-01-24 12:10:39 +0000 (Mon, 24 Jan 2005) New Revision: 552 Modified: home/phd/SQLObject/inheritance/sqlobject/classregistry.py home/phd/SQLObject/inheritance/sqlobject/col.py home/phd/SQLObject/inheritance/sqlobject/dbconnection.py home/phd/SQLObject/inheritance/sqlobject/main.py Log: Optimize inheritance - prefetch children using cursor.fetchmany() instead of selecting them one by one. Modified: home/phd/SQLObject/inheritance/sqlobject/classregistry.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/classregistry.py 2005-01-15 17:00:08 UTC (rev 551) +++ home/phd/SQLObject/inheritance/sqlobject/classregistry.py 2005-01-24 12:10:39 UTC (rev 552) @@ -108,3 +108,6 @@ MasterRegistry = _MasterRegistry() registry = MasterRegistry.registry + +def findClass(name, class_registry=None): + return registry(class_registry).getClass(name) Modified: home/phd/SQLObject/inheritance/sqlobject/col.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-15 17:00:08 UTC (rev 551) +++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-24 12:10:39 UTC (rev 552) @@ -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 @@ -617,7 +618,6 @@ SOKeyCol.__init__(self, **kw) def postgresCreateSQL(self): - from main import findClass sql = SOKeyCol.postgresCreateSQL(self) other = findClass(self.foreignKey) tName = other._table @@ -643,7 +643,6 @@ return sql def sybaseCreateSQL(self): - from sqlobject.main import findClass sql = SOKeyCol.sybaseCreateSQL(self) other = findClass(self.foreignKey) tName = other._table @@ -655,7 +654,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: home/phd/SQLObject/inheritance/sqlobject/dbconnection.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-01-15 17:00:08 UTC (rev 551) +++ home/phd/SQLObject/inheritance/sqlobject/dbconnection.py 2005-01-24 12:10:39 UTC (rev 552) @@ -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") @@ -486,33 +487,89 @@ pass class Iteration(object): + #phd: default array size for cursor.fetchmany() + defaultArraySize = 10000 def __init__(self, dbconn, rawconn, select, keepConnection=False): self.dbconn = dbconn self.rawconn = rawconn self.select = select self.keepConnection = keepConnection - self.cursor = rawconn.cursor() + self.cursor = cursor = rawconn.cursor() self.query = self.dbconn.queryForSelect(select) if dbconn.debug: dbconn.printDebug(rawconn, self.query, 'Select') - self.dbconn._executeRetry(self.rawconn, self.cursor, self.query) + self.dbconn._executeRetry(self.rawconn, cursor, self.query) + 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 __iter__(self): return self def next(self): - result = self.cursor.fetchone() - if result is None: + 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 - if self.select.ops.get('lazyColumns', 0): + result = self._results[0] + del self._results[0] + if lazyColumns: obj = self.select.sourceClass.get(result[0], connection=self.dbconn) return obj else: - obj = self.select.sourceClass.get(result[0], selectResults=result[1:], connection=self.dbconn) + 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:] + def _cleanup(self): if getattr(self, 'query', None) is None: # already cleaned up Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 17:00:08 UTC (rev 551) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-24 12:10:39 UTC (rev 552) @@ -34,6 +34,7 @@ import joins import index import classregistry +findClass = classregistry.findClass # for those who imported findClass from sqlobject.main import sys if sys.version_info[:3] < (2, 2, 0): @@ -315,9 +316,7 @@ 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) +#phd: findClass has been moved to classregistry to avoid circular import def findDependencies(name, registry=None): depends = [] @@ -406,7 +405,7 @@ # aren't using integer IDs _idType = int - def get(cls, id, connection=None, selectResults=None, childUpdate=False): + def get(cls, id, connection=None, selectResults=None, childResults=None, childUpdate=False): assert id is not None, 'None is not a possible id for %s' % cls.__name @@ -434,7 +433,7 @@ if hasattr(val, 'childName'): childName = val.childName if childName is not None: - return val._childClasses[childName].get(id) + 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 |
From: <sub...@co...> - 2005-01-15 17:00:12
|
Author: phd Date: 2005-01-15 17:00:08 +0000 (Sat, 15 Jan 2005) New Revision: 551 Modified: home/phd/SQLObject/inheritance/sqlobject/main.py Log: Emulated basestring for Python 2.2. Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 14:32:28 UTC (rev 550) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 17:00:08 UTC (rev 551) @@ -1330,6 +1330,11 @@ return '_SO_val_%s' % name +try: + basestring +except NameError: # Python 2.2 + basestring = (types.StringType, types.UnicodeType) + class SelectResults(object): def __init__(self, sourceClass, clause, clauseTables=None, @@ -1341,7 +1346,7 @@ tablesDict = sqlbuilder.tablesUsedDict(self.clause) tablesDict[sourceClass._table] = 1 orderBy = ops.get('orderBy') - if orderBy and type(orderBy) <> type(''): + 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. |
From: <sub...@co...> - 2005-01-15 14:32:33
|
Author: phd Date: 2005-01-15 14:32:28 +0000 (Sat, 15 Jan 2005) New Revision: 550 Modified: home/phd/SQLObject/inheritance/sqlobject/col.py home/phd/SQLObject/inheritance/sqlobject/joins.py home/phd/SQLObject/inheritance/sqlobject/main.py home/phd/SQLObject/inheritance/tests/test_sqlobject.py Log: Merged patches from revisions 546:549 from the trunk: 546: Added FloatValidator and a test for it. 547: PickleCol now has a parameter pickleProtocol. Default is 1 (binary) for compatibility with Python 2.2. 548: Fixed incompatibility with Python 2.2: used col.popKey() instead of kw.pop(). 549: Fixed more incompatibility with Python 2.2: use string.find(), emulate sum() and enumerate() if they aren't available. 550: Fixed even more incompatibility with Python 2.2: Use type(orderBy) instead of isinstance(orderBy, basestring). Modified: home/phd/SQLObject/inheritance/sqlobject/col.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-15 14:25:16 UTC (rev 549) +++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-15 14:32:28 UTC (rev 550) @@ -528,13 +528,44 @@ class BoolCol(Col): baseClass = SOBoolCol + +class FloatValidator(validators.Validator): + + def fromPython(self, value, state): + if value is None: + return None + if not isinstance(value, (int, long, float, sqlbuilder.SQLExpression)): + raise validators.InvalidField("expected a float in the FloatCol '%s', got %s instead" % \ + (self.name, type(value)), value, state) + return value + + def toPython(self, value, state): + if value is None: + return None + if isinstance(value, (int, long, float, sqlbuilder.SQLExpression)): + return value + try: + return float(value) + except: + raise validators.InvalidField("expected a float in the FloatCol '%s', got %s instead" % \ + (self.name, type(value)), value, state) + class SOFloatCol(SOCol): + validatorClass = FloatValidator # can be overriden in descendants # 3-03 @@: support precision (e.g., DECIMAL) + def __init__(self, **kw): + SOCol.__init__(self, **kw) + self.validator = validators.All.join(self.createValidator(), self.validator) + def autoConstraints(self): return [consts.isFloat] + def createValidator(self): + """Create a validator for the column. Can be overriden in descendants.""" + return self.validatorClass(name=self.name) + def _sqlType(self): return 'FLOAT' @@ -544,6 +575,7 @@ class FloatCol(Col): baseClass = SOFloatCol + class SOKeyCol(SOCol): # 3-03 @@: this should have a simplified constructor @@ -700,7 +732,7 @@ return value if mxdatetime_available and isinstance(value, DateTimeType): # convert mxDateTime instance to datetime - if ("%H" in self.format) or ("%T" in self.format): + if (self.format.find("%H") >= 0) or (self.format.find("%T")) >= 0: return datetime.datetime(value.year, value.month, value.day, value.hour, value.minute, int(value.second)) else: @@ -904,18 +936,25 @@ return None if isinstance(value, str): return pickle.loads(value) - return value + raise validators.InvalidField("expected a pickle string in the PickleCol '%s', got %s instead" % \ + (self.name, type(value)), value, state) def fromPython(self, value, state): if value is None: return None - pickled = pickle.dumps(value) - return BinaryValidator.fromPython(self, pickled, state) + return pickle.dumps(value) - class SOPickleCol(SOBLOBCol): validatorClass = PickleValidator # can be overriden in descendants + def __init__(self, **kw): + self.pickleProtocol = popKey(kw, 'pickleProtocol', 1) + SOBLOBCol.__init__(self, **kw) + + def createValidator(self): + """Create a validator for the column. Can be overriden in descendants.""" + return self.validatorClass(name=self.name, pickleProtocol=self.pickleProtocol) + class PickleCol(BLOBCol): baseClass = SOPickleCol Modified: home/phd/SQLObject/inheritance/sqlobject/joins.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/joins.py 2005-01-15 14:25:16 UTC (rev 549) +++ home/phd/SQLObject/inheritance/sqlobject/joins.py 2005-01-15 14:32:28 UTC (rev 550) @@ -2,6 +2,7 @@ NoDefault = sqlbuilder.NoDefault import styles import classregistry +from col import popKey __all__ = ['MultipleJoin', 'RelatedJoin'] @@ -18,7 +19,7 @@ kw['joinDef'] = self self.kw = kw if self.kw.has_key('joinMethodName'): - self._joinMethodName = self.kw.pop('joinMethodName') + self._joinMethodName = popKey(self.kw, 'joinMethodName') else: self._joinMethodName = None Modified: home/phd/SQLObject/inheritance/sqlobject/main.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 14:25:16 UTC (rev 549) +++ home/phd/SQLObject/inheritance/sqlobject/main.py 2005-01-15 14:32:28 UTC (rev 550) @@ -1341,7 +1341,7 @@ tablesDict = sqlbuilder.tablesUsedDict(self.clause) tablesDict[sourceClass._table] = 1 orderBy = ops.get('orderBy') - if orderBy and not isinstance(orderBy, basestring): + if orderBy and type(orderBy) <> type(''): 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. Modified: home/phd/SQLObject/inheritance/tests/test_sqlobject.py =================================================================== --- home/phd/SQLObject/inheritance/tests/test_sqlobject.py 2005-01-15 14:25:16 UTC (rev 549) +++ home/phd/SQLObject/inheritance/tests/test_sqlobject.py 2005-01-15 14:32:28 UTC (rev 550) @@ -341,6 +341,14 @@ ######################################## ## Select results ######################################## +try: + enumerate +except NameError: + def enumerate(iterable): + i = 0 + for obj in iterable: + yield i, obj + i += 1 class IterTest(SQLObject): name = StringCol(dbName='name_col') @@ -369,11 +377,6 @@ self.failIf(count != len(self.names)) def test_02_generator(self): - def enumerate(iterable): - i = 0 - for obj in iterable: - yield i, obj - i += 1 all = IterTest.select() count = 0 for i, test in enumerate(all): @@ -579,6 +582,14 @@ self.assertEqual(func([ c.n1 for c in counters]), value) def test1(self): + try: + sum + except NameError: + def sum(sequence): + s = 0 + for item in sequence: + s = s + item + return s self.accumulateEqual(sum,Counter2.select(orderBy='n1'), sum(range(10)) * 10) @@ -890,6 +901,7 @@ name = StringCol(validator=validators.PlainText(), default='x', dbName='name_col') name2 = StringCol(validator=validators.ConfirmType(str), default='y') name3 = IntCol(validator=validators.Wrapper(fromPython=int), default=100) + name4 = FloatCol(default=2.718) class ValidationTest(SQLObjectTest): @@ -905,6 +917,10 @@ t = SOValidation(name2='hey') self.assertRaises(validators.InvalidField, setattr, t, 'name2', 1) + self.assertRaises(validators.InvalidField, setattr, t, + 'name3', '1') + self.assertRaises(validators.InvalidField, setattr, t, + 'name4', '1') t.name2 = 'you' def testWrapType(self): |
From: <sub...@co...> - 2005-01-15 14:25:21
|
Author: phd Date: 2005-01-15 14:25:16 +0000 (Sat, 15 Jan 2005) New Revision: 549 Modified: trunk/SQLObject/sqlobject/col.py trunk/SQLObject/tests/test_sqlobject.py Log: Fixed more incompatibility with Python 2.2: use string.find(), emulate sum() and enumerate() if they aren't available. Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-01-15 13:21:28 UTC (rev 548) +++ trunk/SQLObject/sqlobject/col.py 2005-01-15 14:25:16 UTC (rev 549) @@ -732,7 +732,7 @@ return value if mxdatetime_available and isinstance(value, DateTimeType): # convert mxDateTime instance to datetime - if ("%H" in self.format) or ("%T" in self.format): + if (self.format.find("%H") >= 0) or (self.format.find("%T")) >= 0: return datetime.datetime(value.year, value.month, value.day, value.hour, value.minute, int(value.second)) else: Modified: trunk/SQLObject/tests/test_sqlobject.py =================================================================== --- trunk/SQLObject/tests/test_sqlobject.py 2005-01-15 13:21:28 UTC (rev 548) +++ trunk/SQLObject/tests/test_sqlobject.py 2005-01-15 14:25:16 UTC (rev 549) @@ -341,6 +341,14 @@ ######################################## ## Select results ######################################## +try: + enumerate +except NameError: + def enumerate(iterable): + i = 0 + for obj in iterable: + yield i, obj + i += 1 class IterTest(SQLObject): name = StringCol(dbName='name_col') @@ -369,11 +377,6 @@ self.failIf(count != len(self.names)) def test_02_generator(self): - def enumerate(iterable): - i = 0 - for obj in iterable: - yield i, obj - i += 1 all = IterTest.select() count = 0 for i, test in enumerate(all): @@ -579,6 +582,14 @@ self.assertEqual(func([ c.n1 for c in counters]), value) def test1(self): + try: + sum + except NameError: + def sum(sequence): + s = 0 + for item in sequence: + s = s + item + return s self.accumulateEqual(sum,Counter2.select(orderBy='n1'), sum(range(10)) * 10) |
From: <sub...@co...> - 2005-01-15 13:21:30
|
Author: phd Date: 2005-01-15 13:21:28 +0000 (Sat, 15 Jan 2005) New Revision: 548 Modified: trunk/SQLObject/sqlobject/joins.py Log: Fixed incompatibility with Python 2.2: used col.popKey() instead of kw.pop(). Modified: trunk/SQLObject/sqlobject/joins.py =================================================================== --- trunk/SQLObject/sqlobject/joins.py 2005-01-15 12:50:16 UTC (rev 547) +++ trunk/SQLObject/sqlobject/joins.py 2005-01-15 13:21:28 UTC (rev 548) @@ -2,6 +2,7 @@ NoDefault = sqlbuilder.NoDefault import styles import classregistry +from col import popKey __all__ = ['MultipleJoin', 'RelatedJoin'] @@ -18,7 +19,7 @@ kw['joinDef'] = self self.kw = kw if self.kw.has_key('joinMethodName'): - self._joinMethodName = self.kw.pop('joinMethodName') + self._joinMethodName = popKey(self.kw, 'joinMethodName') else: self._joinMethodName = None |
From: <sub...@co...> - 2005-01-15 12:50:23
|
Author: phd Date: 2005-01-15 12:50:16 +0000 (Sat, 15 Jan 2005) New Revision: 547 Modified: trunk/SQLObject/sqlobject/col.py Log: PickleCol now has a parameter pickleProtocol. Default is 1 (binary) for compatibility with Python 2.2. Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-01-15 11:55:33 UTC (rev 546) +++ trunk/SQLObject/sqlobject/col.py 2005-01-15 12:50:16 UTC (rev 547) @@ -936,18 +936,25 @@ return None if isinstance(value, str): return pickle.loads(value) - return value + raise validators.InvalidField("expected a pickle string in the PickleCol '%s', got %s instead" % \ + (self.name, type(value)), value, state) def fromPython(self, value, state): if value is None: return None - pickled = pickle.dumps(value) - return BinaryValidator.fromPython(self, pickled, state) + return pickle.dumps(value) - class SOPickleCol(SOBLOBCol): validatorClass = PickleValidator # can be overriden in descendants + def __init__(self, **kw): + self.pickleProtocol = popKey(kw, 'pickleProtocol', 1) + SOBLOBCol.__init__(self, **kw) + + def createValidator(self): + """Create a validator for the column. Can be overriden in descendants.""" + return self.validatorClass(name=self.name, pickleProtocol=self.pickleProtocol) + class PickleCol(BLOBCol): baseClass = SOPickleCol |
From: <sub...@co...> - 2005-01-15 11:55:39
|
Author: phd Date: 2005-01-15 11:55:33 +0000 (Sat, 15 Jan 2005) New Revision: 546 Modified: trunk/SQLObject/sqlobject/col.py trunk/SQLObject/tests/test_sqlobject.py Log: Added FloatValidator and a test for it. Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-01-14 17:17:46 UTC (rev 545) +++ trunk/SQLObject/sqlobject/col.py 2005-01-15 11:55:33 UTC (rev 546) @@ -528,13 +528,44 @@ class BoolCol(Col): baseClass = SOBoolCol + +class FloatValidator(validators.Validator): + + def fromPython(self, value, state): + if value is None: + return None + if not isinstance(value, (int, long, float, sqlbuilder.SQLExpression)): + raise validators.InvalidField("expected a float in the FloatCol '%s', got %s instead" % \ + (self.name, type(value)), value, state) + return value + + def toPython(self, value, state): + if value is None: + return None + if isinstance(value, (int, long, float, sqlbuilder.SQLExpression)): + return value + try: + return float(value) + except: + raise validators.InvalidField("expected a float in the FloatCol '%s', got %s instead" % \ + (self.name, type(value)), value, state) + class SOFloatCol(SOCol): + validatorClass = FloatValidator # can be overriden in descendants # 3-03 @@: support precision (e.g., DECIMAL) + def __init__(self, **kw): + SOCol.__init__(self, **kw) + self.validator = validators.All.join(self.createValidator(), self.validator) + def autoConstraints(self): return [consts.isFloat] + def createValidator(self): + """Create a validator for the column. Can be overriden in descendants.""" + return self.validatorClass(name=self.name) + def _sqlType(self): return 'FLOAT' @@ -544,6 +575,7 @@ class FloatCol(Col): baseClass = SOFloatCol + class SOKeyCol(SOCol): # 3-03 @@: this should have a simplified constructor Modified: trunk/SQLObject/tests/test_sqlobject.py =================================================================== --- trunk/SQLObject/tests/test_sqlobject.py 2005-01-14 17:17:46 UTC (rev 545) +++ trunk/SQLObject/tests/test_sqlobject.py 2005-01-15 11:55:33 UTC (rev 546) @@ -890,6 +890,7 @@ name = StringCol(validator=validators.PlainText(), default='x', dbName='name_col') name2 = StringCol(validator=validators.ConfirmType(str), default='y') name3 = IntCol(validator=validators.Wrapper(fromPython=int), default=100) + name4 = FloatCol(default=2.718) class ValidationTest(SQLObjectTest): @@ -905,6 +906,10 @@ t = SOValidation(name2='hey') self.assertRaises(validators.InvalidField, setattr, t, 'name2', 1) + self.assertRaises(validators.InvalidField, setattr, t, + 'name3', '1') + self.assertRaises(validators.InvalidField, setattr, t, + 'name4', '1') t.name2 = 'you' def testWrapType(self): |
From: <sub...@co...> - 2005-01-14 14:28:33
|
Author: phd Date: 2005-01-14 14:28:29 +0000 (Fri, 14 Jan 2005) New Revision: 544 Modified: home/phd/SQLObject/inheritance/sqlobject/col.py home/phd/SQLObject/inheritance/tests/test_sqlobject.py Log: Merged patch from revision 543: added PickleCol, PickleValidator and tests. Modified: home/phd/SQLObject/inheritance/sqlobject/col.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-14 14:24:18 UTC (rev 543) +++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-14 14:28:29 UTC (rev 544) @@ -19,6 +19,10 @@ """ import re, time +try: + import cPickle as pickle +except ImportError: + import pickle import sqlbuilder # Sadly the name "constraints" conflicts with many of the function # arguments in this module, so we rename it: @@ -885,6 +889,37 @@ baseClass = SOBLOBCol +class PickleValidator(BinaryValidator): + """ + Validator for pickle types. A pickle type is simply a binary type with + hidden pickling, so that we can simply store any kind of stuff in a + particular column. + + The support for this relies directly on the support for binary for your + database. + """ + + def toPython(self, value, state): + if value is None: + return None + if isinstance(value, str): + return pickle.loads(value) + return value + + def fromPython(self, value, state): + if value is None: + return None + pickled = pickle.dumps(value) + return BinaryValidator.fromPython(self, pickled, state) + + +class SOPickleCol(SOBLOBCol): + validatorClass = PickleValidator # can be overriden in descendants + +class PickleCol(BLOBCol): + baseClass = SOPickleCol + + def popKey(kw, name, default=None): if not kw.has_key(name): return default Modified: home/phd/SQLObject/inheritance/tests/test_sqlobject.py =================================================================== --- home/phd/SQLObject/inheritance/tests/test_sqlobject.py 2005-01-14 14:24:18 UTC (rev 543) +++ home/phd/SQLObject/inheritance/tests/test_sqlobject.py 2005-01-14 14:28:29 UTC (rev 544) @@ -1342,25 +1342,61 @@ ## BLOB columns ######################################## -class Profile(SQLObject): +class ImageData(SQLObject): image = BLOBCol(default='emptydata', length=65535) class BLOBColTest(SQLObjectTest): - classes = [Profile] + classes = [ImageData] def testBLOBCol(self): data = ''.join([chr(x) for x in range(256)]) - prof = Profile() + prof = ImageData() prof.image = data iid = prof.id connection().cache.clear() - prof2 = Profile.get(iid) - assert prof2.image == data + prof2 = ImageData.get(iid) + self.assertEqual(prof2.image, data) ######################################## +## Pickle columns +######################################## + +class PickleData: + pi = 3.14156 + def __init__(self): + self.question = 'The Ulimate Question of Life, the Universe and Everything' + self.answer = 42 + +class PickleContainer(SQLObject): + pickledata = PickleCol(default=None, length=65535) + +class PickleColTest(SQLObjectTest): + classes = [PickleContainer] + + def testPickleCol(self): + mypickledata = PickleData() + + ctnr = PickleContainer(pickledata=mypickledata) + iid = ctnr.id + + connection().cache.clear() + + ctnr2 = PickleContainer.get(iid) + s2 = ctnr2.pickledata + + self.failUnless(isinstance(s2, PickleData)) + self.failUnless(isinstance(s2.pi, float)) + self.failUnless(isinstance(s2.question, str)) + self.failUnless(isinstance(s2.answer, int)) + self.assertEqual(s2.pi, mypickledata.pi) + self.assertEqual(s2.question, mypickledata.question) + self.assertEqual(s2.answer, mypickledata.answer) + + +######################################## ## Run from command-line: ######################################## |
From: <sub...@co...> - 2005-01-14 14:24:22
|
Author: phd Date: 2005-01-14 14:24:18 +0000 (Fri, 14 Jan 2005) New Revision: 543 Modified: trunk/SQLObject/sqlobject/col.py trunk/SQLObject/tests/test_sqlobject.py Log: Added PickleCol, PickleValidator and tests. Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-01-14 13:53:41 UTC (rev 542) +++ trunk/SQLObject/sqlobject/col.py 2005-01-14 14:24:18 UTC (rev 543) @@ -19,6 +19,10 @@ """ import re, time +try: + import cPickle as pickle +except ImportError: + import pickle import sqlbuilder # Sadly the name "constraints" conflicts with many of the function # arguments in this module, so we rename it: @@ -885,6 +889,37 @@ baseClass = SOBLOBCol +class PickleValidator(BinaryValidator): + """ + Validator for pickle types. A pickle type is simply a binary type with + hidden pickling, so that we can simply store any kind of stuff in a + particular column. + + The support for this relies directly on the support for binary for your + database. + """ + + def toPython(self, value, state): + if value is None: + return None + if isinstance(value, str): + return pickle.loads(value) + return value + + def fromPython(self, value, state): + if value is None: + return None + pickled = pickle.dumps(value) + return BinaryValidator.fromPython(self, pickled, state) + + +class SOPickleCol(SOBLOBCol): + validatorClass = PickleValidator # can be overriden in descendants + +class PickleCol(BLOBCol): + baseClass = SOPickleCol + + def popKey(kw, name, default=None): if not kw.has_key(name): return default Modified: trunk/SQLObject/tests/test_sqlobject.py =================================================================== --- trunk/SQLObject/tests/test_sqlobject.py 2005-01-14 13:53:41 UTC (rev 542) +++ trunk/SQLObject/tests/test_sqlobject.py 2005-01-14 14:24:18 UTC (rev 543) @@ -1342,25 +1342,61 @@ ## BLOB columns ######################################## -class Profile(SQLObject): +class ImageData(SQLObject): image = BLOBCol(default='emptydata', length=65535) class BLOBColTest(SQLObjectTest): - classes = [Profile] + classes = [ImageData] def testBLOBCol(self): data = ''.join([chr(x) for x in range(256)]) - prof = Profile() + prof = ImageData() prof.image = data iid = prof.id connection().cache.clear() - prof2 = Profile.get(iid) - assert prof2.image == data + prof2 = ImageData.get(iid) + self.assertEqual(prof2.image, data) ######################################## +## Pickle columns +######################################## + +class PickleData: + pi = 3.14156 + def __init__(self): + self.question = 'The Ulimate Question of Life, the Universe and Everything' + self.answer = 42 + +class PickleContainer(SQLObject): + pickledata = PickleCol(default=None, length=65535) + +class PickleColTest(SQLObjectTest): + classes = [PickleContainer] + + def testPickleCol(self): + mypickledata = PickleData() + + ctnr = PickleContainer(pickledata=mypickledata) + iid = ctnr.id + + connection().cache.clear() + + ctnr2 = PickleContainer.get(iid) + s2 = ctnr2.pickledata + + self.failUnless(isinstance(s2, PickleData)) + self.failUnless(isinstance(s2.pi, float)) + self.failUnless(isinstance(s2.question, str)) + self.failUnless(isinstance(s2.answer, int)) + self.assertEqual(s2.pi, mypickledata.pi) + self.assertEqual(s2.question, mypickledata.question) + self.assertEqual(s2.answer, mypickledata.answer) + + +######################################## ## Run from command-line: ######################################## |
From: <sub...@co...> - 2005-01-14 13:53:43
|
Author: phd Date: 2005-01-14 13:53:41 +0000 (Fri, 14 Jan 2005) New Revision: 542 Modified: home/phd/SQLObject/inheritance/docs/SQLObject.txt home/phd/SQLObject/inheritance/sqlobject/col.py Log: Merged patches from revisions 539:541. Modified: home/phd/SQLObject/inheritance/docs/SQLObject.txt =================================================================== --- home/phd/SQLObject/inheritance/docs/SQLObject.txt 2005-01-14 13:52:12 UTC (rev 541) +++ home/phd/SQLObject/inheritance/docs/SQLObject.txt 2005-01-14 13:53:41 UTC (rev 542) @@ -1082,9 +1082,10 @@ `MySQLConnection` takes the keyword arguments `host`, `db`, `user`, and `passwd`, just like `MySQLdb.connect` does. -MySQLConnection supports all the features, though MySQL does not -support transactions_ (except using a special proprietary backend that -I haven't used). +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 -------- Modified: home/phd/SQLObject/inheritance/sqlobject/col.py =================================================================== --- home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-14 13:52:12 UTC (rev 541) +++ home/phd/SQLObject/inheritance/sqlobject/col.py 2005-01-14 13:53:41 UTC (rev 542) @@ -898,7 +898,7 @@ all = [] for key, value in globals().items(): - if isinstance(value, type) and issubclass(value, (Col, SOCol)): + if isinstance(value, type) and (issubclass(value, Col) or issubclass(value, SOCol)): all.append(key) __all__.extend(all) del all |
From: <sub...@co...> - 2005-01-14 13:52:17
|
Author: phd Date: 2005-01-14 13:52:12 +0000 (Fri, 14 Jan 2005) New Revision: 541 Modified: trunk/SQLObject/sqlobject/col.py Log: Fixed incompatibility - in Python 2.2 issubclass allows only a class as a second argument. Modified: trunk/SQLObject/sqlobject/col.py =================================================================== --- trunk/SQLObject/sqlobject/col.py 2005-01-13 17:29:02 UTC (rev 540) +++ trunk/SQLObject/sqlobject/col.py 2005-01-14 13:52:12 UTC (rev 541) @@ -898,7 +898,7 @@ all = [] for key, value in globals().items(): - if isinstance(value, type) and issubclass(value, (Col, SOCol)): + if isinstance(value, type) and (issubclass(value, Col) or issubclass(value, SOCol)): all.append(key) __all__.extend(all) del all |