From 593ac85be94c58e4b49e1a9e3b624f7561d48930 Mon Sep 17 00:00:00 2001 From: Ivan Subotic <400790+subotic@users.noreply.github.com> Date: Thu, 10 Oct 2019 19:12:25 +0200 Subject: [PATCH] chore: add runing tests on travis --- .gitignore | 3 + .travis.yml | 10 +++ BUILD | 41 +++++++++++ Makefile | 3 + WORKSPACE | 0 knora/create_ontology.py | 4 +- knora/knora.py | 147 +++++++++++++++++++++------------------ tests/test_knora.py | 36 ++++++---- 8 files changed, 161 insertions(+), 83 deletions(-) create mode 100644 .travis.yml create mode 100644 BUILD create mode 100644 WORKSPACE diff --git a/.gitignore b/.gitignore index 4e3bab257..7f53e0d76 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,6 @@ venv.bak/ .idea .vscode /knora/lists.json + +# bazel +/bazel-* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..f3e2e2bdf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +dist: xenial +sudo: required + +services: + - docker + +script: + - pyenv shell 3.7.1 + - pip3 install -r requirements.txt + - make test diff --git a/BUILD b/BUILD new file mode 100644 index 000000000..4b5d3ebfa --- /dev/null +++ b/BUILD @@ -0,0 +1,41 @@ +py_library( + name = "knora", + srcs = glob(["knora/knora.py"]), +) + +py_binary( + name = "knora_create_ontology", + srcs = ["knora/create_ontology.py"], + deps = ["knora"] +) + +py_binary( + name = "knora-xml-import", + srcs = ["knora/xml2knora.py"], + deps = ["knora"] +) + +py_binary( + name = "knora-reset-triplestore", + srcs = ["knora/reset_triplestore.py"], + deps = ["knora"] +) + +py_binary( + name = "knoractl", + srcs = ["knora/knoractl.py"], + deps = ["knora"] +) + +py_test( + name = "test_knora", + srcs = ["tests/test_knora.py"], + deps = ["knora"], +) + +test_suite( + name = "all_tests", + tests = [ + "test_knora", + ], +) diff --git a/Makefile b/Makefile index b5dc19776..9035f41c7 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,9 @@ serve-docs: ## serve docs for local viewing publish-docs: ## build and publish docs to Github Pages mkdocs gh-deploy +test: ## runs all tests + python3 -m pytest + clean: ## cleans the project directory rm -rf dist/ build/ knora.egg-info/ .pytest_cache/ site/ diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 000000000..e69de29bb diff --git a/knora/create_ontology.py b/knora/create_ontology.py index eb46c26c8..55e909176 100644 --- a/knora/create_ontology.py +++ b/knora/create_ontology.py @@ -115,8 +115,8 @@ def program(args): try: user_iri = con.create_user(username=user["username"], email=user["email"], - givenName=user["givenName"], - familyName=user["familyName"], + given_name=user["givenName"], + family_name=user["familyName"], password="password", lang=user["lang"] if user.get("lang") is not None else "en") except KnoraError as err: diff --git a/knora/knora.py b/knora/knora.py index d3ab563bd..73e96f55e 100755 --- a/knora/knora.py +++ b/knora/knora.py @@ -11,7 +11,6 @@ from rfc3987 import parse from pprint import pprint - # TODO: recheck all the documentation of this file """ Properties in knora-api: @@ -89,6 +88,7 @@ def __init__(self, message): class KnoraStandoffXml: """Used to handle XML strings for standoff markup""" + def __init__(self, xmlstr: str): self.xmlstr = xmlstr @@ -209,13 +209,13 @@ def project_exists(self, proj_iri: str) -> bool: return proj_iri in projects def create_project( - self, - shortcode: str, - shortname: str, - longname: str, - descriptions: Optional[Dict[str, str]] = None, - keywords: Optional[List[str]] = None, - logo: Optional[str] = None) -> str: + self, + shortcode: str, + shortname: str, + longname: str, + descriptions: Optional[Dict[str, str]] = None, + keywords: Optional[List[str]] = None, + logo: Optional[str] = None) -> str: """ Create a new project @@ -257,13 +257,13 @@ def create_project( return res["project"]["id"] def update_project( - self, - shortcode: str, - shortname: Optional[str] = None, - longname: Optional[str] = None, - descriptions: Optional[Dict[str, str]] = None, - keywords: Optional[List[str]] = None, - logo: Optional[str] = None) -> str: + self, + shortcode: str, + shortname: Optional[str] = None, + longname: Optional[str] = None, + descriptions: Optional[Dict[str, str]] = None, + keywords: Optional[List[str]] = None, + logo: Optional[str] = None) -> str: """ :param shortcode: @@ -302,7 +302,7 @@ def get_users(self): """ Get a list of all users - :return: + :return: Json result. """ url = self.server + '/admin/users' req = requests.get(url, headers={'Authorization': 'Bearer ' + self.token}) @@ -313,7 +313,7 @@ def get_users(self): def get_user_by_iri(self, user_iri: str): """ - Get a list of all users + Get single user :return: """ @@ -340,8 +340,8 @@ def get_user_by_email(self, email: str): def create_user(self, username: str, email: str, - givenName: str, - familyName: str, + given_name: str, + family_name: str, password: str, lang: str = "en"): """ @@ -349,8 +349,8 @@ def create_user(self, :param username: The username for login purposes (must be unique) :param email: The email address of the user - :param givenName: The given name (surname, "Vorname", ...) - :param familyName: The family name + :param given_name: The given name (surname, "Vorname", ...) + :param family_name: The family name :param password: The password for the user :param lang: language code, either "en", "de", "fr", "it" [default: "en"] :return: The user ID as IRI @@ -359,8 +359,8 @@ def create_user(self, userinfo = { "username": username, "email": email, - "givenName": givenName, - "familyName": familyName, + "givenName": given_name, + "familyName": family_name, "password": password, "status": True, "lang": lang, @@ -381,7 +381,8 @@ def create_user(self, return res['user']['id'] def add_user_to_project(self, user_iri: str, project_iri: str): - url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/project-memberships/' + quote_plus(project_iri) + url = self.server + '/admin/users/iri/' + quote_plus(user_iri) + '/project-memberships/' + quote_plus( + project_iri) req = requests.post(url, headers={'Authorization': 'Bearer ' + self.token}) self.on_api_error(req) return None @@ -460,7 +461,7 @@ def get_ontology_lastmoddate(self, onto_iri: str): for onto in result['@graph']: if 'knora-api:lastModificationDate' in onto: all_ontos.__setitem__(onto['@id'], onto['knora-api:lastModificationDate']) - else : + else: all_ontos.__setitem__(onto['@id'], None) return all_ontos[onto_iri] @@ -500,17 +501,17 @@ def create_ontology(self, self.on_api_error(req) res = req.json() - #TODO: return also ontology name + # TODO: return also ontology name return {"onto_iri": res['@id'], "last_onto_date": res['knora-api:lastModificationDate']} - def delete_ontology(self, onto_iri: str, last_onto_date = None): + def delete_ontology(self, onto_iri: str, last_onto_date=None): """ A method to delete an ontology from /v2/ontologies :param onto_iri: The ontology to delete :param last_onto_date: the lastModificationDate of an ontology. None by default :return: - """"" #TODO: add return documentation + """"" # TODO: add return documentation url = self.server + "/v2/ontologies/" + urllib.parse.quote_plus(onto_iri) req = requests.delete(url, params={"lastModificationDate": last_onto_date}, @@ -604,18 +605,18 @@ def create_res_class(self, return {"class_iri": res['@graph'][0]['@id'], "last_onto_date": res['knora-api:lastModificationDate']} def create_property( - self, - onto_iri: str, - onto_name: str, - last_onto_date: str, - prop_name: str, - super_props: List[str], - labels: Dict[str, str], - gui_element: str, - gui_attributes: List[str] = None, - subject: Optional[str] = None, - object: Optional[str] = None, - comments: Optional[Dict[str, str]] = None + self, + onto_iri: str, + onto_name: str, + last_onto_date: str, + prop_name: str, + super_props: List[str], + labels: Dict[str, str], + gui_element: str, + gui_attributes: List[str] = None, + subject: Optional[str] = None, + object: Optional[str] = None, + comments: Optional[Dict[str, str]] = None ) -> Dict[str, str]: """Create a Knora property @@ -710,13 +711,13 @@ def create_property( return {"prop_iri": res['@graph'][0]['@id'], "last_onto_date": res['knora-api:lastModificationDate']} def create_cardinality( - self, - onto_iri: str, - onto_name: str, - last_onto_date: str, - class_iri: str, - prop_iri: str, - occurrence: str + self, + onto_iri: str, + onto_name: str, + last_onto_date: str, + class_iri: str, + prop_iri: str, + occurrence: str ) -> Dict[str, str]: """Add a property with a given cardinality to a class @@ -858,7 +859,7 @@ def get_complete_list(self, list_iri: str): self.on_api_error(req) return req.json() - def create_resource(self, schema: Dict, res_class: str, label: str, values: Dict, stillimage = None): + def create_resource(self, schema: Dict, res_class: str, label: str, values: Dict, stillimage=None): """ This method creates a new resource (instance of a resource class) with the default permissions. @@ -935,8 +936,9 @@ def create_valdict(val): # # A knora date value # - res = re.match('(GREGORIAN:|JULIAN:)?(CE:|BCE:)?(\d{4})?(-\d{1,2})?(-\d{1,2})?(:CE|:BCE)?(:\d{4})?(-\d{1,2})?(-\d{1,2})?', - str(val)) + res = re.match( + '(GREGORIAN:|JULIAN:)?(CE:|BCE:)?(\d{4})?(-\d{1,2})?(-\d{1,2})?(:CE|:BCE)?(:\d{4})?(-\d{1,2})?(-\d{1,2})?', + str(val)) if res is None: raise KnoraError("Invalid date format! " + str(val)) dp = res.groups() @@ -1135,7 +1137,8 @@ def list_creator(self, children: List): if len(children) == 0: res = list(map(lambda a: {"name": a["name"], "id": a["id"]}, children)) else: - res = list(map(lambda a: {"name": a["name"], "id": a["id"], "nodes": self.list_creator(a["children"])}, children)) + res = list( + map(lambda a: {"name": a["name"], "id": a["id"], "nodes": self.list_creator(a["children"])}, children)) return res def create_schema(self, shortcode: str, shortname: str): @@ -1191,7 +1194,7 @@ def create_schema(self, shortcode: str, shortname: str): propname = '' link_otypes = [] propcnt = 0 - propindex= {} # we have to keep the order of the properties as given in the ontology.... + propindex = {} # we have to keep the order of the properties as given in the ontology.... for row in qres: nresclass = row.res.toPython() @@ -1314,6 +1317,7 @@ def reset_triplestore_content(self): # pprint(res) return res + class Sipi: def __init__(self, sipiserver: str, token: str): self.sipiserver = sipiserver @@ -1347,14 +1351,16 @@ class BulkImport: def __init__(self, schema: Dict): self.schema = schema self.proj_prefix = 'p' + schema['shortcode'] + '-' + schema["ontoname"] - self.proj_iri = "http://api.knora.org/ontology/" + schema['shortcode'] + "/" + schema["ontoname"] + "/xml-import/v1#" + self.proj_iri = "http://api.knora.org/ontology/" + schema['shortcode'] + "/" + schema[ + "ontoname"] + "/xml-import/v1#" self.xml_prefixes = { None: self.proj_iri, "xsi": "http://www.w3.org/2001/XMLSchema-instance", self.proj_prefix: self.proj_iri, "knoraXmlImport": "http://api.knora.org/ontology/knoraXmlImport/v1#" } - self.root = etree.Element('{http://api.knora.org/ontology/knoraXmlImport/v1#}resources', nsmap=self.xml_prefixes) + self.root = etree.Element('{http://api.knora.org/ontology/knoraXmlImport/v1#}resources', + nsmap=self.xml_prefixes) self.project_shortcode = schema["shortcode"] def new_xml_element(self, tag: str, options: Dict = None, value: str = None): @@ -1478,7 +1484,8 @@ def process_properties(propinfo: Dict, valuestr: any): # exit(0) elif propinfo["otype"] == 'DateValue': # processing and validating date format - res = re.match('(GREGORIAN:|JULIAN:)?(\d{4})?(-\d{1,2})?(-\d{1,2})?(:\d{4})?(-\d{1,2})?(-\d{1,2})?', str(valuestr)) + res = re.match('(GREGORIAN:|JULIAN:)?(\d{4})?(-\d{1,2})?(-\d{1,2})?(:\d{4})?(-\d{1,2})?(-\d{1,2})?', + str(valuestr)) if res is None: raise KnoraError("Invalid date format! " + str(valuestr)) dp = res.groups() @@ -1492,9 +1499,9 @@ def process_properties(propinfo: Dict, valuestr: any): if y1 is None: raise KnoraError("Invalid date format! " + str(valuestr)) if y2 is not None: - date1 = y1*10000; + date1 = y1 * 10000; if m1 is not None: - date1 += m1*100 + date1 += m1 * 100 if d1 is not None: date1 += d1 date2 = y2 * 10000; @@ -1532,21 +1539,25 @@ def process_properties(propinfo: Dict, valuestr: any): for prop_info in self.schema["resources"][resclass]: # first we check if the cardinality allows to add this property if properties.get(prop_info["propname"]) is None: # this property-value is missing - if prop_info["card"] == 'cardinality'\ - and prop_info["cardval"] == 1: - raise KnoraError(resclass + " requires exactly one " + prop_info["propname"] + "-value: none supplied!") - if prop_info["card"] == 'minCardinality'\ - and prop_info["cardval"] == 1: - raise KnoraError(resclass + " requires at least one " + prop_info["propname"] + "-value: none supplied!") + if prop_info["card"] == 'cardinality' \ + and prop_info["cardval"] == 1: + raise KnoraError( + resclass + " requires exactly one " + prop_info["propname"] + "-value: none supplied!") + if prop_info["card"] == 'minCardinality' \ + and prop_info["cardval"] == 1: + raise KnoraError( + resclass + " requires at least one " + prop_info["propname"] + "-value: none supplied!") continue if type(properties[prop_info["propname"]]) is list: if len(properties[prop_info["propname"]]) > 1: if prop_info["card"] == 'maxCardinality' \ - and prop_info["cardval"] == 1: - raise KnoraError(resclass + " allows maximal one " + prop_info["propname"] + "-value: several supplied!") - if prop_info["card"] == 'cardinality'\ - and prop_info["cardval"] == 1: - raise KnoraError(resclass + " requires exactly one " + prop_info["propname"] + "-value: several supplied!") + and prop_info["cardval"] == 1: + raise KnoraError( + resclass + " allows maximal one " + prop_info["propname"] + "-value: several supplied!") + if prop_info["card"] == 'cardinality' \ + and prop_info["cardval"] == 1: + raise KnoraError( + resclass + " requires exactly one " + prop_info["propname"] + "-value: several supplied!") for p in properties[prop_info["propname"]]: xmlopt, value = process_properties(prop_info, p) if xmlopt['knoraType'] == 'link_value': diff --git a/tests/test_knora.py b/tests/test_knora.py index 1a93c1ec3..ce7a65268 100644 --- a/tests/test_knora.py +++ b/tests/test_knora.py @@ -1,16 +1,17 @@ import pytest from knora import Knora + @pytest.fixture() def con(): - def _con(login: bool = True): + 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): + if login: con.login(email, password) return con @@ -19,23 +20,32 @@ def _con(login: bool = True): # resets the content of the triplestore def test_reset_triplestore_content(con): - res = con(False).reset_triplestore_content() - assert(res) + 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(True).get_user_by_iri("http://rdfh.ch/users/root") - assert(res["username"] == "root") + 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(True) + connection = con(login=True) user = { "username": "testtest", "email": "testtest@example.com", - "givenName": "test_given", - "familyName": "test_family", + "given_name": "test_given", + "family_name": "test_family", "password": "test", "lang": "en" } @@ -43,8 +53,8 @@ def test_create_user(con): user_iri = connection.create_user( username=user["username"], email=user["email"], - givenName=user["givenName"], - familyName=user["familyName"], + 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") @@ -52,8 +62,8 @@ def test_create_user(con): res = connection.get_user_by_iri(user_iri) - assert(res["username"] == "testtest") - assert(res["email"] == "testtest@example.com") + assert (res["username"] == "testtest") + assert (res["email"] == "testtest@example.com") # try to login connection.logout()