diff --git a/beat/backend/python/database.py b/beat/backend/python/database.py index c8104406230411a384172d69e646cb65f68dbc65..c6d2fa76b43fd1aa8b730f2d6e362126a70ff047 100644 --- a/beat/backend/python/database.py +++ b/beat/backend/python/database.py @@ -392,6 +392,12 @@ class Database(object): return not bool(self.errors) + @property + def environment(self): + """Returns the run environment if any has been set""" + + return self.data.get("environment") + @property def protocols(self): """The declaration of all the protocols of the database""" diff --git a/beat/backend/python/test/prefix/databases/integers_db_env/1.json b/beat/backend/python/test/prefix/databases/integers_db_env/1.json new file mode 100644 index 0000000000000000000000000000000000000000..71a4ad1b84ce7a9bbb6186a07d7cb6e3755959f7 --- /dev/null +++ b/beat/backend/python/test/prefix/databases/integers_db_env/1.json @@ -0,0 +1,99 @@ +{ + "root_folder": "/tmp/path/not/set", + "environment": { + "name": "Example databases", + "version": "1.4.0" + }, + "protocols": [ + { + "name": "double", + "template": "double", + "sets": [ + { + "name": "double", + "template": "double", + "view": "Double", + "outputs": { + "a": "user/single_integer/1", + "b": "user/single_integer/1", + "sum": "user/single_integer/1" + } + } + ] + }, + { + "name": "triple", + "template": "triple", + "sets": [ + { + "name": "triple", + "view": "Triple", + "template": "triple", + "outputs": { + "a": "user/single_integer/1", + "b": "user/single_integer/1", + "c": "user/single_integer/1", + "sum": "user/single_integer/1" + } + } + ] + }, + { + "name": "two_sets", + "template": "two_sets", + "sets": [ + { + "name": "double", + "template": "double", + "view": "Double", + "outputs": { + "a": "user/single_integer/1", + "b": "user/single_integer/1", + "sum": "user/single_integer/1" + } + }, + { + "name": "triple", + "template": "triple", + "view": "Triple", + "outputs": { + "a": "user/single_integer/1", + "b": "user/single_integer/1", + "c": "user/single_integer/1", + "sum": "user/single_integer/1" + } + } + ] + }, + { + "name": "labelled", + "template": "labelled", + "sets": [ + { + "name": "labelled", + "template": "labelled", + "view": "Labelled", + "outputs": { + "value": "user/single_integer/1", + "label": "user/single_string/1" + } + } + ] + }, + { + "name": "different_frequencies", + "template": "different_frequencies", + "sets": [ + { + "name": "double", + "template": "double", + "view": "DifferentFrequencies", + "outputs": { + "a": "user/single_integer/1", + "b": "user/single_integer/1" + } + } + ] + } + ] +} diff --git a/beat/backend/python/test/prefix/databases/integers_db_env/1.py b/beat/backend/python/test/prefix/databases/integers_db_env/1.py new file mode 100755 index 0000000000000000000000000000000000000000..0ebebd6c4debec559f8a853830073e92a8b71bd5 --- /dev/null +++ b/beat/backend/python/test/prefix/databases/integers_db_env/1.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : + +################################################################################### +# # +# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ # +# Contact: beat.support@idiap.ch # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met: # +# # +# 1. Redistributions of source code must retain the above copyright notice, this # +# list of conditions and the following disclaimer. # +# # +# 2. Redistributions in binary form must reproduce the above copyright notice, # +# this list of conditions and the following disclaimer in the documentation # +# and/or other materials provided with the distribution. # +# # +# 3. Neither the name of the copyright holder nor the names of its contributors # +# may be used to endorse or promote products derived from this software without # +# specific prior written permission. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +# # +################################################################################### + + +import numpy +from collections import namedtuple +from beat.backend.python.database import View + + +class Double(View): + def index(self, root_folder, parameters): + Entry = namedtuple("Entry", ["a", "b", "sum"]) + + return [ + Entry(1, 10, 11), + Entry(2, 20, 22), + Entry(3, 30, 33), + Entry(4, 40, 44), + Entry(5, 50, 55), + Entry(6, 60, 66), + Entry(7, 70, 77), + Entry(8, 80, 88), + Entry(9, 90, 99), + ] + + def get(self, output, index): + obj = self.objs[index] + + if output == "a": + return {"value": numpy.int32(obj.a)} + + elif output == "b": + return {"value": numpy.int32(obj.b)} + + elif output == "sum": + return {"value": numpy.int32(obj.sum)} + elif output == "class": + return {"value": numpy.int32(obj.cls)} + + +# ---------------------------------------------------------- + + +class Triple(View): + def index(self, root_folder, parameters): + Entry = namedtuple("Entry", ["a", "b", "c", "sum"]) + + return [ + Entry(1, 10, 100, 111), + Entry(2, 20, 200, 222), + Entry(3, 30, 300, 333), + Entry(4, 40, 400, 444), + Entry(5, 50, 500, 555), + Entry(6, 60, 600, 666), + Entry(7, 70, 700, 777), + Entry(8, 80, 800, 888), + Entry(9, 90, 900, 999), + ] + + def get(self, output, index): + obj = self.objs[index] + + if output == "a": + return {"value": numpy.int32(obj.a)} + + elif output == "b": + return {"value": numpy.int32(obj.b)} + + elif output == "c": + return {"value": numpy.int32(obj.c)} + + elif output == "sum": + return {"value": numpy.int32(obj.sum)} + + +# ---------------------------------------------------------- + + +class Labelled(View): + def index(self, root_folder, parameters): + Entry = namedtuple("Entry", ["label", "value"]) + + return [ + Entry("A", 1), + Entry("A", 2), + Entry("A", 3), + Entry("A", 4), + Entry("A", 5), + Entry("B", 10), + Entry("B", 20), + Entry("B", 30), + Entry("B", 40), + Entry("B", 50), + Entry("C", 100), + Entry("C", 200), + Entry("C", 300), + Entry("C", 400), + Entry("C", 500), + ] + + def get(self, output, index): + obj = self.objs[index] + + if output == "label": + return {"value": obj.label} + + elif output == "value": + return {"value": numpy.int32(obj.value)} + + +# ---------------------------------------------------------- + + +class DifferentFrequencies(View): + def index(self, root_folder, parameters): + Entry = namedtuple("Entry", ["a", "b"]) + + return [ + Entry(1, 10), + Entry(1, 20), + Entry(1, 30), + Entry(1, 40), + Entry(2, 50), + Entry(2, 60), + Entry(2, 70), + Entry(2, 80), + ] + + def get(self, output, index): + obj = self.objs[index] + + if output == "a": + return {"value": numpy.int32(obj.a)} + + elif output == "b": + return {"value": numpy.int32(obj.b)} diff --git a/beat/backend/python/test/prefix/databases/integers_db_env/2.json b/beat/backend/python/test/prefix/databases/integers_db_env/2.json new file mode 100644 index 0000000000000000000000000000000000000000..636da24bf0f79fcdb0c7a4fe333356152fa6809f --- /dev/null +++ b/beat/backend/python/test/prefix/databases/integers_db_env/2.json @@ -0,0 +1,58 @@ +{ + "schema_version": 2, + "environment": { + "name": "Example databases", + "version": "1.4.0" + }, + "root_folder": "/tmp/path/not/set", + "protocols": [ + { + "name": "double", + "template": "double/1", + "views": { + "double": { + "view": "Double" + } + } + }, + { + "name": "triple", + "template": "triple/1", + "views": { + "triple": { + "view": "Triple" + } + } + }, + { + "name": "two_sets", + "template": "two_sets/1", + "views": { + "double": { + "view": "Double" + }, + "triple": { + "view": "Triple" + } + } + }, + { + "name": "labelled", + "template": "labelled/1", + "views": { + "labelled": { + "view": "Labelled" + } + } + }, + { + "name": "different_frequencies", + "template": "different_frequencies/1", + "views": { + "double" : { + "view": "DifferentFrequencies" + } + } + } + ] +} diff --git a/beat/backend/python/test/prefix/databases/integers_db_env/2.py b/beat/backend/python/test/prefix/databases/integers_db_env/2.py new file mode 100644 index 0000000000000000000000000000000000000000..0ebebd6c4debec559f8a853830073e92a8b71bd5 --- /dev/null +++ b/beat/backend/python/test/prefix/databases/integers_db_env/2.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python +# vim: set fileencoding=utf-8 : + +################################################################################### +# # +# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ # +# Contact: beat.support@idiap.ch # +# # +# Redistribution and use in source and binary forms, with or without # +# modification, are permitted provided that the following conditions are met: # +# # +# 1. Redistributions of source code must retain the above copyright notice, this # +# list of conditions and the following disclaimer. # +# # +# 2. Redistributions in binary form must reproduce the above copyright notice, # +# this list of conditions and the following disclaimer in the documentation # +# and/or other materials provided with the distribution. # +# # +# 3. Neither the name of the copyright holder nor the names of its contributors # +# may be used to endorse or promote products derived from this software without # +# specific prior written permission. # +# # +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +# # +################################################################################### + + +import numpy +from collections import namedtuple +from beat.backend.python.database import View + + +class Double(View): + def index(self, root_folder, parameters): + Entry = namedtuple("Entry", ["a", "b", "sum"]) + + return [ + Entry(1, 10, 11), + Entry(2, 20, 22), + Entry(3, 30, 33), + Entry(4, 40, 44), + Entry(5, 50, 55), + Entry(6, 60, 66), + Entry(7, 70, 77), + Entry(8, 80, 88), + Entry(9, 90, 99), + ] + + def get(self, output, index): + obj = self.objs[index] + + if output == "a": + return {"value": numpy.int32(obj.a)} + + elif output == "b": + return {"value": numpy.int32(obj.b)} + + elif output == "sum": + return {"value": numpy.int32(obj.sum)} + elif output == "class": + return {"value": numpy.int32(obj.cls)} + + +# ---------------------------------------------------------- + + +class Triple(View): + def index(self, root_folder, parameters): + Entry = namedtuple("Entry", ["a", "b", "c", "sum"]) + + return [ + Entry(1, 10, 100, 111), + Entry(2, 20, 200, 222), + Entry(3, 30, 300, 333), + Entry(4, 40, 400, 444), + Entry(5, 50, 500, 555), + Entry(6, 60, 600, 666), + Entry(7, 70, 700, 777), + Entry(8, 80, 800, 888), + Entry(9, 90, 900, 999), + ] + + def get(self, output, index): + obj = self.objs[index] + + if output == "a": + return {"value": numpy.int32(obj.a)} + + elif output == "b": + return {"value": numpy.int32(obj.b)} + + elif output == "c": + return {"value": numpy.int32(obj.c)} + + elif output == "sum": + return {"value": numpy.int32(obj.sum)} + + +# ---------------------------------------------------------- + + +class Labelled(View): + def index(self, root_folder, parameters): + Entry = namedtuple("Entry", ["label", "value"]) + + return [ + Entry("A", 1), + Entry("A", 2), + Entry("A", 3), + Entry("A", 4), + Entry("A", 5), + Entry("B", 10), + Entry("B", 20), + Entry("B", 30), + Entry("B", 40), + Entry("B", 50), + Entry("C", 100), + Entry("C", 200), + Entry("C", 300), + Entry("C", 400), + Entry("C", 500), + ] + + def get(self, output, index): + obj = self.objs[index] + + if output == "label": + return {"value": obj.label} + + elif output == "value": + return {"value": numpy.int32(obj.value)} + + +# ---------------------------------------------------------- + + +class DifferentFrequencies(View): + def index(self, root_folder, parameters): + Entry = namedtuple("Entry", ["a", "b"]) + + return [ + Entry(1, 10), + Entry(1, 20), + Entry(1, 30), + Entry(1, 40), + Entry(2, 50), + Entry(2, 60), + Entry(2, 70), + Entry(2, 80), + ] + + def get(self, output, index): + obj = self.objs[index] + + if output == "a": + return {"value": numpy.int32(obj.a)} + + elif output == "b": + return {"value": numpy.int32(obj.b)} diff --git a/beat/backend/python/test/test_database.py b/beat/backend/python/test/test_database.py index 3b68145b78de1da15c63eff41cd444a87032ea2d..7720fce7a68b4c60d91cb5e13c1d5c72a8744df2 100644 --- a/beat/backend/python/test/test_database.py +++ b/beat/backend/python/test/test_database.py @@ -161,3 +161,23 @@ def test_duplicate_key_error(): database = Database(prefix, "duplicate_key_error/1") nose.tools.assert_false(database.valid) nose.tools.assert_true("Database declaration file invalid" in database.errors[0]) + + +# ---------------------------------------------------------- + +REFERENCE_ENVIRONMENT = {"name": "Example databases", "version": "1.4.0"} + + +def test_envionment_requirement(): + for db_name in INTEGERS_DBS: + yield compare_environment, db_name, None + + for index in range(1, 3): + yield compare_environment, "integers_db_env/{}".format( + index + ), REFERENCE_ENVIRONMENT + + +def compare_environment(db_name, environment): + db = load(db_name) + nose.tools.assert_equal(db.environment, environment)