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()]
|
request = BrowserRequest(environ['wsgi.input'], environ)
|
||||||
|
request.setPublication(Publication(appRoot))
|
||||||
|
request = publish(request, True)
|
||||||
|
response = request.response
|
||||||
|
start_response(response.getStatusString(), response.getHeaders())
|
||||||
|
return response.consumeBodyIter()
|
||||||
|
return zope_app
|
||||||
|
|
||||||
|
|
||||||
def zope_app(environ, start_response):
|
class Publication(DefaultPublication):
|
||||||
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()
|
|
||||||
|
|
||||||
|
def traverseName(self, request, ob, name):
|
||||||
|
next = getView(request, ob, name)
|
||||||
|
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
|
||||||
|
|
||||||
class AppRoot:
|
def getDefaultTraversal(self, request, ob):
|
||||||
"""Zope Demo AppRoot"""
|
if IView.providedBy(ob):
|
||||||
|
return ob, ()
|
||||||
def __call__(self):
|
return ob, ('index.html',)
|
||||||
"""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'
|
|
||||||
|
|
||||||
|
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):
|
||||||
stmt = self.table.select().where(
|
if crit:
|
||||||
|
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'
|
||||||
|
@ -22,33 +23,33 @@ class Test(unittest.TestCase):
|
||||||
def test_001_tracking(self):
|
def test_001_tracking(self):
|
||||||
storage.dropTable('tracks')
|
storage.dropTable('tracks')
|
||||||
tracks = storage.create(tracking.Container)
|
tracks = storage.create(tracking.Container)
|
||||||
|
|
||||||
tr01 = tracking.Track('t01', 'john')
|
tr01 = tracking.Track('t01', 'john')
|
||||||
tr01.update(dict(activity='testing'))
|
tr01.update(dict(activity='testing'))
|
||||||
self.assertEqual(tr01.head, {'taskId': 't01', 'userName': 'john'})
|
self.assertEqual(tr01.head, {'taskId': 't01', 'userName': 'john'})
|
||||||
self.assertEqual(tr01.taskId, 't01')
|
self.assertEqual(tr01.taskId, 't01')
|
||||||
self.assertEqual(tr01.userName, 'john')
|
self.assertEqual(tr01.userName, 'john')
|
||||||
|
|
||||||
self.assertTrue(tracks.getTable() is not None)
|
self.assertTrue(tracks.getTable() is not None)
|
||||||
|
|
||||||
trid01 = tracks.save(tr01)
|
trid01 = tracks.save(tr01)
|
||||||
self.assertTrue(trid01 > 0)
|
self.assertTrue(trid01 > 0)
|
||||||
|
|
||||||
#tr01a = tracks.get(trid01)
|
#tr01a = tracks.get(trid01)
|
||||||
tr01a = tracks['%07i' % trid01]
|
tr01a = tracks['%07i' % trid01]
|
||||||
self.assertEqual(tr01a.head, tr01.head)
|
self.assertEqual(tr01a.head, tr01.head)
|
||||||
self.assertEqual(tr01a.trackId, trid01)
|
self.assertEqual(tr01a.trackId, trid01)
|
||||||
self.assertEqual(tr01a.data.get('activity'), 'testing')
|
self.assertEqual(tr01a.data.get('activity'), 'testing')
|
||||||
|
|
||||||
tr01a.update(dict(text='Set up unit tests.'))
|
tr01a.update(dict(text='Set up unit tests.'))
|
||||||
tr01a.timeStamp = None
|
tr01a.timeStamp = None
|
||||||
self.assertTrue(tracks.save(tr01a) > 0)
|
self.assertTrue(tracks.save(tr01a) > 0)
|
||||||
|
|
||||||
tr01b = tracks.queryLast(taskId='t01')
|
tr01b = tracks.queryLast(taskId='t01')
|
||||||
self.assertEqual(tr01b.head, tr01.head)
|
self.assertEqual(tr01b.head, tr01.head)
|
||||||
self.assertNotEqual(tr01b.trackId, trid01)
|
self.assertNotEqual(tr01b.trackId, trid01)
|
||||||
self.assertEqual(tr01b.data.get('activity'), 'testing')
|
self.assertEqual(tr01b.data.get('activity'), 'testing')
|
||||||
|
|
||||||
tr02 = tracking.Track('t02', 'jim', trackId=31, timeStamp=datetime(2023, 11, 30),
|
tr02 = tracking.Track('t02', 'jim', trackId=31, timeStamp=datetime(2023, 11, 30),
|
||||||
data=dict(activity='concept'))
|
data=dict(activity='concept'))
|
||||||
trid02 = tracks.upsert(tr02)
|
trid02 = tracks.upsert(tr02)
|
||||||
|
@ -58,16 +59,16 @@ class Test(unittest.TestCase):
|
||||||
trid021 = tracks.upsert(tr02)
|
trid021 = tracks.upsert(tr02)
|
||||||
self.assertEqual(trid021, trid01)
|
self.assertEqual(trid021, trid01)
|
||||||
self.assertEqual(tr02.uid, 'rec-' + str(trid01))
|
self.assertEqual(tr02.uid, 'rec-' + str(trid01))
|
||||||
|
|
||||||
tr03 = storage.getItem('rec-31')
|
tr03 = storage.getItem('rec-31')
|
||||||
self.assertEqual(tr03.trackId, 31)
|
self.assertEqual(tr03.trackId, 31)
|
||||||
|
|
||||||
n = tracks.remove(31)
|
n = tracks.remove(31)
|
||||||
self.assertEqual(n, 1)
|
self.assertEqual(n, 1)
|
||||||
self.assertEqual(tracks.get(31), None)
|
self.assertEqual(tracks.get(31), None)
|
||||||
|
|
||||||
storage.commit()
|
storage.commit()
|
||||||
|
|
||||||
def test_002_folder(self):
|
def test_002_folder(self):
|
||||||
storage.dropTable('folders')
|
storage.dropTable('folders')
|
||||||
root = folder.Root(storage)
|
root = folder.Root(storage)
|
||||||
|
@ -80,14 +81,48 @@ class Test(unittest.TestCase):
|
||||||
ch1 = top['child1']
|
ch1 = top['child1']
|
||||||
self.assertEqual(ch1.parent, top.rid)
|
self.assertEqual(ch1.parent, top.rid)
|
||||||
self.assertEqual(list(top.keys()), ['child1'])
|
self.assertEqual(list(top.keys()), ['child1'])
|
||||||
|
|
||||||
storage.commit()
|
storage.commit()
|
||||||
|
|
||||||
|
def test_003_type(self):
|
||||||
def suite():
|
storage.dropTable('types')
|
||||||
return unittest.TestSuite((
|
concept.setupCoreTypes(storage)
|
||||||
unittest.TestLoader().loadTestsFromTestCase(Test),
|
|
||||||
))
|
types = storage.getContainer(concept.Type)
|
||||||
|
tps = list(types.query())
|
||||||
if __name__ == '__main__':
|
self.assertEqual(len(tps), 6)
|
||||||
unittest.main(defaultTest='suite')
|
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()
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue