From 7858b91c7db0bb01850c654953d95b3a915245b4 Mon Sep 17 00:00:00 2001 From: William Ronchetti Date: Thu, 21 Nov 2019 12:53:53 -0500 Subject: [PATCH 1/8] refactor unique/name_key into schema, add validator --- src/snovault/resources.py | 14 +- src/snovault/schema_utils.py | 19 +++ src/snovault/test_schemas/EmbeddingTest.json | 3 +- src/snovault/test_schemas/TestingKeys.json | 28 ++++ .../test_schemas/TestingLinkTargetSno.json | 3 +- src/snovault/tests/test_keyed_items.py | 130 ++++++++++++++++++ src/snovault/tests/test_views.py | 2 +- src/snovault/tests/testing_views.py | 35 ++++- src/snovault/typeinfo.py | 5 + 9 files changed, 223 insertions(+), 16 deletions(-) create mode 100644 src/snovault/test_schemas/TestingKeys.json create mode 100644 src/snovault/tests/test_keyed_items.py diff --git a/src/snovault/resources.py b/src/snovault/resources.py index 912b25260..520e2c69f 100644 --- a/src/snovault/resources.py +++ b/src/snovault/resources.py @@ -200,8 +200,8 @@ def get(self, name, default=None): if not self._allow_contained(resource): return default return resource - if self.unique_key is not None: - resource = self.connection.get_by_unique_key(self.unique_key, name) + for unique_key in self.type_info.schema_keys.keys(): + resource = self.connection.get_by_unique_key(unique_key, name) if resource is not None: if not self._allow_contained(resource): return default @@ -249,7 +249,6 @@ class Collection(AbstractCollection): class Item(Resource): item_type = None base_types = ['Item'] - name_key = None rev = {} aggregated_items = {} embedded_list = [] @@ -280,10 +279,9 @@ def __parent__(self): @property def __name__(self): - - if self.name_key is None: + if self.type_info.name_key is None: return str(self.uuid) - return self.properties.get(self.name_key, None) or str(self.uuid) + return self.properties.get(self.type_info.name_key, None) or str(self.uuid) @property def properties(self): @@ -461,8 +459,8 @@ def _update(self, properties, sheets=None): del properties['uuid'] # validation on name key and unique keys - nk_val = properties.get(self.name_key, '') - self.validate_path_characters(self.name_key, nk_val) + nk_val = properties.get(self.type_info.name_key, '') + self.validate_path_characters(self.type_info.name_key, nk_val) unique_keys = self.unique_keys(properties) for k, values in unique_keys.items(): diff --git a/src/snovault/schema_utils.py b/src/snovault/schema_utils.py index 2f666214b..c5d8f46ec 100644 --- a/src/snovault/schema_utils.py +++ b/src/snovault/schema_utils.py @@ -287,6 +287,24 @@ def calculatedProperty(validator, linkTo, instance, schema): yield ValidationError('submission of calculatedProperty disallowed') +def nameKey(validator, name_key, instance, schema): + """ + Validates name key field in schema is a property field that is marked as + a uniqueKey + """ + if not validator.is_type(name_key, "string"): + raise Exception("Bad schema") # keeping convention for now + if name_key not in schema: + if name_key not in schema['properties']: + yield ValidationError('Given name key is not a schema field!') + else: + prop = schema['properties'][name_key] + else: + prop = schema[name_key] + if not prop.get('uniqueKey', False): + yield ValidationError('Given name key is not a uniqueKey!') + + class SchemaValidator(Draft4Validator): VALIDATORS = Draft4Validator.VALIDATORS.copy() VALIDATORS['calculatedProperty'] = calculatedProperty @@ -295,6 +313,7 @@ class SchemaValidator(Draft4Validator): VALIDATORS['permission'] = permission VALIDATORS['requestMethod'] = requestMethod VALIDATORS['validators'] = validators + VALIDATORS['name_key'] = nameKey SERVER_DEFAULTS = SERVER_DEFAULTS diff --git a/src/snovault/test_schemas/EmbeddingTest.json b/src/snovault/test_schemas/EmbeddingTest.json index 17aaa825b..497f62d06 100644 --- a/src/snovault/test_schemas/EmbeddingTest.json +++ b/src/snovault/test_schemas/EmbeddingTest.json @@ -16,6 +16,7 @@ "type": "string" } }, + "name_key": "accession", "columns": { "title": { "title": "Title" @@ -33,4 +34,4 @@ "title": "Date added" } } -} \ No newline at end of file +} diff --git a/src/snovault/test_schemas/TestingKeys.json b/src/snovault/test_schemas/TestingKeys.json new file mode 100644 index 000000000..791bc0ea8 --- /dev/null +++ b/src/snovault/test_schemas/TestingKeys.json @@ -0,0 +1,28 @@ +{ + "type": "object", + "properties": { + "name": { + "title": "Common Name", + "description": "Unique name for this object", + "type": "string", + "uniqueKey": true + }, + "grouping": { + "title": "Grouping", + "description": "String name of a group that this item belongs to (not unique)", + "type": "string" + }, + "obj_id": { + "title": "Object ID", + "description": "Unique ID of this object", + "type": "string", + "uniqueKey": true + }, + "system_id": { + "title": "System ID", + "description": "Unique System ID for this object that is not marked as unique in schema", + "type": "string" + } + }, + "name_key": "obj_id" +} diff --git a/src/snovault/test_schemas/TestingLinkTargetSno.json b/src/snovault/test_schemas/TestingLinkTargetSno.json index 4e7f68610..59c12eae0 100644 --- a/src/snovault/test_schemas/TestingLinkTargetSno.json +++ b/src/snovault/test_schemas/TestingLinkTargetSno.json @@ -12,5 +12,6 @@ "type": "string" } }, + "name_key": "name", "additionalProperties": false -} \ No newline at end of file +} diff --git a/src/snovault/tests/test_keyed_items.py b/src/snovault/tests/test_keyed_items.py new file mode 100644 index 000000000..31ca427d0 --- /dev/null +++ b/src/snovault/tests/test_keyed_items.py @@ -0,0 +1,130 @@ +import pytest + + +@pytest.fixture +def TestKey(testapp): + """ Posts an item under testing_keys_schema """ + url = '/testing-keys' + item = { + 'name': 'Orange', + 'grouping': 'fruit', + 'obj_id': '123' + } + testapp.post_json(url, item, status=201) + + +@pytest.fixture +def TestKeyDefinition(testapp): + """ Posts an item under testing_keys_def """ + url = '/testing-keys-def' + item = { + 'name': 'Orange', + 'grouping': 'fruit', + 'obj_id': '123', + 'system_id': 'abc' + } + testapp.post_json(url, item, status=201) + + +@pytest.fixture +def TestKeyName(testapp): + """ Posts an item under testing_keys_name """ + url = '/testing-keys-name' + item = { + 'name': 'Orange', + 'grouping': 'fruit', + 'obj_id': '123', + 'system_id': 'abc' + } + testapp.post_json(url, item, status=201) + + +def test_schema_unique_key(TestKey, testapp): + """ + Tests that when we define a uniqueKey on the schema that we cannot post a + second item with a repeat key value in any fields + """ + url = '/testing-keys' + duplicate_name = { + 'name': 'Orange', + 'grouping': 'fruit', + 'obj_id': '456', + 'system_id': 'def' + } + testapp.post_json(url, duplicate_name, status=409) + duplicate_id = { + 'name': 'Banana', + 'grouping': 'fruit', + 'obj_id': '123', + 'system_id': 'def' + } + testapp.post_json(url, duplicate_id, status=409) + duplicate_system_id = { + 'name': 'Banana', + 'grouping': 'fruit', + 'obj_id': '456', + 'system_id': 'abc' + } # should be allowed since not marked as unique wrt the DB + testapp.post_json(url, duplicate_system_id, status=201) + correct = { + 'name': 'Apple', + 'grouping': 'fruit', + 'obj_id': '789', + 'system_id': 'hij' + } # should also work since all fields are unique + testapp.post_json(url, correct, status=201) + # both gets should not work since obj_id and name are only uniqueKey's + testapp.get(url + '/' + correct['obj_id']).follow(status=200) + testapp.get(url + '/' + correct['name']).follow(status=200) + + +def test_definition_unique_key(TestKeyDefinition, testapp): + """ Tests behavior associated with setting unique_key in type definition """ + url = '/testing-keys-def' + duplicate_id = { + 'name': 'Banana', + 'grouping': 'fruit', + 'obj_id': '123', + 'system_id': 'def' + } # sanity: posting should fail as above since obj_id is a uniqueKey + testapp.post_json(url, duplicate_id, status=409) + duplicate_system_id = { + 'name': 'Banana', + 'grouping': 'fruit', + 'obj_id': '456', + 'system_id': 'abc' + } # this will work despite system_id being marked as unique_key in definition + testapp.post_json(url, duplicate_system_id, status=201) + # obj_id should succeed since it is a unique_key + resp = testapp.get(url + '/' + duplicate_system_id['obj_id']).follow(status=200).json + assert resp['obj_id'] in resp['@id'] # @id is uuid since no name_key set + testapp.get(url + '/' + duplicate_system_id['obj_id']).follow(status=200) + testapp.get(url + '/' + duplicate_system_id['name']).follow(status=200) + + +def test_name_key(TestKeyName, testapp): + """ + Tests behavior associated with setting a name_key in the type definition + which should allow us to lookup the item via resource + """ + url = '/testing-keys-name' + duplicate_id = { + 'name': 'Banana', + 'grouping': 'fruit', + 'obj_id': '123', + 'system_id': 'def' + + } # sanity: posting should fail as above since obj_id is a uniqueKey + testapp.post_json(url, duplicate_id, status=409) + correct = { + 'name': 'Apple', + 'grouping': 'fruit', + 'obj_id': '789', + 'system_id': 'hij' + } + testapp.post_json(url, correct, status=201) + # obj_id should succeed since it is a name_key + resp = testapp.get(url + '/' + correct['obj_id']).follow(status=200).json + assert resp['obj_id'] in resp['@id'] # @id is obj_id since it is the name_key + testapp.get(url + '/' + correct['obj_id']).follow(status=200) + testapp.get(url + '/' + correct['name']).follow(status=200) diff --git a/src/snovault/tests/test_views.py b/src/snovault/tests/test_views.py index 4a2eff844..98b697cf5 100644 --- a/src/snovault/tests/test_views.py +++ b/src/snovault/tests/test_views.py @@ -153,7 +153,7 @@ def test_jsonld_context(testapp): @pytest.mark.slow -@pytest.mark.parametrize('item_type', PARAMETERIZED_NAMES) +@pytest.mark.parametrize('item_type', [k for k in PARAMETERIZED_NAMES if k != 'TestingKeys']) def test_index_data_workbook(testapp, indexer_testapp, item_type): res = testapp.get('/%s?limit=all' % item_type).follow(status=200) for item in res.json['@graph']: diff --git a/src/snovault/tests/testing_views.py b/src/snovault/tests/testing_views.py index 56791c111..6ffb3ed43 100644 --- a/src/snovault/tests/testing_views.py +++ b/src/snovault/tests/testing_views.py @@ -174,12 +174,12 @@ class Item(BaseItem): @property def __name__(self): - if self.name_key is None: + if self.type_info.name_key is None: return self.uuid properties = self.upgrade_properties() if properties.get('status') == 'replaced': return self.uuid - return properties.get(self.name_key, None) or self.uuid + return properties.get(self.type_info.name_key, None) or self.uuid def __acl__(self): # Don't finalize to avoid validation here. @@ -283,7 +283,6 @@ def edit_json(context, request): class AbstractItemTest(Item): item_type = 'AbstractItemTest' base_types = ['AbstractItemTest'] + Item.base_types - name_key = 'accession' @collection( @@ -320,7 +319,6 @@ class AbstractItemTestSecondSubItem(AbstractItemTest): class EmbeddingTest(Item): item_type = 'embedding_test' schema = load_schema('snovault:test_schemas/EmbeddingTest.json') - name_key = 'accession' # use TestingDownload to test embedded_list = [ @@ -357,7 +355,6 @@ class TestingLinkAggregateSno(Item): @collection('testing-link-targets-sno', unique_key='testing_link_target_sno:name') class TestingLinkTargetSno(Item): item_type = 'testing_link_target_sno' - name_key = 'name' schema = load_schema('snovault:test_schemas/TestingLinkTargetSno.json') rev = { 'reverse': ('TestingLinkSourceSno', 'target'), @@ -410,6 +407,34 @@ class TestingDependencies(Item): schema = load_schema('snovault:test_schemas/TestingDependencies.json') +@collection('testing-keys') +class TestingKeys(Item): + """ Intended to test the behavior of uniqueKey value in schema """ + item_type = 'testing_keys' + schema = load_schema('snovault:test_schemas/TestingKeys.json') + + +@collection('testing-keys-def') +class TestingKeysDef(Item): + """ + Intended to test the behavior of the unique_key setting when it overlaps with + uniqueKey setting in the schema + """ + item_type = 'testing_keys_def' + schema = load_schema('snovault:test_schemas/TestingKeys.json') + + +@collection('testing-keys-name') +class TestingKeysName(Item): + """ + We set obj_id as a unique key so that it can be used as a name_key in the + resource path. We should now see the name key in the @id field instead of + the uuid + """ + item_type = 'testing_keys_name' + schema = load_schema('snovault:test_schemas/TestingKeys.json') + + @view_config(name='testing-render-error', request_method='GET') def testing_render_error(request): return { diff --git a/src/snovault/typeinfo.py b/src/snovault/typeinfo.py index a737dbe6a..adf89930e 100644 --- a/src/snovault/typeinfo.py +++ b/src/snovault/typeinfo.py @@ -151,6 +151,11 @@ def schema(self): schema['properties'][name] = prop.schema return schema + @reify + def name_key(self): + schema = self.factory.schema + return schema.get('name_key', None) + class TypesTool(object): """ From bee0cc0576823fdf97a583033fa755579be6bc5a Mon Sep 17 00:00:00 2001 From: William Ronchetti Date: Thu, 21 Nov 2019 15:13:36 -0500 Subject: [PATCH 2/8] revert refactor, just rename unique_key to traversal_key --- docs/make.bat | 35 ----------- docs/source/keys.rst | 58 +++++++++++++++++++ src/snovault/indexing_views.py | 22 +------ src/snovault/resources.py | 24 ++++---- src/snovault/schema_utils.py | 19 ------ src/snovault/test_schemas/EmbeddingTest.json | 1 - src/snovault/test_schemas/TestingKeys.json | 3 +- .../test_schemas/TestingLinkTargetSno.json | 1 - src/snovault/tests/test_keyed_items.py | 46 ++++++++++----- src/snovault/tests/test_views.py | 2 +- src/snovault/tests/testing_key.py | 2 +- src/snovault/tests/testing_views.py | 43 +++++++++----- 12 files changed, 138 insertions(+), 118 deletions(-) delete mode 100644 docs/make.bat create mode 100644 docs/source/keys.rst diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 53941892e..000000000 --- a/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd \ No newline at end of file diff --git a/docs/source/keys.rst b/docs/source/keys.rst new file mode 100644 index 000000000..4963ad085 --- /dev/null +++ b/docs/source/keys.rst @@ -0,0 +1,58 @@ +================ +Keys in Snovault +================ + +Broadly speaking in the 4DN space there are four different types of 'keys'. 'Unique', 'name' and 'traversal' keys are used in both Fourfront/CGAP and Snovault while 'lookup' keys are used only in FF/CGAP. This document will only touch on the first three. + + +Unique Key and Traversal Key +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These two keys are bundled together because they are closely related. A unique key is denoted in the schema of an item. This is a constraint on the items in the database that says that no two items can share the same value in this field. Note that if there are multiple fields that are marked as uniqueKey's then either one can be used to uniquely identify items. In either case a traversal key must be specified in the collection decorator, which must be one of the unique keys. If a traversal key is not specified you will not be able to lookup items via the resource path with either of their unique keys. See the below examples from the tests. + +.. code-block:: python + + # All of the following collections are referencing a schema which denotes + # fields 'obj_id' and 'name' as uniqueKey's. In the below case though if you + # wanted to look up an item by this type from the point of view of snovault + # you would only be able to do so via uuid + @collection('testing-keys') + class TestingKeys(Item): + """ Intended to test the behavior of uniqueKey value in schema """ + item_type = 'testing_keys' + schema = load_schema('snovault:test_schemas/TestingKeys.json') + + + # In this case we specify the traversal_key to be the obj_id. This allows us to + # use the resource path to get the item ie: Get /testing-keys-def/ + # Note that the resource path is still the uuid + @collection('testing-keys-def', traversal_key='testing_keys_def:obj_id') + class TestingKeysDef(Item): + """ + Intended to test the behavior of setting a traversal key equal to one of the + uniqueKey's specified in the schema. This should allow us to get the object + via obj_id whereas before we could not. + """ + item_type = 'testing_keys_def' + schema = load_schema('snovault:test_schemas/TestingKeys.json') + +Name Key +^^^^^^^^ + +The name key is a special field specified on the item type definition. It augments the resource path so that the '@id' field of the item contains a path using the name_key instead of the uuid. See final example below. + +.. code-block:: python + + # In this case we specify matching traversal_key and name_key. This means that + # the resource path is augmented to show the name_key instead of the uuid AND + # you can get the item via resource path ie: Get /testing-keys-name/ + @collection('testing-keys-name', traversal_key='testing_keys_name:name') + class TestingKeysName(Item): + """ + We set name as a traversal key so that it can be used as a name_key in the + resource path. We should now see the name key in the @id field instead of + the uuid + """ + item_type = 'testing_keys_name' + schema = load_schema('snovault:test_schemas/TestingKeys.json') + name_key = 'name' diff --git a/src/snovault/indexing_views.py b/src/snovault/indexing_views.py index 5f8cca522..1aae44249 100644 --- a/src/snovault/indexing_views.py +++ b/src/snovault/indexing_views.py @@ -106,28 +106,11 @@ def item_index_data(context, request): links = new_links principals_allowed = calc_principals(context) - path = resource_path(context) - paths = {path} + path = resource_path(context) + '/' collection = context.collection - with indexing_timer(indexing_stats, 'unique_keys'): unique_keys = context.unique_keys(properties) - if collection.unique_key in unique_keys: - paths.update( - resource_path(collection, key) - for key in unique_keys[collection.unique_key]) - - with indexing_timer(indexing_stats, 'paths'): - for base in (collection, request.root): - for key_name in ('accession', 'alias'): - if key_name not in unique_keys: - continue - paths.add(resource_path(base, uuid)) - paths.update( - resource_path(base, key) - for key in unique_keys[key_name]) - - path = path + '/' + # setting _indexing_view enables the embed_cache and cause population of # request._linked_uuids and request._rev_linked_uuids_by_item request._indexing_view = True @@ -182,7 +165,6 @@ def item_index_data(context, request): 'links': links, 'max_sid': context.max_sid, 'object': object_view, - 'paths': sorted(paths), 'principals_allowed': principals_allowed, 'properties': properties, 'propsheets': { diff --git a/src/snovault/resources.py b/src/snovault/resources.py index 520e2c69f..243eb5d74 100644 --- a/src/snovault/resources.py +++ b/src/snovault/resources.py @@ -142,12 +142,12 @@ class AbstractCollection(Resource, Mapping): And some other info as well. Collections allow retrieval of specific items with them by using the `get` - method with uuid or the unique_key + method with uuid or the traversal_key (which must be a unique key) """ properties = {} - unique_key = None + traversal_key = None - def __init__(self, registry, name, type_info, properties=None, acl=None, unique_key=None): + def __init__(self, registry, name, type_info, properties=None, acl=None, traversal_key=None): self.registry = registry self.__name__ = name self.type_info = type_info @@ -155,8 +155,8 @@ def __init__(self, registry, name, type_info, properties=None, acl=None, unique_ self.properties = properties if acl is not None: self.__acl__ = acl - if unique_key is not None: - self.unique_key = unique_key + if traversal_key is not None: + self.traversal_key = traversal_key @reify def connection(self): @@ -200,8 +200,8 @@ def get(self, name, default=None): if not self._allow_contained(resource): return default return resource - for unique_key in self.type_info.schema_keys.keys(): - resource = self.connection.get_by_unique_key(unique_key, name) + if self.traversal_key is not None: + resource = self.connection.get_by_unique_key(self.traversal_key, name) if resource is not None: if not self._allow_contained(resource): return default @@ -248,6 +248,7 @@ class Collection(AbstractCollection): class Item(Resource): item_type = None + name_key = None base_types = ['Item'] rev = {} aggregated_items = {} @@ -279,9 +280,9 @@ def __parent__(self): @property def __name__(self): - if self.type_info.name_key is None: + if self.name_key is None: return str(self.uuid) - return self.properties.get(self.type_info.name_key, None) or str(self.uuid) + return self.properties.get(self.name_key, None) or str(self.uuid) @property def properties(self): @@ -357,6 +358,7 @@ def get_filtered_rev_links(self, request, name): return filtered_uuids def unique_keys(self, properties): + """ Gets all schema fields defined to be uniqueKey's """ return { name: [v for prop in props for v in ensurelist(properties.get(prop, ()))] for name, props in self.type_info.schema_keys.items() @@ -459,8 +461,8 @@ def _update(self, properties, sheets=None): del properties['uuid'] # validation on name key and unique keys - nk_val = properties.get(self.type_info.name_key, '') - self.validate_path_characters(self.type_info.name_key, nk_val) + nk_val = properties.get(self.name_key, '') + self.validate_path_characters(self.name_key, nk_val) unique_keys = self.unique_keys(properties) for k, values in unique_keys.items(): diff --git a/src/snovault/schema_utils.py b/src/snovault/schema_utils.py index c5d8f46ec..2f666214b 100644 --- a/src/snovault/schema_utils.py +++ b/src/snovault/schema_utils.py @@ -287,24 +287,6 @@ def calculatedProperty(validator, linkTo, instance, schema): yield ValidationError('submission of calculatedProperty disallowed') -def nameKey(validator, name_key, instance, schema): - """ - Validates name key field in schema is a property field that is marked as - a uniqueKey - """ - if not validator.is_type(name_key, "string"): - raise Exception("Bad schema") # keeping convention for now - if name_key not in schema: - if name_key not in schema['properties']: - yield ValidationError('Given name key is not a schema field!') - else: - prop = schema['properties'][name_key] - else: - prop = schema[name_key] - if not prop.get('uniqueKey', False): - yield ValidationError('Given name key is not a uniqueKey!') - - class SchemaValidator(Draft4Validator): VALIDATORS = Draft4Validator.VALIDATORS.copy() VALIDATORS['calculatedProperty'] = calculatedProperty @@ -313,7 +295,6 @@ class SchemaValidator(Draft4Validator): VALIDATORS['permission'] = permission VALIDATORS['requestMethod'] = requestMethod VALIDATORS['validators'] = validators - VALIDATORS['name_key'] = nameKey SERVER_DEFAULTS = SERVER_DEFAULTS diff --git a/src/snovault/test_schemas/EmbeddingTest.json b/src/snovault/test_schemas/EmbeddingTest.json index 497f62d06..c7a2ed8ed 100644 --- a/src/snovault/test_schemas/EmbeddingTest.json +++ b/src/snovault/test_schemas/EmbeddingTest.json @@ -16,7 +16,6 @@ "type": "string" } }, - "name_key": "accession", "columns": { "title": { "title": "Title" diff --git a/src/snovault/test_schemas/TestingKeys.json b/src/snovault/test_schemas/TestingKeys.json index 791bc0ea8..25fcde991 100644 --- a/src/snovault/test_schemas/TestingKeys.json +++ b/src/snovault/test_schemas/TestingKeys.json @@ -23,6 +23,5 @@ "description": "Unique System ID for this object that is not marked as unique in schema", "type": "string" } - }, - "name_key": "obj_id" + } } diff --git a/src/snovault/test_schemas/TestingLinkTargetSno.json b/src/snovault/test_schemas/TestingLinkTargetSno.json index 59c12eae0..b7df15aaa 100644 --- a/src/snovault/test_schemas/TestingLinkTargetSno.json +++ b/src/snovault/test_schemas/TestingLinkTargetSno.json @@ -12,6 +12,5 @@ "type": "string" } }, - "name_key": "name", "additionalProperties": false } diff --git a/src/snovault/tests/test_keyed_items.py b/src/snovault/tests/test_keyed_items.py index 31ca427d0..819b3d1cc 100644 --- a/src/snovault/tests/test_keyed_items.py +++ b/src/snovault/tests/test_keyed_items.py @@ -73,13 +73,16 @@ def test_schema_unique_key(TestKey, testapp): 'system_id': 'hij' } # should also work since all fields are unique testapp.post_json(url, correct, status=201) - # both gets should not work since obj_id and name are only uniqueKey's - testapp.get(url + '/' + correct['obj_id']).follow(status=200) - testapp.get(url + '/' + correct['name']).follow(status=200) + # both gets should not work since obj_id and name not are name_keys + testapp.get(url + '/' + correct['obj_id'], status=404) + testapp.get(url + '/' + correct['name'], status=404) def test_definition_unique_key(TestKeyDefinition, testapp): - """ Tests behavior associated with setting unique_key in type definition """ + """ + Tests that using 'obj_id' as the name key allows lookup via resource path + using 'obj_id' and not 'name' + """ url = '/testing-keys-def' duplicate_id = { 'name': 'Banana', @@ -97,15 +100,16 @@ def test_definition_unique_key(TestKeyDefinition, testapp): testapp.post_json(url, duplicate_system_id, status=201) # obj_id should succeed since it is a unique_key resp = testapp.get(url + '/' + duplicate_system_id['obj_id']).follow(status=200).json - assert resp['obj_id'] in resp['@id'] # @id is uuid since no name_key set - testapp.get(url + '/' + duplicate_system_id['obj_id']).follow(status=200) - testapp.get(url + '/' + duplicate_system_id['name']).follow(status=200) + # since obj_id is a traversal_key but not a name_key we still expect uuid + assert resp['uuid'] in resp['@id'] + # should fail since name is not a name_key + testapp.get(url + '/' + duplicate_system_id['name'], status=404) def test_name_key(TestKeyName, testapp): """ - Tests behavior associated with setting a name_key in the type definition - which should allow us to lookup the item via resource + Tests that using 'name' as the name key allows lookup via resource path + using 'name' and not 'obj_id' """ url = '/testing-keys-name' duplicate_id = { @@ -113,7 +117,6 @@ def test_name_key(TestKeyName, testapp): 'grouping': 'fruit', 'obj_id': '123', 'system_id': 'def' - } # sanity: posting should fail as above since obj_id is a uniqueKey testapp.post_json(url, duplicate_id, status=409) correct = { @@ -124,7 +127,22 @@ def test_name_key(TestKeyName, testapp): } testapp.post_json(url, correct, status=201) # obj_id should succeed since it is a name_key - resp = testapp.get(url + '/' + correct['obj_id']).follow(status=200).json - assert resp['obj_id'] in resp['@id'] # @id is obj_id since it is the name_key - testapp.get(url + '/' + correct['obj_id']).follow(status=200) - testapp.get(url + '/' + correct['name']).follow(status=200) + resp = testapp.get(url + '/' + correct['name']).follow(status=200).json + assert resp['name'] in resp['@id'] # @id is name since its the name_key + testapp.get(url + '/' + correct['obj_id'], status=404) + + +def test_name_key_traversal_key_mismatch(testapp): + """ + Tries to post an item to a type with mismatched traversal and name key + which will fail + """ + url = '/testing-keys-mismatch' + item = { + 'name': 'Orange', + 'grouping': 'fruit', + 'obj_id': '123', + 'system_id': 'abc' + } + with pytest.raises(KeyError): # error in embed.py:181 + testapp.post_json(url, item) diff --git a/src/snovault/tests/test_views.py b/src/snovault/tests/test_views.py index 98b697cf5..4a2eff844 100644 --- a/src/snovault/tests/test_views.py +++ b/src/snovault/tests/test_views.py @@ -153,7 +153,7 @@ def test_jsonld_context(testapp): @pytest.mark.slow -@pytest.mark.parametrize('item_type', [k for k in PARAMETERIZED_NAMES if k != 'TestingKeys']) +@pytest.mark.parametrize('item_type', PARAMETERIZED_NAMES) def test_index_data_workbook(testapp, indexer_testapp, item_type): res = testapp.get('/%s?limit=all' % item_type).follow(status=200) for item in res.json['@graph']: diff --git a/src/snovault/tests/testing_key.py b/src/snovault/tests/testing_key.py index c0bf05191..b780bc157 100644 --- a/src/snovault/tests/testing_key.py +++ b/src/snovault/tests/testing_key.py @@ -16,7 +16,7 @@ def includeme(config): 'title': 'Test keys', 'description': 'Testing. Testing. 1, 2, 3.', }, - unique_key='testing_accession', + traversal_key='testing_accession', ) class TestingKey(Item): item_type = 'testing_key' diff --git a/src/snovault/tests/testing_views.py b/src/snovault/tests/testing_views.py index 6ffb3ed43..c1151e61d 100644 --- a/src/snovault/tests/testing_views.py +++ b/src/snovault/tests/testing_views.py @@ -174,12 +174,12 @@ class Item(BaseItem): @property def __name__(self): - if self.type_info.name_key is None: + if self.name_key is None: return self.uuid properties = self.upgrade_properties() if properties.get('status') == 'replaced': return self.uuid - return properties.get(self.type_info.name_key, None) or self.uuid + return properties.get(self.name_key, None) or self.uuid def __acl__(self): # Don't finalize to avoid validation here. @@ -275,7 +275,7 @@ def edit_json(context, request): @abstract_collection( name='abstractItemTests', - unique_key='accession', + traversal_key='accession', properties={ 'title': "AbstractItemTests", 'description': "Abstract Item that is inherited for testing", @@ -283,11 +283,12 @@ def edit_json(context, request): class AbstractItemTest(Item): item_type = 'AbstractItemTest' base_types = ['AbstractItemTest'] + Item.base_types + name_key = 'accession' @collection( name='abstract-item-test-sub-items', - unique_key='accession', + traversal_key='accession', properties={ 'title': "AbstractItemTestSubItems", 'description': "Item based off of AbstractItemTest" @@ -299,7 +300,7 @@ class AbstractItemTestSubItem(AbstractItemTest): @collection( name='abstract-item-test-second-sub-items', - unique_key='accession', + traversal_key='accession', properties={ 'title': 'AbstractItemTestSecondSubItems', 'description': "Second item based off of AbstractItemTest" @@ -311,7 +312,7 @@ class AbstractItemTestSecondSubItem(AbstractItemTest): @collection( name='embedding-tests', - unique_key='accession', + traversal_key='accession', properties={ 'title': 'EmbeddingTests', 'description': 'Listing of EmbeddingTests' @@ -319,6 +320,7 @@ class AbstractItemTestSecondSubItem(AbstractItemTest): class EmbeddingTest(Item): item_type = 'embedding_test' schema = load_schema('snovault:test_schemas/EmbeddingTest.json') + name_key = 'accession' # use TestingDownload to test embedded_list = [ @@ -337,7 +339,7 @@ class TestingDownload(ItemWithAttachment): schema = load_schema('snovault:test_schemas/TestingDownload.json') -@collection('testing-link-sources-sno', unique_key='testing_link_sources-sno:name') +@collection('testing-link-sources-sno', traversal_key='testing_link_sources-sno:name') class TestingLinkSourceSno(Item): item_type = 'testing_link_source_sno' schema = load_schema('snovault:test_schemas/TestingLinkSourceSno.json') @@ -352,10 +354,11 @@ class TestingLinkAggregateSno(Item): } -@collection('testing-link-targets-sno', unique_key='testing_link_target_sno:name') +@collection('testing-link-targets-sno', traversal_key='testing_link_target_sno:name') class TestingLinkTargetSno(Item): item_type = 'testing_link_target_sno' schema = load_schema('snovault:test_schemas/TestingLinkTargetSno.json') + name_key = 'name' rev = { 'reverse': ('TestingLinkSourceSno', 'target'), } @@ -414,25 +417,39 @@ class TestingKeys(Item): schema = load_schema('snovault:test_schemas/TestingKeys.json') -@collection('testing-keys-def') +@collection('testing-keys-def', traversal_key='testing_keys_def:obj_id') class TestingKeysDef(Item): """ - Intended to test the behavior of the unique_key setting when it overlaps with - uniqueKey setting in the schema + Intended to test the behavior of setting a traversal key equal to one of the + uniqueKey's specified in the schema. This should allow us to get the object + via obj_id whereas before we could not. """ item_type = 'testing_keys_def' schema = load_schema('snovault:test_schemas/TestingKeys.json') -@collection('testing-keys-name') +@collection('testing-keys-name', traversal_key='testing_keys_name:name') class TestingKeysName(Item): """ - We set obj_id as a unique key so that it can be used as a name_key in the + We set name as a traversal key so that it can be used as a name_key in the resource path. We should now see the name key in the @id field instead of the uuid """ item_type = 'testing_keys_name' schema = load_schema('snovault:test_schemas/TestingKeys.json') + name_key = 'name' + + +@collection('testing-keys-mismatch', traversal_key='testing_keys_mismatch:name') +class TestingKeysMismatch(Item): + """ + Tests behavior when we set a traversal key to one value and name key to + another. In this case posting anything should fail since the traversal key + and name key must match. + """ + item_type = 'testing_keys_mismatch' + schema = load_schema('snovault:test_schemas/TestingKeys.json') + name_key = 'obj_id' @view_config(name='testing-render-error', request_method='GET') From 1155d527ca8a983037bcc994ee7dd7647e2e5e05 Mon Sep 17 00:00:00 2001 From: William Ronchetti Date: Fri, 22 Nov 2019 08:45:33 -0500 Subject: [PATCH 3/8] bump version --- src/snovault/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snovault/_version.py b/src/snovault/_version.py index cbda5a75f..624dff9a8 100644 --- a/src/snovault/_version.py +++ b/src/snovault/_version.py @@ -1,4 +1,4 @@ """Version information.""" # The following line *must* be the last in the module, exactly as formatted: -__version__ = "1.3.4" +__version__ = "1.3.5" From a9ed036097d021a35fcaf939dd1e65d9ee572f1f Mon Sep 17 00:00:00 2001 From: William Ronchetti Date: Fri, 22 Nov 2019 11:30:22 -0500 Subject: [PATCH 4/8] sort actions --- src/snovault/resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snovault/resources.py b/src/snovault/resources.py index 243eb5d74..cb8e627ee 100644 --- a/src/snovault/resources.py +++ b/src/snovault/resources.py @@ -76,7 +76,7 @@ def jsonld_context(self, request): def actions(self, request): actions = calculate_properties(self, request, category='action') if actions: - return list(actions.values()) + return sorted(list(actions.values())) class Root(Resource): From a8d7e63ec72d0ef17eb52dd07ab277a137d5be3a Mon Sep 17 00:00:00 2001 From: William Ronchetti Date: Fri, 22 Nov 2019 11:35:49 -0500 Subject: [PATCH 5/8] small doc update --- docs/source/keys.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/keys.rst b/docs/source/keys.rst index 4963ad085..588065da7 100644 --- a/docs/source/keys.rst +++ b/docs/source/keys.rst @@ -39,13 +39,14 @@ These two keys are bundled together because they are closely related. A unique k Name Key ^^^^^^^^ -The name key is a special field specified on the item type definition. It augments the resource path so that the '@id' field of the item contains a path using the name_key instead of the uuid. See final example below. +The name key is a special field specified on the item type definition. It augments the resource path so that the '@id' field of the item contains a path using the name_key instead of the uuid. To be explicit, the name key must match the traversal key and is only really useful for changing the resource path. See final example below. .. code-block:: python # In this case we specify matching traversal_key and name_key. This means that # the resource path is augmented to show the name_key instead of the uuid AND # you can get the item via resource path ie: Get /testing-keys-name/ + # and that is what always shows up when you access that item @collection('testing-keys-name', traversal_key='testing_keys_name:name') class TestingKeysName(Item): """ From d110fbd774726849f898a92efa409e7e7a75d756 Mon Sep 17 00:00:00 2001 From: William Ronchetti Date: Fri, 22 Nov 2019 11:47:18 -0500 Subject: [PATCH 6/8] dict fix --- src/snovault/resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snovault/resources.py b/src/snovault/resources.py index cb8e627ee..270be2f95 100644 --- a/src/snovault/resources.py +++ b/src/snovault/resources.py @@ -76,7 +76,7 @@ def jsonld_context(self, request): def actions(self, request): actions = calculate_properties(self, request, category='action') if actions: - return sorted(list(actions.values())) + return sorted(list(actions.values()), key=lambda d: d.get('name')) class Root(Resource): From 30e5072f73bbf1d1f5d3e9452b7e803c8fbfa3f4 Mon Sep 17 00:00:00 2001 From: William Ronchetti Date: Mon, 25 Nov 2019 14:13:53 -0500 Subject: [PATCH 7/8] revert name_key schema prop, remove paths from create_mapping --- src/snovault/elasticsearch/create_mapping.py | 5 ----- src/snovault/typeinfo.py | 5 ----- 2 files changed, 10 deletions(-) diff --git a/src/snovault/elasticsearch/create_mapping.py b/src/snovault/elasticsearch/create_mapping.py index 1e5eb0b78..04f38376d 100644 --- a/src/snovault/elasticsearch/create_mapping.py +++ b/src/snovault/elasticsearch/create_mapping.py @@ -474,11 +474,6 @@ def es_mapping(mapping, agg_items_mapping): 'type': 'object', 'include_in_all': False }, - 'paths': { - 'type': 'keyword', - 'ignore_above': KW_IGNORE_ABOVE, - 'include_in_all': False - }, 'indexing_stats': { 'type': 'object', 'include_in_all': False diff --git a/src/snovault/typeinfo.py b/src/snovault/typeinfo.py index adf89930e..a737dbe6a 100644 --- a/src/snovault/typeinfo.py +++ b/src/snovault/typeinfo.py @@ -151,11 +151,6 @@ def schema(self): schema['properties'][name] = prop.schema return schema - @reify - def name_key(self): - schema = self.factory.schema - return schema.get('name_key', None) - class TypesTool(object): """ From 45a1bccd5cc8d7a5f0f23cfe979f25d4de121245 Mon Sep 17 00:00:00 2001 From: Will Ronchetti Date: Tue, 3 Dec 2019 14:39:00 -0500 Subject: [PATCH 8/8] traversal -> identification --- docs/source/keys.rst | 29 +++++++++++++-------- src/snovault/_version.py | 2 +- src/snovault/elasticsearch/indexer_utils.py | 2 +- src/snovault/resources.py | 14 +++++----- src/snovault/tests/test_keyed_items.py | 4 +-- src/snovault/tests/testing_key.py | 2 +- src/snovault/tests/testing_views.py | 18 ++++++------- 7 files changed, 39 insertions(+), 32 deletions(-) diff --git a/docs/source/keys.rst b/docs/source/keys.rst index 588065da7..b4b34892f 100644 --- a/docs/source/keys.rst +++ b/docs/source/keys.rst @@ -2,13 +2,20 @@ Keys in Snovault ================ -Broadly speaking in the 4DN space there are four different types of 'keys'. 'Unique', 'name' and 'traversal' keys are used in both Fourfront/CGAP and Snovault while 'lookup' keys are used only in FF/CGAP. This document will only touch on the first three. +Broadly speaking in the 4DN space there are four different types of 'keys'. 'Unique', 'name' and 'identifying' keys are used in both Fourfront/CGAP and Snovault while 'lookup' keys are used only in FF/CGAP. This document will only touch on the first three. Below is a small table illustrating where each type of key is defined. -Unique Key and Traversal Key -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +.. csv-table:: + :header: "Schema", "Collection", "Type" + :widths: 10, 20, 10 -These two keys are bundled together because they are closely related. A unique key is denoted in the schema of an item. This is a constraint on the items in the database that says that no two items can share the same value in this field. Note that if there are multiple fields that are marked as uniqueKey's then either one can be used to uniquely identify items. In either case a traversal key must be specified in the collection decorator, which must be one of the unique keys. If a traversal key is not specified you will not be able to lookup items via the resource path with either of their unique keys. See the below examples from the tests. + uniqueKey, identification_key, name_key + + +Unique Key and Identification Key +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These two keys are bundled together because they are closely related. A unique key is denoted in the schema of an item. This is a constraint on the items in the database that says that no two items can share the same value in this field. Note that if there are multiple fields that are marked as uniqueKey's then either one can be used to uniquely identify items. In either case a identification key must be specified in the collection decorator, which must be one of the unique keys. If a identification key is not specified you will not be able to lookup items via the resource path with either of their unique keys. See the below examples from the tests. .. code-block:: python @@ -23,13 +30,13 @@ These two keys are bundled together because they are closely related. A unique k schema = load_schema('snovault:test_schemas/TestingKeys.json') - # In this case we specify the traversal_key to be the obj_id. This allows us to + # In this case we specify the identification_key to be the obj_id. This allows us to # use the resource path to get the item ie: Get /testing-keys-def/ # Note that the resource path is still the uuid - @collection('testing-keys-def', traversal_key='testing_keys_def:obj_id') + @collection('testing-keys-def', identification_key='testing_keys_def:obj_id') class TestingKeysDef(Item): """ - Intended to test the behavior of setting a traversal key equal to one of the + Intended to test the behavior of setting a identification key equal to one of the uniqueKey's specified in the schema. This should allow us to get the object via obj_id whereas before we could not. """ @@ -39,18 +46,18 @@ These two keys are bundled together because they are closely related. A unique k Name Key ^^^^^^^^ -The name key is a special field specified on the item type definition. It augments the resource path so that the '@id' field of the item contains a path using the name_key instead of the uuid. To be explicit, the name key must match the traversal key and is only really useful for changing the resource path. See final example below. +The name key is a special field specified on the item type definition. It augments the resource path so that the '@id' field of the item contains a path using the name_key instead of the uuid. To be explicit, the name key must match the identification key and is only really useful for changing the resource path. See final example below. .. code-block:: python - # In this case we specify matching traversal_key and name_key. This means that + # In this case we specify matching identification_key and name_key. This means that # the resource path is augmented to show the name_key instead of the uuid AND # you can get the item via resource path ie: Get /testing-keys-name/ # and that is what always shows up when you access that item - @collection('testing-keys-name', traversal_key='testing_keys_name:name') + @collection('testing-keys-name', identification_key='testing_keys_name:name') class TestingKeysName(Item): """ - We set name as a traversal key so that it can be used as a name_key in the + We set name as a identification key so that it can be used as a name_key in the resource path. We should now see the name key in the @id field instead of the uuid """ diff --git a/src/snovault/_version.py b/src/snovault/_version.py index 624dff9a8..70dcf2ade 100644 --- a/src/snovault/_version.py +++ b/src/snovault/_version.py @@ -1,4 +1,4 @@ """Version information.""" # The following line *must* be the last in the module, exactly as formatted: -__version__ = "1.3.5" +__version__ = "1.3.6" diff --git a/src/snovault/elasticsearch/indexer_utils.py b/src/snovault/elasticsearch/indexer_utils.py index b282e0b91..3a4dc49b6 100644 --- a/src/snovault/elasticsearch/indexer_utils.py +++ b/src/snovault/elasticsearch/indexer_utils.py @@ -12,7 +12,7 @@ def get_namespaced_index(config, index): settings = config.registry.settings except: # accept either config or registry as first arg settings = config.settings - namespace = settings.get('indexer.namespace', '') + namespace = settings.get('indexer.namespace') or '' return namespace + index diff --git a/src/snovault/resources.py b/src/snovault/resources.py index 270be2f95..dcb7bcd91 100644 --- a/src/snovault/resources.py +++ b/src/snovault/resources.py @@ -142,12 +142,12 @@ class AbstractCollection(Resource, Mapping): And some other info as well. Collections allow retrieval of specific items with them by using the `get` - method with uuid or the traversal_key (which must be a unique key) + method with uuid or the identification_key (which must be a unique key) """ properties = {} - traversal_key = None + identification_key = None - def __init__(self, registry, name, type_info, properties=None, acl=None, traversal_key=None): + def __init__(self, registry, name, type_info, properties=None, acl=None, identification_key=None): self.registry = registry self.__name__ = name self.type_info = type_info @@ -155,8 +155,8 @@ def __init__(self, registry, name, type_info, properties=None, acl=None, travers self.properties = properties if acl is not None: self.__acl__ = acl - if traversal_key is not None: - self.traversal_key = traversal_key + if identification_key is not None: + self.identification_key = identification_key @reify def connection(self): @@ -200,8 +200,8 @@ def get(self, name, default=None): if not self._allow_contained(resource): return default return resource - if self.traversal_key is not None: - resource = self.connection.get_by_unique_key(self.traversal_key, name) + if self.identification_key is not None: + resource = self.connection.get_by_unique_key(self.identification_key, name) if resource is not None: if not self._allow_contained(resource): return default diff --git a/src/snovault/tests/test_keyed_items.py b/src/snovault/tests/test_keyed_items.py index 819b3d1cc..d9f778280 100644 --- a/src/snovault/tests/test_keyed_items.py +++ b/src/snovault/tests/test_keyed_items.py @@ -100,7 +100,7 @@ def test_definition_unique_key(TestKeyDefinition, testapp): testapp.post_json(url, duplicate_system_id, status=201) # obj_id should succeed since it is a unique_key resp = testapp.get(url + '/' + duplicate_system_id['obj_id']).follow(status=200).json - # since obj_id is a traversal_key but not a name_key we still expect uuid + # since obj_id is a identification_key but not a name_key we still expect uuid assert resp['uuid'] in resp['@id'] # should fail since name is not a name_key testapp.get(url + '/' + duplicate_system_id['name'], status=404) @@ -132,7 +132,7 @@ def test_name_key(TestKeyName, testapp): testapp.get(url + '/' + correct['obj_id'], status=404) -def test_name_key_traversal_key_mismatch(testapp): +def test_name_key_identification_key_mismatch(testapp): """ Tries to post an item to a type with mismatched traversal and name key which will fail diff --git a/src/snovault/tests/testing_key.py b/src/snovault/tests/testing_key.py index b780bc157..23f35fb10 100644 --- a/src/snovault/tests/testing_key.py +++ b/src/snovault/tests/testing_key.py @@ -16,7 +16,7 @@ def includeme(config): 'title': 'Test keys', 'description': 'Testing. Testing. 1, 2, 3.', }, - traversal_key='testing_accession', + identification_key='testing_accession', ) class TestingKey(Item): item_type = 'testing_key' diff --git a/src/snovault/tests/testing_views.py b/src/snovault/tests/testing_views.py index c1151e61d..e3f84e6f7 100644 --- a/src/snovault/tests/testing_views.py +++ b/src/snovault/tests/testing_views.py @@ -275,7 +275,7 @@ def edit_json(context, request): @abstract_collection( name='abstractItemTests', - traversal_key='accession', + identification_key='accession', properties={ 'title': "AbstractItemTests", 'description': "Abstract Item that is inherited for testing", @@ -288,7 +288,7 @@ class AbstractItemTest(Item): @collection( name='abstract-item-test-sub-items', - traversal_key='accession', + identification_key='accession', properties={ 'title': "AbstractItemTestSubItems", 'description': "Item based off of AbstractItemTest" @@ -300,7 +300,7 @@ class AbstractItemTestSubItem(AbstractItemTest): @collection( name='abstract-item-test-second-sub-items', - traversal_key='accession', + identification_key='accession', properties={ 'title': 'AbstractItemTestSecondSubItems', 'description': "Second item based off of AbstractItemTest" @@ -312,7 +312,7 @@ class AbstractItemTestSecondSubItem(AbstractItemTest): @collection( name='embedding-tests', - traversal_key='accession', + identification_key='accession', properties={ 'title': 'EmbeddingTests', 'description': 'Listing of EmbeddingTests' @@ -339,7 +339,7 @@ class TestingDownload(ItemWithAttachment): schema = load_schema('snovault:test_schemas/TestingDownload.json') -@collection('testing-link-sources-sno', traversal_key='testing_link_sources-sno:name') +@collection('testing-link-sources-sno', identification_key='testing_link_sources-sno:name') class TestingLinkSourceSno(Item): item_type = 'testing_link_source_sno' schema = load_schema('snovault:test_schemas/TestingLinkSourceSno.json') @@ -354,7 +354,7 @@ class TestingLinkAggregateSno(Item): } -@collection('testing-link-targets-sno', traversal_key='testing_link_target_sno:name') +@collection('testing-link-targets-sno', identification_key='testing_link_target_sno:name') class TestingLinkTargetSno(Item): item_type = 'testing_link_target_sno' schema = load_schema('snovault:test_schemas/TestingLinkTargetSno.json') @@ -417,7 +417,7 @@ class TestingKeys(Item): schema = load_schema('snovault:test_schemas/TestingKeys.json') -@collection('testing-keys-def', traversal_key='testing_keys_def:obj_id') +@collection('testing-keys-def', identification_key='testing_keys_def:obj_id') class TestingKeysDef(Item): """ Intended to test the behavior of setting a traversal key equal to one of the @@ -428,7 +428,7 @@ class TestingKeysDef(Item): schema = load_schema('snovault:test_schemas/TestingKeys.json') -@collection('testing-keys-name', traversal_key='testing_keys_name:name') +@collection('testing-keys-name', identification_key='testing_keys_name:name') class TestingKeysName(Item): """ We set name as a traversal key so that it can be used as a name_key in the @@ -440,7 +440,7 @@ class TestingKeysName(Item): name_key = 'name' -@collection('testing-keys-mismatch', traversal_key='testing_keys_mismatch:name') +@collection('testing-keys-mismatch', identification_key='testing_keys_mismatch:name') class TestingKeysMismatch(Item): """ Tests behavior when we set a traversal key to one value and name key to