merge branch master, fix tests for using zope testrunner, with postgres only
This commit is contained in:
		
						commit
						0fb0ba0c74
					
				
					 13 changed files with 275 additions and 43 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -10,3 +10,4 @@ | ||||||
| *#*# | *#*# | ||||||
| *.#* | *.#* | ||||||
| __pycache__ | __pycache__ | ||||||
|  | var | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								scopes/organize/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								scopes/organize/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | """package scopes.organize""" | ||||||
							
								
								
									
										13
									
								
								scopes/organize/task.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								scopes/organize/task.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | # scopes.organize.task | ||||||
|  | 
 | ||||||
|  | """Task (and corresponding container) implementation.""" | ||||||
|  | 
 | ||||||
|  | from scopes.storage.common import registerContainerClass | ||||||
|  | from scopes.storage.concept import Concept | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Task(Concept): | ||||||
|  | 
 | ||||||
|  |     headFields = ['name'] | ||||||
|  |     prefix = 'tsk'  | ||||||
|  | 
 | ||||||
							
								
								
									
										1
									
								
								scopes/server/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								scopes/server/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | """package scopes.server""" | ||||||
							
								
								
									
										44
									
								
								scopes/server/app.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								scopes/server/app.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,44 @@ | ||||||
|  | # scopes.server.app | ||||||
|  | 
 | ||||||
|  | from zope.publisher.base import DefaultPublication | ||||||
|  | from zope.publisher.browser import BrowserRequest | ||||||
|  | from zope.publisher.publish import publish | ||||||
|  | from zope.traversing.publicationtraverse import PublicationTraverser | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def demo_app(environ, start_response): | ||||||
|  |     print(f'*** environ {environ}.') | ||||||
|  |     status = '200 OK' | ||||||
|  |     headers = [("Content-type", "text/plain; charset=utf-8")] | ||||||
|  |     start_response(status, headers) | ||||||
|  |     return ['Hello World'.encode()] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def zope_app(environ, start_response): | ||||||
|  |     request = BrowserRequest(environ['wsgi.input'], environ) | ||||||
|  |     request.setPublication(DefaultPublication(AppRoot())) | ||||||
|  |     request = publish(request, False) | ||||||
|  |     response = request.response | ||||||
|  |     start_response(response.getStatusString(), response.getHeaders()) | ||||||
|  |     return response.consumeBodyIter() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class AppRoot: | ||||||
|  |     """Zope Demo AppRoot""" | ||||||
|  | 
 | ||||||
|  |     def __call__(self): | ||||||
|  |         """calling AppRoot""" | ||||||
|  |         return 'At root' | ||||||
|  | 
 | ||||||
|  |     def __getitem__(self, key): | ||||||
|  |         def child(): | ||||||
|  |             """get child""" | ||||||
|  |             print(f'--- getitem {key}') | ||||||
|  |             return 'getitem' | ||||||
|  |         return child | ||||||
|  | 
 | ||||||
|  |     def hello(self):  | ||||||
|  |         """method hello""" | ||||||
|  |         return 'Hello AppRoot' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -4,35 +4,61 @@ | ||||||
| 
 | 
 | ||||||
| import base64 | import base64 | ||||||
| from sqlalchemy import create_engine, MetaData, text | from sqlalchemy import create_engine, MetaData, text | ||||||
| from sqlalchemy.orm import scoped_session, sessionmaker | from sqlalchemy import Integer | ||||||
|  | from sqlalchemy.dialects.sqlite import JSON | ||||||
| import threading | import threading | ||||||
| import zope.sqlalchemy |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def getEngine(dbtype, dbname, user, pw, host='localhost', port=5432, **kw): | class StorageFactory(object): | ||||||
|     return create_engine('%s://%s:%s@%s:%s/%s' % ( |  | ||||||
|         dbtype, user, pw, host, port, dbname), **kw) |  | ||||||
| 
 | 
 | ||||||
| def sessionFactory(engine): |     def sessionFactory(self): | ||||||
|     Session = scoped_session(sessionmaker(bind=engine, twophase=True)) |          return self.engine.connect | ||||||
|     zope.sqlalchemy.register(Session) |  | ||||||
|     return Session |  | ||||||
| 
 | 
 | ||||||
| # put something like this in code before first creating a Storage object |     @staticmethod | ||||||
| #engine = getEngine('postgresql+psycopg', 'testdb', 'testuser', 'secret') |     def getEngine(dbtype, dbname, user, pw, host='localhost', port=5432, **kw): | ||||||
| #scopes.storage.common.engine = engine |         return create_engine('%s:///%s' % (dbtype, dbname), **kw) | ||||||
| #scopes.storage.common.Session = sessionFactory(engine) |  | ||||||
| 
 | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def mark_changed(session): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def commit(conn): | ||||||
|  |         conn.commit() | ||||||
|  | 
 | ||||||
|  |     IdType = Integer | ||||||
|  |     JsonType = JSON | ||||||
|  | 
 | ||||||
|  |     def __init__(self, config): | ||||||
|  |         self.engine = self.getEngine(config.dbengine, config.dbname,  | ||||||
|  |                                      config.dbuser, config.dbpassword)  | ||||||
|  |         self.Session = self.sessionFactory() | ||||||
|  | 
 | ||||||
|  |     def __call__(self, schema=None): | ||||||
|  |         return Storage(self, schema=schema) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # you may put something like this in your code: | ||||||
|  | #factory = StorageFactory(config) | ||||||
|  | # and then call at appropriate places: | ||||||
|  | #storage = scopes.storage.common.factory(schema=...) | ||||||
| 
 | 
 | ||||||
| class Storage(object): | class Storage(object): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, schema=None): |     def __init__(self, db, schema=None): | ||||||
|         self.engine = engine |         self.db = db | ||||||
|         self.session = Session() |         self.engine = db.engine | ||||||
|  |         self.session = db.Session() | ||||||
|         self.schema = schema |         self.schema = schema | ||||||
|         self.metadata = MetaData(schema=schema) |         self.metadata = MetaData(schema=schema) | ||||||
|         self.containers = {} |         self.containers = {} | ||||||
| 
 | 
 | ||||||
|  |     def commit(self): | ||||||
|  |         self.db.commit(self.session) | ||||||
|  | 
 | ||||||
|  |     def mark_changed(self): | ||||||
|  |         self.db.mark_changed(self.session) | ||||||
|  | 
 | ||||||
|     def create(self, cls): |     def create(self, cls): | ||||||
|         container = cls(self) |         container = cls(self) | ||||||
|         self.add(container) |         self.add(container) | ||||||
|  | @ -57,7 +83,8 @@ class Storage(object): | ||||||
| 
 | 
 | ||||||
|     def dropTable(self, tableName): |     def dropTable(self, tableName): | ||||||
|         with self.engine.begin() as conn: |         with self.engine.begin() as conn: | ||||||
|             conn.execute(text('drop table if exists %s.%s' % (self.schema, tableName))) |             prefix = self.schema and self.schema + '.' or '' | ||||||
|  |             conn.execute(text('drop table if exists %s%s' % (prefix, tableName))) | ||||||
| 
 | 
 | ||||||
|     def resetSequence(self, tableName, colName, v): |     def resetSequence(self, tableName, colName, v): | ||||||
|         sq = ('alter sequence %s.%s_%s_seq restart %i' %  |         sq = ('alter sequence %s.%s_%s_seq restart %i' %  | ||||||
|  | @ -71,8 +98,10 @@ class Storage(object): | ||||||
| registry = {} | registry = {} | ||||||
| 
 | 
 | ||||||
| def registerContainerClass(cls): | def registerContainerClass(cls): | ||||||
|     # TODO: error on duplicate key |     prefix = cls.itemFactory.prefix | ||||||
|     registry[cls.itemFactory.prefix] = cls |     if prefix in registry: | ||||||
|  |         raise ValueError("prefix '%s' already registered!" % prefix) | ||||||
|  |     registry[prefix] = cls | ||||||
|     cls.headCols = cols = tuple(f.lower() for f in cls.itemFactory.headFields) |     cls.headCols = cols = tuple(f.lower() for f in cls.itemFactory.headFields) | ||||||
|     if cls.indexes is None: |     if cls.indexes is None: | ||||||
|         cls.indexes = [cols[i:] for i in range(len(cols))] |         cls.indexes = [cols[i:] for i in range(len(cols))] | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								scopes/storage/concept.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								scopes/storage/concept.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | # scopes.storage.concept | ||||||
|  | 
 | ||||||
|  | """Abstract base classes for concept map application classes.""" | ||||||
|  | 
 | ||||||
|  | from scopes.storage.common import registerContainerClass | ||||||
|  | from scopes.storage.tracking import Container, Track | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Concept(Track): | ||||||
|  | 
 | ||||||
|  |     headFields = ['name'] | ||||||
|  |   | ||||||
							
								
								
									
										1
									
								
								scopes/storage/db/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								scopes/storage/db/__init__.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | """scopes.storage.db""" | ||||||
							
								
								
									
										37
									
								
								scopes/storage/db/postgres.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								scopes/storage/db/postgres.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | ||||||
|  | # scopes.storage.db.postgres | ||||||
|  | 
 | ||||||
|  | """Database-related code specific for PostgreSQL.""" | ||||||
|  | 
 | ||||||
|  | from sqlalchemy import create_engine | ||||||
|  | from sqlalchemy import BigInteger | ||||||
|  | from sqlalchemy.dialects.postgresql import JSONB | ||||||
|  | from sqlalchemy.orm import scoped_session, sessionmaker | ||||||
|  | import transaction | ||||||
|  | from zope.sqlalchemy import register, mark_changed | ||||||
|  | 
 | ||||||
|  | from scopes.storage.common import StorageFactory | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class StorageFactory(StorageFactory): | ||||||
|  | 
 | ||||||
|  |     def sessionFactory(self): | ||||||
|  |         Session = scoped_session(sessionmaker(bind=self.engine, twophase=True)) | ||||||
|  |         register(Session) | ||||||
|  |         return Session | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def getEngine(dbtype, dbname, user, pw, host='localhost', port=5432, **kw): | ||||||
|  |         return create_engine('%s://%s:%s@%s:%s/%s' % ( | ||||||
|  |             dbtype, user, pw, host, port, dbname), **kw) | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def mark_changed(session): | ||||||
|  |         return mark_changed(session) | ||||||
|  | 
 | ||||||
|  |     @staticmethod | ||||||
|  |     def commit(conn): | ||||||
|  |         transaction.commit() | ||||||
|  | 
 | ||||||
|  |     IdType = BigInteger | ||||||
|  |     JsonType = JSONB | ||||||
|  | 
 | ||||||
							
								
								
									
										51
									
								
								scopes/storage/folder.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								scopes/storage/folder.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | ||||||
|  | # scopes.storage.folder | ||||||
|  | 
 | ||||||
|  | from scopes.storage.common import registerContainerClass | ||||||
|  | from scopes.storage.tracking import Container, Track | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Folder(Track): | ||||||
|  | 
 | ||||||
|  |     headFields = ['parent', 'name', 'ref'] | ||||||
|  |     prefix = 'fldr' | ||||||
|  | 
 | ||||||
|  |     def keys(self): | ||||||
|  |         for f in self.container.query(parent=self.rid): | ||||||
|  |             yield f.name | ||||||
|  | 
 | ||||||
|  |     def get(self, key, default=None): | ||||||
|  |         value = self.container.queryLast(parent=self.rid, name=key) | ||||||
|  |         if value is None: | ||||||
|  |             return default | ||||||
|  |         return value | ||||||
|  | 
 | ||||||
|  |     def __getitem__(self, key): | ||||||
|  |         value = self.container.queryLast(parent=self.rid, name=key) | ||||||
|  |         if value is None: | ||||||
|  |             raise KeyError(key) | ||||||
|  |         return value | ||||||
|  | 
 | ||||||
|  |     def __setitem__(self, key, value): | ||||||
|  |         value.set('parent', self.rid) | ||||||
|  |         value.set('name', key) | ||||||
|  |         self.container.save(value) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Root(Folder): | ||||||
|  |     """A dummy (virtual) root folder for creating real folders | ||||||
|  |        using the Folder API.""" | ||||||
|  | 
 | ||||||
|  |     def __init__(self, storage): | ||||||
|  |         cont = storage.create(Folders) | ||||||
|  |         super(Root, self).__init__(container=cont) | ||||||
|  | 
 | ||||||
|  |     uid = '' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @registerContainerClass | ||||||
|  | class Folders(Container): | ||||||
|  | 
 | ||||||
|  |     itemFactory = Folder | ||||||
|  |     indexes = [('parent', 'name'), ('ref',)] | ||||||
|  |     tableName = 'folders' | ||||||
|  |     insertOnChange = False | ||||||
							
								
								
									
										13
									
								
								scopes/storage/relation.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								scopes/storage/relation.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | # scopes.storage.relation | ||||||
|  | 
 | ||||||
|  | """An SQL-based relationship engine using RDF-like triples.""" | ||||||
|  | 
 | ||||||
|  | from scopes.storage.common import registerContainerClass | ||||||
|  | from scopes.storage.tracking import Container, Track | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Triple(Track): | ||||||
|  | 
 | ||||||
|  |     headFields = ['first', 'second', 'pred'] | ||||||
|  |     prefix = 'rel' | ||||||
|  |   | ||||||
|  | @ -9,11 +9,8 @@ data (payload) represented as a dict. | ||||||
| import base64 | import base64 | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from sqlalchemy import Table, Column, Index | from sqlalchemy import Table, Column, Index | ||||||
| from sqlalchemy import BigInteger, DateTime, Text, func | from sqlalchemy import DateTime, Text, func | ||||||
| from sqlalchemy import and_ | from sqlalchemy import and_ | ||||||
| from sqlalchemy.dialects.postgresql import JSONB |  | ||||||
| import transaction |  | ||||||
| from zope.sqlalchemy import register, mark_changed |  | ||||||
| 
 | 
 | ||||||
| from scopes.storage.common import registerContainerClass | from scopes.storage.common import registerContainerClass | ||||||
| 
 | 
 | ||||||
|  | @ -36,6 +33,15 @@ class Track(object): | ||||||
|         self.trackId = kw.get('trackId') |         self.trackId = kw.get('trackId') | ||||||
|         self.container = kw.get('container') |         self.container = kw.get('container') | ||||||
| 
 | 
 | ||||||
|  |     def set(self, attr, value): | ||||||
|  |         if attr in self.headFields: | ||||||
|  |             if value is None: | ||||||
|  |                 value = '' | ||||||
|  |             self.head[attr] = value | ||||||
|  |             setattr(self, attr, value) | ||||||
|  |         else: | ||||||
|  |             raise AttributeError(attr) | ||||||
|  | 
 | ||||||
|     def update(self, data, overwrite=False): |     def update(self, data, overwrite=False): | ||||||
|         if data is None: |         if data is None: | ||||||
|             return |             return | ||||||
|  | @ -50,6 +56,12 @@ class Track(object): | ||||||
|             return None |             return None | ||||||
|         return '%s-%d' % (self.prefix, self.trackId) |         return '%s-%d' % (self.prefix, self.trackId) | ||||||
| 
 | 
 | ||||||
|  |     @property | ||||||
|  |     def rid(self): | ||||||
|  |         if self.trackId is None: | ||||||
|  |             return '' | ||||||
|  |         return str(self.trackId) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| @registerContainerClass | @registerContainerClass | ||||||
| class Container(object): | class Container(object): | ||||||
|  | @ -64,6 +76,7 @@ class Container(object): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, storage): |     def __init__(self, storage): | ||||||
|         self.storage = storage |         self.storage = storage | ||||||
|  |         self.db = storage.db | ||||||
|         self.session = storage.session |         self.session = storage.session | ||||||
|         self.table = self.getTable() |         self.table = self.getTable() | ||||||
| 
 | 
 | ||||||
|  | @ -100,7 +113,7 @@ class Container(object): | ||||||
|         values = self.setupValues(track, withTrackId) |         values = self.setupValues(track, withTrackId) | ||||||
|         stmt = t.insert().values(**values).returning(t.c.trackid) |         stmt = t.insert().values(**values).returning(t.c.trackid) | ||||||
|         trackId = self.session.execute(stmt).first()[0] |         trackId = self.session.execute(stmt).first()[0] | ||||||
|         mark_changed(self.session) |         self.storage.mark_changed() | ||||||
|         return trackId |         return trackId | ||||||
| 
 | 
 | ||||||
|     def update(self, track): |     def update(self, track): | ||||||
|  | @ -111,7 +124,7 @@ class Container(object): | ||||||
|         stmt = t.update().values(**values).where(t.c.trackid == track.trackId) |         stmt = t.update().values(**values).where(t.c.trackid == track.trackId) | ||||||
|         n = self.session.execute(stmt).rowcount |         n = self.session.execute(stmt).rowcount | ||||||
|         if n > 0: |         if n > 0: | ||||||
|             mark_changed(self.session) |             self.storage.mark_changed() | ||||||
|         return n |         return n | ||||||
| 
 | 
 | ||||||
|     def upsert(self, track): |     def upsert(self, track): | ||||||
|  | @ -129,7 +142,7 @@ class Container(object): | ||||||
|         stmt = self.table.delete().where(self.table.c.trackid == trackId) |         stmt = self.table.delete().where(self.table.c.trackid == trackId) | ||||||
|         n = self.session.execute(stmt).rowcount |         n = self.session.execute(stmt).rowcount | ||||||
|         if n > 0: |         if n > 0: | ||||||
|             mark_changed(self.session) |             self.storage.mark_changed() | ||||||
|         return n |         return n | ||||||
| 
 | 
 | ||||||
|     def makeTrack(self, r): |     def makeTrack(self, r): | ||||||
|  | @ -162,7 +175,7 @@ class Container(object): | ||||||
| 
 | 
 | ||||||
| def createTable(storage, tableName, headcols, indexes=None): | def createTable(storage, tableName, headcols, indexes=None): | ||||||
|     metadata = storage.metadata |     metadata = storage.metadata | ||||||
|     cols = [Column('trackid', BigInteger, primary_key=True)] |     cols = [Column('trackid', storage.db.IdType, primary_key=True)] | ||||||
|     idxs = [] |     idxs = [] | ||||||
|     for ix, f in enumerate(headcols): |     for ix, f in enumerate(headcols): | ||||||
|         cols.append(Column(f.lower(), Text, nullable=False, server_default='')) |         cols.append(Column(f.lower(), Text, nullable=False, server_default='')) | ||||||
|  | @ -172,7 +185,7 @@ def createTable(storage, tableName, headcols, indexes=None): | ||||||
|         indexName = 'idx_%s_%d' % (tableName, (ix + 1)) |         indexName = 'idx_%s_%d' % (tableName, (ix + 1)) | ||||||
|         idxs.append(Index(indexName, *idef)) |         idxs.append(Index(indexName, *idef)) | ||||||
|     idxs.append(Index('idx_%s_ts' % tableName, 'timestamp')) |     idxs.append(Index('idx_%s_ts' % tableName, 'timestamp')) | ||||||
|     cols.append(Column('data', JSONB, nullable=False, server_default='{}')) |     cols.append(Column('data', storage.db.JsonType, nullable=False, server_default='{}')) | ||||||
|     table = Table(tableName, metadata, *(cols+idxs), extend_existing=True) |     table = Table(tableName, metadata, *(cols+idxs), extend_existing=True) | ||||||
|     metadata.create_all(storage.engine) |     metadata.create_all(storage.engine) | ||||||
|     return table |     return table | ||||||
|  |  | ||||||
|  | @ -1,27 +1,28 @@ | ||||||
| #! /usr/bin/python | """The real test implementations""" | ||||||
| 
 |  | ||||||
| """Tests for the 'scopes.storage' package.""" |  | ||||||
| 
 | 
 | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| import transaction |  | ||||||
| import unittest | import unittest | ||||||
| 
 | 
 | ||||||
| import scopes.storage.common | from scopes.storage import folder, tracking | ||||||
| from scopes.storage.common import Storage, getEngine, sessionFactory |  | ||||||
| from scopes.storage import proxy |  | ||||||
| from scopes.storage import tracking |  | ||||||
| 
 | 
 | ||||||
| engine = getEngine('postgresql', 'ccotest', 'ccotest', 'cco') | #import config | ||||||
| scopes.storage.common.engine = engine | class Config(object): pass | ||||||
| scopes.storage.common.Session = sessionFactory(engine) |  | ||||||
| 
 | 
 | ||||||
| storage = Storage(schema='testing') | config = Config() | ||||||
|  | config.dbengine = 'postgresql' | ||||||
|  | config.dbname = 'ccotest' | ||||||
|  | config.dbuser = 'ccotest' | ||||||
|  | config.dbpassword = 'cco' | ||||||
|  | 
 | ||||||
|  | # PostgreSQL-specific settings | ||||||
|  | from scopes.storage.db.postgres import StorageFactory  | ||||||
|  | factory = StorageFactory(config) | ||||||
|  | storage = factory(schema='testing') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Test(unittest.TestCase): | class Test(unittest.TestCase): | ||||||
|     "Basic tests for the cco.storage package." |  | ||||||
| 
 | 
 | ||||||
|     def testBasicStuff(self): |     def test_001_tracking(self): | ||||||
|         storage.dropTable('tracks') |         storage.dropTable('tracks') | ||||||
|         tracks = storage.create(tracking.Container) |         tracks = storage.create(tracking.Container) | ||||||
| 
 | 
 | ||||||
|  | @ -67,7 +68,22 @@ class Test(unittest.TestCase): | ||||||
|         self.assertEqual(n, 1) |         self.assertEqual(n, 1) | ||||||
|         self.assertEqual(tracks.get(31), None) |         self.assertEqual(tracks.get(31), None) | ||||||
| 
 | 
 | ||||||
|         transaction.commit() |         storage.commit() | ||||||
|  | 
 | ||||||
|  |     def test_002_folder(self): | ||||||
|  |         storage.dropTable('folders') | ||||||
|  |         root = folder.Root(storage) | ||||||
|  |         self.assertEqual(list(root.keys()), []) | ||||||
|  |         root['top'] = folder.Folder() | ||||||
|  |         self.assertEqual(list(root.keys()), ['top']) | ||||||
|  |         top = root['top'] | ||||||
|  |         top['child1'] = folder.Folder(data=dict(title='First Child')) | ||||||
|  |         self.assertEqual(list(top.keys()), ['child1']) | ||||||
|  |         ch1 = top['child1'] | ||||||
|  |         self.assertEqual(ch1.parent, top.rid) | ||||||
|  |         self.assertEqual(list(top.keys()), ['child1']) | ||||||
|  | 
 | ||||||
|  |         storage.commit() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def suite(): | def suite(): | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue