diff --git a/BUILD b/BUILD index 4b5d3ebfa..c9ca21801 100644 --- a/BUILD +++ b/BUILD @@ -1,41 +1,97 @@ +load("@rules_python//python:defs.bzl", "py_binary") +load("@rules_python//python:defs.bzl", "py_library") +load("@rules_python//python:defs.bzl", "py_test") + +load("@knora_py_deps//:requirements.bzl", "requirement") + py_library( name = "knora", srcs = glob(["knora/knora.py"]), + deps = [ + requirement("rdflib"), + requirement("lxml"), + requirement("validators"), + requirement("requests"), + requirement("jsonschema"), + requirement("click"), + requirement("rfc3987"), + requirement("pprint"), + ] ) py_binary( name = "knora_create_ontology", srcs = ["knora/create_ontology.py"], - deps = ["knora"] + deps = [ + "knora", + requirement("jsonschema"), + requirement("pprint"), + ] ) py_binary( - name = "knora-xml-import", + name = "knora_xml_import", srcs = ["knora/xml2knora.py"], - deps = ["knora"] + deps = [ + "knora", + requirement("lxml"), + requirement("pprint"), + ], ) py_binary( - name = "knora-reset-triplestore", + name = "knora_reset_triplestore", srcs = ["knora/reset_triplestore.py"], - deps = ["knora"] + deps = [":knora"], ) py_binary( name = "knoractl", srcs = ["knora/knoractl.py"], - deps = ["knora"] + deps = [":knora"], +) + +py_test( + name = "test_create_ontology", + srcs = ["test/test_create_ontology.py"], + deps = [":knora"], +) + +py_test( + name = "test_create_resource", + srcs = ["test/test_create_resource.py"], + deps = [ + ":knora", + requirement("pprint"), + ], ) py_test( name = "test_knora", - srcs = ["tests/test_knora.py"], - deps = ["knora"], + srcs = ["test/test_knora.py"], + deps = [":knora"], ) test_suite( name = "all_tests", tests = [ + "test_create_ontology", + "test_create_resource", "test_knora", ], ) + +py_library( + name = "test_lib", + srcs = glob(["test/*.py"]), + deps = [ + ":knora", + ], +) + +py_binary( + name = "run_tests", + main = "test/run.py", + srcs = ["test/run.py"], + deps = ["test_lib"], +) diff --git a/Makefile b/Makefile index 9035f41c7..fcd8b5d9e 100644 --- a/Makefile +++ b/Makefile @@ -16,8 +16,13 @@ serve-docs: ## serve docs for local viewing publish-docs: ## build and publish docs to Github Pages mkdocs gh-deploy +.PHONY: install-requirements +install-requirements: ## install requirements + pip3 install -r requirements.txt + +.PHONY: test test: ## runs all tests - python3 -m pytest + cd test && python3 -m unittest clean: ## cleans the project directory rm -rf dist/ build/ knora.egg-info/ .pytest_cache/ site/ diff --git a/README.md b/README.md index 0c4e0edd7..803fae6b0 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,13 @@ $ python3 setup.py install The project contains a Makefile defining management tasks. Please use `make help` to see what is available. +## Testing + +```bash +$ make install-requirements +$ make test +``` + ## Publishing to PyPi Generate distribution package. Make sure you have the latest versions of `setuptools` and `wheel` installed: @@ -70,15 +77,7 @@ $ python3 -m twine upload dist/* For local development: ```bash -$ python3 setup.py develop -``` - -## Testing - -```bash -$ pip3 install pytest -$ pip3 install --editable . -$ pytest +$ python3 setup.py --editable . ``` ## Requirements diff --git a/WORKSPACE b/WORKSPACE index e69de29bb..6b66dc41e 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -0,0 +1,42 @@ +workspace(name = "knora_py") + +# use bazel federation (set of rule versions known to work well together) +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +http_archive( + name = "bazel_federation", + url = "https://github.com/bazelbuild/bazel-federation/releases/download/0.0.1/bazel_federation-0.0.1.tar.gz", + sha256 = "506dfbfd74ade486ac077113f48d16835fdf6e343e1d4741552b450cfc2efb53", +) + +# load the initializer methods for all the rules we want to use in this workspace +load("@bazel_federation//:repositories.bzl", + "rules_python", +) + +# run any rule specific setups +rules_python() +load("@bazel_federation//setup:rules_python.bzl", "rules_python_setup") +rules_python_setup() + +# load py_repositories from rules_python +load("@rules_python//python:repositories.bzl", "py_repositories") +py_repositories() + +# load pip_repositories from rules_python +load("@rules_python//python:pip.bzl", "pip_repositories") +pip_repositories() + +# allows to use requirements.txt for loading the dependencies +load("@rules_python//python:pip.bzl", "pip_import") + +# This rule translates the specified requirements.txt into +# @knora_py_deps//:requirements.bzl, which itself exposes a pip_install method. +pip_import( + name = "knora_py_deps", + requirements = "//:requirements.txt", +) + +# Load the pip_install symbol for knora_py_deps, and create the dependencies' +# repositories. +load("@knora_py_deps//:requirements.bzl", "pip_install") +pip_install() diff --git a/knora/knora.py b/knora/knora.py index 73e96f55e..7e8d051d0 100755 --- a/knora/knora.py +++ b/knora/knora.py @@ -116,6 +116,7 @@ def __init__(self, server: str, prefixes: Dict[str, str] = None): """ self.server = server self.prefixes = prefixes + self.token = None def login(self, email: str, password: str): """ @@ -142,12 +143,13 @@ def get_token(self): return self.token def logout(self): - req = requests.delete( - self.server + '/v2/authentication', - headers={'Authorization': 'Bearer ' + self.token} - ) - self.on_api_error(req) - self.token = None + if self.token is not None: + req = requests.delete( + self.server + '/v2/authentication', + headers={'Authorization': 'Bearer ' + self.token} + ) + self.on_api_error(req) + self.token = None def __del__(self): self.logout() @@ -245,7 +247,7 @@ def create_project( project['logo'] = logo jsondata = json.dumps(project) - print(jsondata) + # print(jsondata) req = requests.post(self.server + "/admin/projects", headers={'Content-Type': 'application/json; charset=UTF-8', diff --git a/requirements.txt b/requirements.txt index ccb6c7442..bc7962d65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,11 @@ twine pytest mkdocs==1.0.4 mkdocs-material +rdflib +lxml +validators +requests +jsonschema +click +rfc3987 +pprint diff --git a/setup.py b/setup.py index 90dd74434..3da16efe5 100644 --- a/setup.py +++ b/setup.py @@ -27,16 +27,16 @@ 'jsonschema', 'click', 'rfc3987', - 'pprint' + 'pprint', ], entry_points={ 'console_scripts': [ 'knora-create-ontology=knora.create_ontology:main', 'knora-xml-import=knora.xml2knora:main', 'knora-reset-triplestore=knora.reset_triplestore:main', - 'knoractl=knoractl:main' + 'knoractl=knoractl:main', ], }, include_package_data=True, - zip_safe=False + zip_safe=False, ) diff --git a/tests/lists.json b/test/lists.json similarity index 100% rename from tests/lists.json rename to test/lists.json diff --git a/test/run.py b/test/run.py new file mode 100644 index 000000000..50206bd33 --- /dev/null +++ b/test/run.py @@ -0,0 +1,55 @@ +"""Universal launcher for unit tests""" + +import argparse +import logging +import os +import sys +import unittest + + +def main(): + """Parse args, collect tests and run them""" + # Disable *.pyc files + sys.dont_write_bytecode = True + + # Add ".." to module search path + cur_dir = os.path.dirname(os.path.realpath(__file__)) + top_dir = os.path.abspath(os.path.join(cur_dir, os.pardir)) + sys.path.append(top_dir) + + # Parse command line arguments + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("-v", "--verbose", action="count", default=0, + help="verbosity level, use: [-v | -vv | -vvv]") + parser.add_argument("-s", "--start-directory", default=None, + help="directory to start discovery") + parser.add_argument("-p", "--pattern", default="test*.py", + help="pattern to match test files ('test*.py' default)") + parser.add_argument("test", nargs="*", + help="test specs (e.g. module.TestCase.test_func)") + args = parser.parse_args() + + if not args.start_directory: + args.start_directory = cur_dir + + if args.verbose > 2: + logging.basicConfig(level=logging.DEBUG, format="DEBUG: %(message)s") + + loader = unittest.TestLoader() + if args.test: + # Add particular tests + for test in args.test: + suite = unittest.TestSuite() + suite.addTests(loader.loadTestsFromName(test)) + else: + # Find all tests + suite = loader.discover(args.start_directory, args.pattern) + + runner = unittest.TextTestRunner(verbosity=args.verbose) + result = runner.run(suite) + return result.wasSuccessful() + + +if __name__ == "__main__": + # NOTE: True(success) -> 0, False(fail) -> 1 + exit(not main()) diff --git a/tests/test-onto.json b/test/test-onto.json similarity index 100% rename from tests/test-onto.json rename to test/test-onto.json diff --git a/tests/test.tif b/test/test.tif similarity index 100% rename from tests/test.tif rename to test/test.tif diff --git a/test/test_create_ontology.py b/test/test_create_ontology.py new file mode 100644 index 000000000..0eeebe54d --- /dev/null +++ b/test/test_create_ontology.py @@ -0,0 +1,12 @@ +import unittest + + +class TestCreateOntology(unittest.TestCase): + + @unittest.skip("not implemented") + def test_create_ontology(self): + pass + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_create_resource.py b/test/test_create_resource.py new file mode 100644 index 000000000..52f68302a --- /dev/null +++ b/test/test_create_resource.py @@ -0,0 +1,52 @@ +import unittest +from pprint import pprint +from knora import Knora, Sipi + + +class TestCreateResource(unittest.TestCase): + + @unittest.skip("not implemented") + def test_create_resource(self): + server = "http://0.0.0.0:3333" + sipi = "http://0.0.0.0:1024" + email = "root@example.com" + password = "test" + projectcode = "00FE" + ontoname = "KPT" + + con = Knora(server) + con.login(email, password) + graph = con.get_ontology_graph('00FE', 'kpt') + # print(graph) + # exit(0) + schema = con.create_schema(projectcode, ontoname) + # pprint(schema) + # exit(0) + + inst1_info = con.create_resource(schema, "object1", "obj1_inst1", { + "textprop": "Dies ist ein Text!", + "intprop": 7, + "listprop": "options:opt2", + "dateprop": "1966:CE:1967-05-21", + "decimalprop": {'value': "3.14159", 'comment': "Die Zahl PI"}, + "geonameprop": "2661604", + "richtextprop": "\n

this is text

with standoff
", + "intervalprop": "13.57:15.88" + }) + pprint(inst1_info) + + # first upload image to SIPI + sipi = Sipi(sipi, con.get_token()) + res = sipi.upload_image('test.tif') + pprint(res) + + fileref = res['uploadedFiles'][0]['internalFilename'] + inst2_info = con.create_resource(schema, "object2", "obj2_inst1", { + "titleprop": "Stained glass", + "linkprop": inst1_info['iri'] + }, fileref) + pprint(inst2_info) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/test_knora.py b/test/test_knora.py new file mode 100644 index 000000000..afdbbb9e9 --- /dev/null +++ b/test/test_knora.py @@ -0,0 +1,72 @@ +import unittest +from knora import Knora + + +class TestKnora(unittest.TestCase): + + def con(self, login: bool = True) -> Knora: + server = "http://0.0.0.0:3333" + email = "root@example.com" + password = "test" + # projectcode = "00FE" + # ontoname = "KPT" + con = Knora(server) + if login: + con.login(email, password) + return con + + # resets the content of the triplestore + def test_reset_triplestore_content(self): + res = self.con(login=False).reset_triplestore_content() + self.assertIsNotNone(res) + + # retrieves all users + def test_get_users(self): + res = self.con(login=True).get_users() + # print(res) + self.assertEqual(len(res), 19) + + # retrieves user information + def test_get_user(self): + res = self.con(login=True).get_user_by_iri(user_iri='http://rdfh.ch/users/root') + # print(res) + self.assertEqual(res["username"], "root") + + # creates a user + def test_create_user(self): + connection = self.con(login=True) + user = { + "username": "testtest", + "email": "testtest@example.com", + "given_name": "test_given", + "family_name": "test_family", + "password": "test", + "lang": "en" + } + + user_iri = connection.create_user( + username=user["username"], + email=user["email"], + given_name=user["given_name"], + family_name=user["family_name"], + password=user["password"], + lang=user["lang"] if user.get("lang") is not None else "en") + + # print(user_iri) + + # check that the created user exists + res = connection.get_user_by_iri(user_iri) + self.assertEqual(res["username"], "testtest") + self.assertEqual(res["email"], "testtest@example.com") + + # logout current user + connection.logout() + self.assertIsNone(connection.get_token()) + + # login as newly created user + connection.login(res["email"], "test") + self.assertIsNotNone(connection.get_token()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index fbf9341f8..000000000 --- a/tests/conftest.py +++ /dev/null @@ -1,12 +0,0 @@ -import os - -import pytest - -import knora.create_ontology - - -@pytest.fixture -def create_test_ontology_fixture(): - dir_path = os.path.dirname(os.path.realpath(__file__)) - os.chdir(dir_path) - knora.create_ontology.main(['./test-onto.json']) diff --git a/tests/test_create_ontology.py b/tests/test_create_ontology.py deleted file mode 100644 index 28d2d3b21..000000000 --- a/tests/test_create_ontology.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - - -@pytest.mark.skip(reason="broken") -def test_create_test_onto(create_test_ontology_fixture): - pass - diff --git a/tests/test_create_resource.py b/tests/test_create_resource.py deleted file mode 100644 index d6ae52990..000000000 --- a/tests/test_create_resource.py +++ /dev/null @@ -1,50 +0,0 @@ -import os -from typing import List, Set, Dict, Tuple, Optional -from pprint import pprint -import argparse -import json -from jsonschema import validate -from knora import KnoraError, Knora, Sipi -import pytest - - -@pytest.mark.skip(reason="broken") -def test_create_resource(create_test_ontology_fixture): - server = "http://0.0.0.0:3333" - sipi = "http://0.0.0.0:1024", - user = "root@example.com", - password = "test" - projectcode = "00FE" - ontoname = "KPT" - - con = Knora(server, user, password) - graph = con.get_ontology_graph('00FE', 'kpt') - # print(graph) - # exit(0) - schema = con.create_schema(projectcode, ontoname) - # pprint(schema) - # exit(0) - - inst1_info = con.create_resource(schema, "object1", "obj1_inst1", { - "textprop": "Dies ist ein Text!", - "intprop": 7, - "listprop": "options:opt2", - "dateprop": "1966:CE:1967-05-21", - "decimalprop": {'value': "3.14159", 'comment': "Die Zahl PI"}, - "geonameprop": "2661604", - "richtextprop": "\n

this is text

with standoff
", - "intervalprop": "13.57:15.88" - }) - pprint(inst1_info) - - # first upload image to SIPI - sipi = Sipi(sipi, con.get_token()) - res = sipi.upload_image('test.tif') - pprint(res) - - fileref = res['uploadedFiles'][0]['internalFilename'] - inst2_info = con.create_resource(schema, "object2", "obj2_inst1", { - "titleprop": "Stained glass", - "linkprop": inst1_info['iri'] - }, fileref) - pprint(inst2_info) \ No newline at end of file diff --git a/tests/test_knora.py b/tests/test_knora.py deleted file mode 100644 index e2ed2fde0..000000000 --- a/tests/test_knora.py +++ /dev/null @@ -1,74 +0,0 @@ -import pytest -from knora import Knora - - -@pytest.fixture() -def con(): - def _con(login: bool = True) -> Knora: - server = "http://0.0.0.0:3333" - email = "root@example.com" - password = "test" - # projectcode = "00FE" - # ontoname = "KPT" - con = Knora(server) - if login: - con.login(email, password) - return con - - return _con - - -# resets the content of the triplestore -def test_reset_triplestore_content(con): - res = con(login=False).reset_triplestore_content() - assert res - - -# retrieves all users -def test_get_users(con): - res = con(login=True).get_users() - print(res) - assert (len(res) == 18) - - -# retrieves user information -def test_get_user(con): - res = con(login=True).get_user_by_iri(user_iri='http://rdfh.ch/users/root') - print(res) - assert (res["username"] == "root") - - -# creates a user -def test_create_user(con): - connection = con(login=True) - user = { - "username": "testtest", - "email": "testtest@example.com", - "given_name": "test_given", - "family_name": "test_family", - "password": "test", - "lang": "en" - } - - user_iri = connection.create_user( - username=user["username"], - email=user["email"], - given_name=user["given_name"], - family_name=user["family_name"], - password=user["password"], - lang=user["lang"] if user.get("lang") is not None else "en") - - print(user_iri) - - # check that the created user exists - res = connection.get_user_by_iri(user_iri) - assert (res["username"] == "testtest") - assert (res["email"] == "testtest@example.com") - - # logout - connection.logout() - assert (connection.get_token() is None) - - # login - connection.login(res["email"], "test") - assert (connection.get_token() is not None)