merge branch master, + fix tests
This commit is contained in:
		
						commit
						f100a18f22
					
				
					 10 changed files with 414 additions and 70 deletions
				
			
		
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -7,6 +7,8 @@ | ||||||
| *.sublime-project | *.sublime-project | ||||||
| *.sublime-workspace | *.sublime-workspace | ||||||
| *.ropeproject | *.ropeproject | ||||||
|  | .env | ||||||
|  | .pytest.ini | ||||||
| *#*# | *#*# | ||||||
| *.#* | *.#* | ||||||
| __pycache__ | __pycache__ | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								scopes/interfaces.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								scopes/interfaces.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | # scopes.interfaces | ||||||
|  | 
 | ||||||
|  | from zope.interface import Interface | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class ITraversable(Interface): | ||||||
|  | 
 | ||||||
|  |     def get(key, default=None): | ||||||
|  |         """Return the item addressed by `key`; return `default` if not found.""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IContainer(ITraversable): | ||||||
|  | 
 | ||||||
|  |     def values(): | ||||||
|  |         """Return a sequence of child objects.""" | ||||||
|  | 
 | ||||||
|  |     def __getitem__(key): | ||||||
|  |         """Return the item addressed by `key`; raise KeyError if not found.""" | ||||||
|  | 
 | ||||||
|  |     def __setitem__(key, value): | ||||||
|  |         """Store the `value` under the `key`.  | ||||||
|  | 
 | ||||||
|  |         May modify `value` so that the attributes referencing this object | ||||||
|  |         and the value object (e.g. `parent` and `name`) are stored correctly.""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IReference(Interface): | ||||||
|  | 
 | ||||||
|  |     def getTarget(): | ||||||
|  |         """Return item referenced by this object.""" | ||||||
|  | 
 | ||||||
|  |     def setTarget(target): | ||||||
|  |         """Store reference to target item.""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class IView(Interface): | ||||||
|  | 
 | ||||||
|  |     def __call__(): | ||||||
|  |         """Render the view data as HTML or JSON.""" | ||||||
|  | 
 | ||||||
|  | @ -2,43 +2,49 @@ | ||||||
| 
 | 
 | ||||||
| from zope.publisher.base import DefaultPublication | from zope.publisher.base import DefaultPublication | ||||||
| from zope.publisher.browser import BrowserRequest | from zope.publisher.browser import BrowserRequest | ||||||
|  | from zope.publisher.interfaces import NotFound | ||||||
| from zope.publisher.publish import publish | from zope.publisher.publish import publish | ||||||
| from zope.traversing.publicationtraverse import PublicationTraverser | 
 | ||||||
|  | from scopes.interfaces import ITraversable, IView | ||||||
|  | from scopes.server.browser import getView | ||||||
|  | import scopes.storage.concept # register container classes | ||||||
|  | from scopes.storage.folder import Root | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def demo_app(environ, start_response): | def zope_app_factory(config): | ||||||
|     print(f'*** environ {environ}.') |     storageFactory = config.StorageFactory(config) | ||||||
|     status = '200 OK' |     def zope_app(environ, start_response): | ||||||
|     headers = [("Content-type", "text/plain; charset=utf-8")] |         storage = storageFactory(config.dbschema) | ||||||
|     start_response(status, headers) |         appRoot = Root(storage) | ||||||
|     return ['Hello World'.encode()] |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def zope_app(environ, start_response): |  | ||||||
|         request = BrowserRequest(environ['wsgi.input'], environ) |         request = BrowserRequest(environ['wsgi.input'], environ) | ||||||
|     request.setPublication(DefaultPublication(AppRoot())) |         request.setPublication(Publication(appRoot)) | ||||||
|     request = publish(request, False) |         request = publish(request, True) | ||||||
|         response = request.response |         response = request.response | ||||||
|         start_response(response.getStatusString(), response.getHeaders()) |         start_response(response.getStatusString(), response.getHeaders()) | ||||||
|         return response.consumeBodyIter() |         return response.consumeBodyIter() | ||||||
|  |     return zope_app | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AppRoot: | class Publication(DefaultPublication): | ||||||
|     """Zope Demo AppRoot""" |  | ||||||
| 
 | 
 | ||||||
|     def __call__(self): |     def traverseName(self, request, ob, name): | ||||||
|         """calling AppRoot""" |         next = getView(request, ob, name) | ||||||
|         return 'At root' |         if next is not None: | ||||||
|  |             return next | ||||||
|  |         if ITraversable.providedBy(ob): | ||||||
|  |             next = ob.get(name) | ||||||
|  |         if next is None: | ||||||
|  |             raise NotFound(ob, name, request) | ||||||
|  |         return next | ||||||
| 
 | 
 | ||||||
|     def __getitem__(self, key): |     def getDefaultTraversal(self, request, ob): | ||||||
|         def child(): |         if IView.providedBy(ob): | ||||||
|             """get child""" |             return ob, () | ||||||
|             print(f'--- getitem {key}') |         return ob, ('index.html',) | ||||||
|             return 'getitem' |  | ||||||
|         return child |  | ||||||
| 
 |  | ||||||
|     def hello(self):  |  | ||||||
|         """method hello""" |  | ||||||
|         return 'Hello AppRoot' |  | ||||||
| 
 | 
 | ||||||
|  |     def handleException(self, ob, request, exc_info, retry_allowed=True): | ||||||
|  |         if exc_info[0] != NotFound: | ||||||
|  |             raise | ||||||
|  |         request.response.reset() | ||||||
|  |         request.response.handleException(exc_info) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										63
									
								
								scopes/server/browser.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								scopes/server/browser.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,63 @@ | ||||||
|  | # scopes.server.browser | ||||||
|  | 
 | ||||||
|  | import json | ||||||
|  | from zope.interface import implementer | ||||||
|  | from scopes.interfaces import IContainer, IReference, IView | ||||||
|  | 
 | ||||||
|  | views = {} # registry for all views: {name: {prefix: viewClass, ...}, ...} | ||||||
|  | 
 | ||||||
|  | def register(name, *contextClasses): | ||||||
|  |     """Use as decorator: `@register(name, class, ...).  | ||||||
|  |        class `None` means default view for all classes.""" | ||||||
|  |     def doRegister(factory): | ||||||
|  |         implementer(IView)(factory) | ||||||
|  |         nameEntry = views.setdefault(name, {}) | ||||||
|  |         for cl in contextClasses: | ||||||
|  |             nameEntry[cl.prefix] = factory | ||||||
|  |         else: | ||||||
|  |             nameEntry[''] = factory | ||||||
|  |         return factory | ||||||
|  |     return doRegister | ||||||
|  | 
 | ||||||
|  | def getView(request, ob, name): | ||||||
|  |     nameEntry = views.get(name) | ||||||
|  |     if nameEntry is None: | ||||||
|  |         return None | ||||||
|  |     factory = nameEntry.get(ob.prefix) | ||||||
|  |     if factory is None: | ||||||
|  |         factory = nameEntry.get('') | ||||||
|  |     if factory is None: | ||||||
|  |         return None | ||||||
|  |     return factory(ob, request) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @register('index.html') | ||||||
|  | @register('index.json') | ||||||
|  | class DefaultView: | ||||||
|  | 
 | ||||||
|  |     def __init__(self, context, request): | ||||||
|  |         self.context = context | ||||||
|  |         self.request = request | ||||||
|  | 
 | ||||||
|  |     def __call__(self): | ||||||
|  |         result = self.prepareResult() | ||||||
|  |         return self.render(result) | ||||||
|  | 
 | ||||||
|  |     def prepareResult(self): | ||||||
|  |         ob = self.context | ||||||
|  |         result = ob.asDict() | ||||||
|  |         if IContainer.providedBy(ob): | ||||||
|  |             result['items'] = [v.asDict() for v in ob.values()] | ||||||
|  |         if IReference.providedBy(ob): | ||||||
|  |             target = ob.getTarget() | ||||||
|  |             if target: | ||||||
|  |                 result['target'] = target.asDict() | ||||||
|  |                 if IContainer.providedBy(target): | ||||||
|  |                     result['target']['items'] = [v.asDict() for v in target.values()] | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
|  |     def render(self, result): | ||||||
|  |         self.request.response.setHeader('Content-type', 'application/json; charset=utf-8') | ||||||
|  |         return json.dumps(result).encode('UTF-8') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -33,13 +33,17 @@ class Storage(object): | ||||||
|     def add(self, container): |     def add(self, container): | ||||||
|         self.containers[container.itemFactory.prefix] = container |         self.containers[container.itemFactory.prefix] = container | ||||||
| 
 | 
 | ||||||
|     def getItem(self, uid): |     def getContainer(self, itemClass): | ||||||
|         prefix, id = uid.split('-') |         prefix = itemClass.prefix | ||||||
|         id = int(id) |  | ||||||
|         container = self.containers.get(prefix) |         container = self.containers.get(prefix) | ||||||
|         if container is None: |         if container is None: | ||||||
|             container = self.create(registry[prefix]) |             return self.create(registry[prefix]) | ||||||
|         return container.get(id) |         return container | ||||||
|  | 
 | ||||||
|  |     def getItem(self, uid): | ||||||
|  |         prefix, id = uid.split('-') | ||||||
|  |         cls = registry[prefix].itemFactory | ||||||
|  |         return self.getContainer(cls).get(int(id)) | ||||||
| 
 | 
 | ||||||
|     def getExistingTable(self, tableName): |     def getExistingTable(self, tableName): | ||||||
|         metadata = self.metadata |         metadata = self.metadata | ||||||
|  |  | ||||||
|  | @ -1,12 +1,145 @@ | ||||||
| # scopes.storage.concept | # scopes.storage.concept | ||||||
| 
 | 
 | ||||||
| """Abstract base classes for concept map application classes.""" | """Core classes for concept map structure.""" | ||||||
| 
 | 
 | ||||||
| from scopes.storage.common import registerContainerClass | from zope.interface import implementer | ||||||
|  | from scopes.interfaces import IContainer | ||||||
|  | from scopes.storage.common import registerContainerClass, registry | ||||||
| from scopes.storage.tracking import Container, Track | from scopes.storage.tracking import Container, Track | ||||||
| 
 | 
 | ||||||
|  | defaultPredicate = 'standard' | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class Concept(Track): | class Concept(Track): | ||||||
| 
 | 
 | ||||||
|     headFields = ['name'] |     headFields = ['name'] | ||||||
| 
 | 
 | ||||||
|  |     def parents(self, predicate=None): | ||||||
|  |         return (r.getFirst() for r in self.parentRels(predicate)) | ||||||
|  | 
 | ||||||
|  |     def parentRels(self, predicate=None): | ||||||
|  |         return self.container.queryRels(second=self, predicate=predicate) | ||||||
|  | 
 | ||||||
|  |     def children(self, predicate=None): | ||||||
|  |         return (r.getSecond() for r in self.childRels(predicate)) | ||||||
|  | 
 | ||||||
|  |     def childRels(self, predicate=None): | ||||||
|  |         return self.container.queryRels(first=self, predicate=predicate) | ||||||
|  | 
 | ||||||
|  |     def values(self): | ||||||
|  |         return self.children(defaultPredicate) | ||||||
|  | 
 | ||||||
|  |     def addChild(self, child, predicate=defaultPredicate): | ||||||
|  |         rels = self.container.storage.getContainer(Triple) | ||||||
|  |         rels.save(Triple(self.uid, child.uid, predicate)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Concepts(Container): | ||||||
|  | 
 | ||||||
|  |     insertOnChange = False | ||||||
|  |     indexes = None | ||||||
|  | 
 | ||||||
|  |     def queryRels(self, **crit): | ||||||
|  |         #pred = crit.get(predicate) | ||||||
|  |         #if pred is not None and isinstance(pred, ('string', 'bytes')): | ||||||
|  |         #    crit['predicate'] = self.storage.getContainer(Predicate).queryLast(name=pred) | ||||||
|  |         for k, v in crit.items(): | ||||||
|  |             if isinstance(v, Track): | ||||||
|  |                 crit[k] = v.uid | ||||||
|  |         rels = self.storage.getContainer(Triple) | ||||||
|  |         return rels.query(**crit) | ||||||
|  |   | ||||||
|  | 
 | ||||||
|  |  # implementation of relationships between concepts using RDF-like triples | ||||||
|  | 
 | ||||||
|  | class Predicate(Concept): | ||||||
|  | 
 | ||||||
|  |     prefix = 'pred' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @registerContainerClass | ||||||
|  | class Predicates(Concepts): | ||||||
|  | 
 | ||||||
|  |     itemFactory = Predicate | ||||||
|  |     tableName = 'preds' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def storePredicate(storage, name): | ||||||
|  |     preds = storage.getContainer(Predicate) | ||||||
|  |     preds.save(Predicate(name)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class Triple(Track): | ||||||
|  | 
 | ||||||
|  |     headFields = ['first', 'second', 'predicate'] | ||||||
|  |     prefix = 'rel' | ||||||
|  | 
 | ||||||
|  |     def getFirst(self): | ||||||
|  |         return self.container.storage.getItem(self.first) | ||||||
|  | 
 | ||||||
|  |     def getSecond(self): | ||||||
|  |         return self.container.storage.getItem(self.second) | ||||||
|  | 
 | ||||||
|  |     def getPredicate(self): | ||||||
|  |         return self.container.storage.getItem(self.second) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @registerContainerClass | ||||||
|  | class Rels(Container): | ||||||
|  | 
 | ||||||
|  |     itemFactory = Triple | ||||||
|  |     indexes = [('first', 'predicate', 'second'), | ||||||
|  |                ('first', 'second'), ('predicate', 'second')] | ||||||
|  |     tableName = 'rels' | ||||||
|  |     insertOnChange = False | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # types stuff | ||||||
|  | 
 | ||||||
|  | @implementer(IContainer) | ||||||
|  | class Type(Concept): | ||||||
|  | 
 | ||||||
|  |     headFields = ['name', 'tprefix'] | ||||||
|  |     prefix = 'type' | ||||||
|  | 
 | ||||||
|  |     @property | ||||||
|  |     def typeClass(self): | ||||||
|  |         return registry[self.tprefix].itemFactory | ||||||
|  | 
 | ||||||
|  |     def values(self): | ||||||
|  |         cont = self.container.storage.getContainer(self.typeClass) | ||||||
|  |         return cont.query() | ||||||
|  | 
 | ||||||
|  |     def get(self, key, default=None): | ||||||
|  |         cont = self.container.storage.getContainer(self.typeClass) | ||||||
|  |         return cont.queryLast(name=key) or default | ||||||
|  | 
 | ||||||
|  |     def __getitem__(self, key): | ||||||
|  |         value = self.get(key) | ||||||
|  |         if value is None: | ||||||
|  |             raise KeyError(key) | ||||||
|  |         return value | ||||||
|  | 
 | ||||||
|  |     def __setitem__(self, key, value): | ||||||
|  |         cont = self.container.storage.getContainer(self.typeClass) | ||||||
|  |         value.name = key | ||||||
|  |         cont.save(value) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @registerContainerClass | ||||||
|  | class Types(Concepts): | ||||||
|  | 
 | ||||||
|  |     itemFactory = Type | ||||||
|  |     indexes = [('name',), ('tprefix',)] | ||||||
|  |     tableName = 'types' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def storeType(storage, cls, name): | ||||||
|  |     types = storage.getContainer(Type) | ||||||
|  |     types.save(Type(name, cls.prefix)) | ||||||
|  | 
 | ||||||
|  | def setupCoreTypes(storage): | ||||||
|  |     for c in registry.values(): | ||||||
|  |         cls = c.itemFactory | ||||||
|  |         storeType(storage, cls, cls.__name__.lower()) | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -1,17 +1,26 @@ | ||||||
| # scopes.storage.folder | # scopes.storage.folder | ||||||
| 
 | 
 | ||||||
|  | from zope.interface import implementer | ||||||
|  | 
 | ||||||
|  | from scopes.interfaces import IContainer, IReference | ||||||
| from scopes.storage.common import registerContainerClass | from scopes.storage.common import registerContainerClass | ||||||
| from scopes.storage.tracking import Container, Track | from scopes.storage.tracking import Container, Track | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @implementer(IContainer, IReference) | ||||||
| class Folder(Track): | class Folder(Track): | ||||||
| 
 | 
 | ||||||
|     headFields = ['parent', 'name', 'ref'] |     headFields = ['parent', 'name', 'ref'] | ||||||
|     prefix = 'fldr' |     prefix = 'fldr' | ||||||
| 
 | 
 | ||||||
|  |     def values(self): | ||||||
|  |         return self.container.query(parent=self.rid) | ||||||
|  | 
 | ||||||
|  |     def items(self): | ||||||
|  |         return ((v.name, v) for v in self.values()) | ||||||
|  | 
 | ||||||
|     def keys(self): |     def keys(self): | ||||||
|         for f in self.container.query(parent=self.rid): |         return (v.name for v in self.values()) | ||||||
|             yield f.name |  | ||||||
| 
 | 
 | ||||||
|     def get(self, key, default=None): |     def get(self, key, default=None): | ||||||
|         value = self.container.queryLast(parent=self.rid, name=key) |         value = self.container.queryLast(parent=self.rid, name=key) | ||||||
|  | @ -20,7 +29,7 @@ class Folder(Track): | ||||||
|         return value |         return value | ||||||
| 
 | 
 | ||||||
|     def __getitem__(self, key): |     def __getitem__(self, key): | ||||||
|         value = self.container.queryLast(parent=self.rid, name=key) |         value = self.get(key) | ||||||
|         if value is None: |         if value is None: | ||||||
|             raise KeyError(key) |             raise KeyError(key) | ||||||
|         return value |         return value | ||||||
|  | @ -30,6 +39,19 @@ class Folder(Track): | ||||||
|         value.set('name', key) |         value.set('name', key) | ||||||
|         self.container.save(value) |         self.container.save(value) | ||||||
| 
 | 
 | ||||||
|  |     def getTarget(self): | ||||||
|  |         if self.ref == '': | ||||||
|  |             return None | ||||||
|  |         return self.container.storage.getItem(self.ref) | ||||||
|  | 
 | ||||||
|  |     def setTarget(self, target): | ||||||
|  |         self.set('ref', target.uid) | ||||||
|  |         self.container.update(self) | ||||||
|  | 
 | ||||||
|  |     def __str__(self): | ||||||
|  |         return '%s: %s; keys: %s' % (self.__class__.__name__, | ||||||
|  |                 self.name, list(self.keys())) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class Root(Folder): | class Root(Folder): | ||||||
|     """A dummy (virtual) root folder for creating real folders |     """A dummy (virtual) root folder for creating real folders | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								scopes/storage/topic.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								scopes/storage/topic.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | # scopes.storage.topic | ||||||
|  | 
 | ||||||
|  | from scopes.storage.common import registerContainerClass | ||||||
|  | from scopes.storage import concept | ||||||
|  | 
 | ||||||
|  | class Topic(concept.Concept): | ||||||
|  | 
 | ||||||
|  |     prefix = 'tpc' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @registerContainerClass | ||||||
|  | class Topics(concept.Concepts): | ||||||
|  | 
 | ||||||
|  |     itemFactory = Topic | ||||||
|  |     tableName = 'topics' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @ -22,6 +22,9 @@ class Track(object): | ||||||
| 
 | 
 | ||||||
|     def __init__(self, *keys, **kw): |     def __init__(self, *keys, **kw): | ||||||
|         self.head = {} |         self.head = {} | ||||||
|  |         for k, v in kw.items(): | ||||||
|  |             if k in self.headFields: | ||||||
|  |                 self.head[k] = kw.pop(k) | ||||||
|         for ix, k in enumerate(keys): |         for ix, k in enumerate(keys): | ||||||
|             self.head[self.headFields[ix]] = k |             self.head[self.headFields[ix]] = k | ||||||
|         for k in self.headFields: |         for k in self.headFields: | ||||||
|  | @ -62,6 +65,13 @@ class Track(object): | ||||||
|             return '' |             return '' | ||||||
|         return str(self.trackId) |         return str(self.trackId) | ||||||
| 
 | 
 | ||||||
|  |     def __repr__(self): | ||||||
|  |         return '%s: %s' % (self.__class__.__name__, self.asDict()) | ||||||
|  | 
 | ||||||
|  |     def asDict(self): | ||||||
|  |         return dict(uid=self.uid, head=self.head, data=self.data,  | ||||||
|  |                     timeStamp = str(self.timeStamp)[:19]) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| @registerContainerClass | @registerContainerClass | ||||||
| class Container(object): | class Container(object): | ||||||
|  | @ -91,8 +101,11 @@ class Container(object): | ||||||
|         return tr |         return tr | ||||||
| 
 | 
 | ||||||
|     def query(self, **crit): |     def query(self, **crit): | ||||||
|  |         if crit: | ||||||
|             stmt = self.table.select().where( |             stmt = self.table.select().where( | ||||||
|                 and_(*self.setupWhere(crit))).order_by(self.table.c.trackid) |                 and_(*self.setupWhere(crit))).order_by(self.table.c.trackid) | ||||||
|  |         else: | ||||||
|  |             stmt = self.table.select().order_by(self.table.c.trackid) | ||||||
|         for r in self.session.execute(stmt): |         for r in self.session.execute(stmt): | ||||||
|             yield self.makeTrack(r) |             yield self.makeTrack(r) | ||||||
| 
 | 
 | ||||||
|  | @ -102,16 +115,23 @@ class Container(object): | ||||||
|         return self.makeTrack(self.session.execute(stmt).first()) |         return self.makeTrack(self.session.execute(stmt).first()) | ||||||
| 
 | 
 | ||||||
|     def save(self, track): |     def save(self, track): | ||||||
|  |         track.container = self | ||||||
|         crit = dict((hf, track.head[hf]) for hf in track.headFields) |         crit = dict((hf, track.head[hf]) for hf in track.headFields) | ||||||
|         found = self.queryLast(**crit) |         found = self.queryLast(**crit) | ||||||
|         if found is None: |         if found is None: | ||||||
|             return self.insert(track) |             return self.insert(track) | ||||||
|         if self.insertOnChange and found.data != track.data: |         if self.insertOnChange and found.data != track.data: | ||||||
|             return self.insert(track) |             return self.insert(track) | ||||||
|         if found.data != track.data or found.timeStamp != track.timeStamp: |         changed = False | ||||||
|  |         if found.data != track.data: | ||||||
|             found.update(track.data) |             found.update(track.data) | ||||||
|  |             changed = True | ||||||
|  |         if track.timeStamp is not None and found.timeStamp != track.timeStamp: | ||||||
|             found.timeStamp = track.timeStamp |             found.timeStamp = track.timeStamp | ||||||
|  |             changed = True | ||||||
|  |         if changed: | ||||||
|             self.update(found) |             self.update(found) | ||||||
|  |         track.trackId = found.trackId | ||||||
|         return found.trackId |         return found.trackId | ||||||
| 
 | 
 | ||||||
|     def insert(self, track, withTrackId=False): |     def insert(self, track, withTrackId=False): | ||||||
|  | @ -119,14 +139,15 @@ 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] | ||||||
|  |         track.trackId = trackId | ||||||
|         self.storage.mark_changed() |         self.storage.mark_changed() | ||||||
|         return trackId |         return trackId | ||||||
| 
 | 
 | ||||||
|     def update(self, track): |     def update(self, track, updateTimeStamp=False): | ||||||
|         t = self.table |         t = self.table | ||||||
|  |         if updateTimeStamp or track.timeStamp is None: | ||||||
|  |             track.timeStamp = datetime.now() | ||||||
|         values = self.setupValues(track) |         values = self.setupValues(track) | ||||||
|         if track.timeStamp is None: |  | ||||||
|             values['timestamp'] = datetime.now() |  | ||||||
|         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: | ||||||
|  | @ -158,7 +179,7 @@ class Container(object): | ||||||
|                 *r[1:-2], trackId=r[0],timeStamp=r[-2], data=r[-1], container=self) |                 *r[1:-2], trackId=r[0],timeStamp=r[-2], data=r[-1], container=self) | ||||||
| 
 | 
 | ||||||
|     def setupWhere(self, crit): |     def setupWhere(self, crit): | ||||||
|         return [self.table.c[k.lower()] == v for k, v in crit.items()] |         return [self.table.c[k.lower()] == v for k, v in crit.items() if v is not None] | ||||||
| 
 | 
 | ||||||
|     def setupValues(self, track, withTrackId=False): |     def setupValues(self, track, withTrackId=False): | ||||||
|         values = {} |         values = {} | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
|  | # scopes/tests.py | ||||||
|  | 
 | ||||||
| """The real test implementations""" | """The real test implementations""" | ||||||
| 
 | 
 | ||||||
| from datetime import datetime | from datetime import datetime | ||||||
| import unittest | import unittest | ||||||
| 
 | from scopes.storage import concept, folder, topic, tracking | ||||||
| from scopes.storage import folder, tracking |  | ||||||
| 
 | 
 | ||||||
| import config | import config | ||||||
| config.dbengine = 'postgresql' | config.dbengine = 'postgresql' | ||||||
|  | @ -83,11 +84,45 @@ class Test(unittest.TestCase): | ||||||
|      |      | ||||||
|         storage.commit() |         storage.commit() | ||||||
|      |      | ||||||
|  |     def test_003_type(self): | ||||||
|  |         storage.dropTable('types') | ||||||
|  |         concept.setupCoreTypes(storage) | ||||||
|      |      | ||||||
| def suite(): |         types = storage.getContainer(concept.Type) | ||||||
|     return unittest.TestSuite(( |         tps = list(types.query()) | ||||||
|         unittest.TestLoader().loadTestsFromTestCase(Test), |         self.assertEqual(len(tps), 6) | ||||||
|     )) |         self.assertEqual(tps[0].name, 'topic') | ||||||
|  |      | ||||||
|  |         tfolder = types.queryLast(name='folder') | ||||||
|  |         fldrs = list(tfolder.values()) | ||||||
|  |         self.assertEqual(len(fldrs), 2) | ||||||
|  |         self.assertEqual(fldrs[0].name, 'top') | ||||||
|  |      | ||||||
|  |         storage.commit() | ||||||
|  |      | ||||||
|  |     def test_004_topic(self): | ||||||
|  |         storage.dropTable('topics') | ||||||
|  |         topics = storage.getContainer(topic.Topic) | ||||||
|  |         types = storage.getContainer(concept.Type) | ||||||
|  |         concept.storePredicate(storage, concept.defaultPredicate) | ||||||
|  |         root = folder.Root(storage) | ||||||
|  |         root['top']['topics'] = ftopics = folder.Folder() | ||||||
|  |         ttopic = types.queryLast(name='topic') | ||||||
|  |         self.assertEqual(ttopic.name, 'topic') | ||||||
|  |         ftopics.setTarget(ttopic) | ||||||
|  |         self.assertEqual(ftopics.ref, 'type-1') | ||||||
|  |      | ||||||
|  |         tp_itc = topic.Topic('itc', data=dict( | ||||||
|  |             title='ITC', description='Information and Communication Technology')) | ||||||
|  |         topics.save(tp_itc) | ||||||
|  |         tp_proglang = topic.Topic('prog_lang', data=dict( | ||||||
|  |             title='Programming Languages',  | ||||||
|  |             description='Programming Languages')) | ||||||
|  |         topics.save(tp_proglang) | ||||||
|  |         tp_itc.addChild(tp_proglang) | ||||||
|  |      | ||||||
|  |         c = list(tp_itc.children()) | ||||||
|  |         self.assertEqual(c[0].name, 'prog_lang') | ||||||
|  |      | ||||||
|  |         storage.commit() | ||||||
|   |   | ||||||
| if __name__ == '__main__': |  | ||||||
|     unittest.main(defaultTest='suite') |  | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue