From 3e1127e798168a8b15a5d1a2063c0bb2990c06b8 Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Thu, 14 Mar 2024 10:21:43 +0100 Subject: [PATCH] work in progress: concept map, starting with types --- scopes/interfaces.py | 15 +++++++++-- scopes/server/browser.py | 15 ++++++----- scopes/storage/concept.py | 56 ++++++++++++++++++++++++++++++++++++++- scopes/storage/folder.py | 1 - tests/test_standard.py | 5 +++- tests/tlib_storage.py | 11 +++++++- 6 files changed, 91 insertions(+), 12 deletions(-) diff --git a/scopes/interfaces.py b/scopes/interfaces.py index 50f23f7..194e4f1 100644 --- a/scopes/interfaces.py +++ b/scopes/interfaces.py @@ -5,7 +5,7 @@ from zope.interface import Interface class ITraversable(Interface): - def get(key, default): + def get(key, default=None): """Return the item addressed by `key`; return `default` if not found.""" @@ -15,7 +15,7 @@ class IContainer(ITraversable): """Return a sequence of child objects.""" def __getitem__(key): - """Return the item addressed by `key`; rais KeyError if not found.""" + """Return the item addressed by `key`; raise KeyError if not found.""" def __setitem__(key, value): """Store the `value` under the `key`. @@ -24,6 +24,17 @@ class IContainer(ITraversable): and the value object (e.g. `parent´ and `name`) are stored correctly.""" +class IConcept(IContainer): + + def parents(*predicates): + """Return a sequence of `Triple`s in which this object is + referenced as `second`.""" + + def children(*predicates): + """Return a sequence of `Triple`s in which this object is + referenced as `first`.""" + + class IView(Interface): def __call__(): diff --git a/scopes/server/browser.py b/scopes/server/browser.py index 19b517d..4cd432a 100644 --- a/scopes/server/browser.py +++ b/scopes/server/browser.py @@ -4,13 +4,16 @@ import json from zope.interface import implementer from scopes.interfaces import IContainer, IView -views = {} +views = {} # registry for all views: {name: {prefix: viewClass, ...}, ...} -def register(contextClass, name): +def register(name, *contextClasses): + """Use as decorator: `@register(name, class, ...). + class `None` means default view for all classes.""" def doRegister(viewClass): nameEntry = views.setdefault(name, {}) - key = contextClass and contextClass.prefix or '' - nameEntry[key] = viewClass + for cl in contextClasses: + key = cl and cl.prefix or '' + nameEntry[key] = viewClass return viewClass return doRegister @@ -26,8 +29,8 @@ def getView(request, ob, name): return viewClass(ob, request) -@register(None, 'index.html') -@register(None, 'index.json') +@register('index.html', None) +@register('index.json', None) @implementer(IView) class DefaultView: diff --git a/scopes/storage/concept.py b/scopes/storage/concept.py index 95ede52..df962cb 100644 --- a/scopes/storage/concept.py +++ b/scopes/storage/concept.py @@ -1,12 +1,66 @@ # scopes.storage.concept -"""Abstract base classes for concept map application classes.""" +"""Core classes for concept map structure.""" +from zope.interface import implementer +from scopes.interfaces import IConcept from scopes.storage.common import registerContainerClass from scopes.storage.tracking import Container, Track +@implementer(IConcept) class Concept(Track): headFields = ['name'] + + +class Concepts(Container): + + insertOnChange = False + +class Predicate(Concept): + + prefix = 'pred' + + +@registerContainerClass +class Predicates(Concepts): + + itemFactory = Predicate + tableName = 'preds' + + +class Triple(Track): + + headFields = ['first', 'second', 'predicate'] + prefix = 'rel' + + +@registerContainerClass +class Rels(Container): + + itemFactory = Triple + indexes = [('first', 'predicate', 'second'), + ('first', 'second'), ('predicate', 'second')] + tableName = 'rels' + insertOnChange = False + + +# types stuff + +class Type(Concept): + + headFields = ['name', 'prefix'] + prefix = 'type' + + def get(key, default=None): + return self.container.queryLast(name=key) or default + + +@registerContainerClass +class Types(Concepts): + + itemFactory = Type + indexes = [('name',), ('prefix',)] + tableName = 'types' diff --git a/scopes/storage/folder.py b/scopes/storage/folder.py index c0a8c25..5057372 100644 --- a/scopes/storage/folder.py +++ b/scopes/storage/folder.py @@ -9,7 +9,6 @@ from scopes.storage.tracking import Container, Track @implementer(IContainer) class Folder(Track): - """Needs docstring to be traversable.""" headFields = ['parent', 'name', 'ref'] prefix = 'fldr' diff --git a/tests/test_standard.py b/tests/test_standard.py index 82bd8a9..207f3b1 100644 --- a/tests/test_standard.py +++ b/tests/test_standard.py @@ -21,7 +21,10 @@ class Test(unittest.TestCase): def test_002_folder(self): tlib_storage.test_folder(self, config) - def test_003_server(self): + def test_003_type(self): + tlib_storage.test_type(self, config) + + def test_013_server(self): tlib_server.test_app(self, config) diff --git a/tests/tlib_storage.py b/tests/tlib_storage.py index 630d73c..0c6c70b 100644 --- a/tests/tlib_storage.py +++ b/tests/tlib_storage.py @@ -3,7 +3,7 @@ """Test implementation for the `scopes.storage` package.""" from datetime import datetime -from scopes.storage import folder, tracking +from scopes.storage import concept, folder, tracking def test_tracking(self, config): @@ -73,3 +73,12 @@ def test_folder(self, config): storage.commit() + +def test_type(self, config): + storage = config.storageFactory(config.dbschema) + storage.dropTable('types') + types = storage.create(concept.Types) + tid01 = types.save(concept.Type('type', 'type')) + self.assertEqual(tid01, 1) + + storage.commit()