diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5a47aa19e5568c17770f12645ec0d504c8f885da..0cfcd600cd1a6cab335c2af20a704ef0bfdfc14f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -403,7 +403,7 @@ test-lib: script: - "pip install pipenv" - "pipenv install gunicorn && pipenv install --dev --system --deploy" - - cd ./lib/python/ && coverage run -m pytest tests/test_unit_analyse.py tests/test_unit_container.py tests/test_unit_database.py tests/test_unit_identifier.py tests/test_unit_license.py tests/test_unit_query.py tests/test_unit_rest_client.py tests/test_unit_table.py tests/test_unit_user.py tests/test_unit_view.py --junitxml=report.xml && coverage html --omit="test/*" && coverage report --omit="test/*" > ./coverage.txt + - cd ./lib/python/ && coverage run -m pytest tests/test_unit_analyse.py tests/test_unit_container.py tests/test_unit_database.py tests/test_unit_identifier.py tests/test_unit_image.py tests/test_unit_messages.py tests/test_unit_license.py tests/test_unit_query.py tests/test_unit_rest_client.py tests/test_unit_table.py tests/test_unit_user.py tests/test_unit_view.py --junitxml=report.xml && coverage html --omit="test/*" && coverage report --omit="test/*" > ./coverage.txt - "cat ./coverage.txt | grep -o 'TOTAL[^%]*%'" artifacts: when: always diff --git a/dbrepo-auth-service/listeners/target/create-event-listener.jar b/dbrepo-auth-service/listeners/target/create-event-listener.jar index f370c8825750a431296ef68c3d482a2e4eee9389..a33db1e9045a92823996c61a9223aafb0d25404e 100644 Binary files a/dbrepo-auth-service/listeners/target/create-event-listener.jar and b/dbrepo-auth-service/listeners/target/create-event-listener.jar differ diff --git a/dbrepo-metadata-db/migration/16/gen_data_sql.py b/dbrepo-metadata-db/migration/16/gen_data_sql.py new file mode 100644 index 0000000000000000000000000000000000000000..eeade6f3ab6a989103e9ae88f56ed59e4779745f --- /dev/null +++ b/dbrepo-metadata-db/migration/16/gen_data_sql.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +import os +import uuid + +from dbrepo.RestClient import RestClient + +endpoint = os.getenv('METADATA_SERVICE_ENDPOINT', 'http://localhost') +username = os.getenv('SYSTEM_USERNAME', 'admin') +password = os.getenv('SYSTEM_PASSWORD', 'admin') +client = RestClient(endpoint=endpoint, username=username, password=password) + +plan: [str] = [] + + +def update_concepts() -> None: + plan.append("-- concepts") + plan.append("BEGIN;") + for concept in client.get_concepts(): + old_id: int = concept.id + new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_columns_concepts SET id = '{new_id}' WHERE id = {old_id};") + plan.append(f"UPDATE mdb_concepts SET id = '{new_id}' WHERE id = {old_id};") + plan.append("COMMIT;") + + +def update_ontologies() -> None: + plan.append("-- ontologies") + plan.append("BEGIN;") + plan.append(f"UPDATE mdb_ontology SET id = UUID()") + plan.append("COMMIT;") + + +def update_units() -> None: + plan.append("-- units") + plan.append("BEGIN;") + for unit in client.get_units(): + old_id: int = unit.id + new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_columns_units SET id = '{new_id}' WHERE id = {old_id};") + plan.append(f"UPDATE mdb_units SET id = '{new_id}' WHERE id = {old_id};") + plan.append("COMMIT;") + + +def update_images() -> None: + plan.append("-- images") + plan.append("BEGIN;") + for image in client.get_images(): + old_id: int = image.id + new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_images SET id = '{new_id}' WHERE id = {old_id};") + plan.append(f"UPDATE mdb_image_operators SET id = UUID(), image_id = '{new_id}' WHERE image_id = {old_id};") + plan.append(f"UPDATE mdb_image_types SET id = UUID(), image_id = '{new_id}' WHERE image_id = {old_id};") + plan.append(f"UPDATE mdb_containers SET id = UUID(), image_id = '{new_id}' WHERE image_id = {old_id};") + plan.append("COMMIT;") + + +def update_containers() -> None: + plan.append("-- containers") + plan.append("BEGIN;") + for containers in client.get_containers(): + old_id: int = containers.id + new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_containers SET id = '{new_id}' WHERE id = {old_id};") + plan.append(f"UPDATE mdb_databases SET cid = '{new_id}' WHERE cid = {old_id};") + plan.append("COMMIT;") + + +def update_databases() -> None: + plan.append("-- databases") + plan.append("BEGIN;") + for _database in client.get_databases(): + database = client.get_database(database_id=_database.id) + old_id: int = database.id + new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_tables SET tDBID = '{new_id}' WHERE tDBID = {old_id};") + plan.append(f"UPDATE mdb_have_access SET database_id = '{new_id}' WHERE database_id = {old_id};") + plan.append(f"UPDATE mdb_views SET vdbid = '{new_id}' WHERE vdbid = {old_id};") + plan.append(f"UPDATE mdb_identifiers SET dbid = '{new_id}' WHERE dbid = {old_id};") + plan.append(f"UPDATE mdb_access SET aDBID = '{new_id}' WHERE aDBID = {old_id};") + for view in database.views: + v_old_id: int = view.id + v_new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_identifiers SET vid = '{v_new_id}' WHERE vid = {v_old_id};") + plan.append(f"UPDATE mdb_view_columns SET id = UUID(), view_id = '{v_new_id}' WHERE tid = {v_old_id};") + for table in database.tables: + tbl_old_id: int = table.id + tbl_new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_identifiers SET tid = '{tbl_new_id}' WHERE tid = {tbl_old_id};") + plan.append(f"UPDATE mdb_constraints_checks SET id = UUID(), tid = '{tbl_new_id}' WHERE tid = {tbl_old_id};") + for fk in table.constraints.foreign_keys: + fk_old_id: int = fk.id + fk_new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_constraints_foreign_key SET id = '{fk_new_id}', tid = '{tbl_new_id}' WHERE id = {fk_old_id};") + for fkref in fk.references: + plan.append(f"UPDATE mdb_constraints_foreign_key_reference SET id = UUID(), fkid = '{fk_new_id}' WHERE fkid = {fkref};") + for pk in table.constraints.primary_key: + pk_old_id: int = pk.id + plan.append(f"UPDATE mdb_constraints_primary_key SET pkid = UUID(), tID = '{tbl_new_id}' WHERE tID = {pk_old_id};") + for uk in table.constraints.uniques: + uk_old_id: int = uk.id + uk_new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_constraints_unique SET uid = '{uk_new_id}', tid = '{tbl_new_id}' WHERE uid = {uk_old_id}") + plan.append(f"UPDATE mdb_constraints_unique_columns SET id = UUID(), uid = '{uk_new_id}' WHERE uid = {uk_old_id}") + for column in table.columns: + col_old_id: int = column.id + col_new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_columns SET ID = '{col_new_id}' WHERE ID = {col_old_id};") + plan.append(f"UPDATE mdb_constraints_unique_columns SET cid = '{col_new_id}' WHERE cid = {col_old_id};") + plan.append(f"UPDATE mdb_constraints_primary_key SET cid = '{col_new_id}' WHERE cid = {col_old_id};") + plan.append(f"UPDATE mdb_constraints_foreign_key_reference SET cid = '{col_new_id}' WHERE cid = {col_old_id};") + plan.append(f"UPDATE mdb_constraints_foreign_key_reference SET rcid = '{col_new_id}' WHERE rcid = {col_old_id};") + plan.append(f"UPDATE mdb_columns_concepts SET cID = '{col_new_id}' WHERE cID = {col_old_id};") + plan.append(f"UPDATE mdb_columns_units SET cID = '{col_new_id}' WHERE cID = {col_old_id};") + plan.append(f"UPDATE mdb_columns_sets SET column_id = '{col_new_id}' WHERE column_id = {col_old_id};") + plan.append(f"UPDATE mdb_columns_enums SET column_id = '{col_new_id}' WHERE column_id = {col_old_id};") + plan.append(f"UPDATE mdb_tables SET ID = '{tbl_new_id}' WHERE ID = {tbl_old_id};") + plan.append(f"UPDATE mdb_databases SET id = '{new_id}' WHERE id = {old_id};") + plan.append("COMMIT;") + +def update_messages() -> None: + plan.append("-- messages") + plan.append("BEGIN;") + plan.append(f"UPDATE mdb_banner_messages SET ID = UUID();") + plan.append("COMMIT;") + +def update_identifiers() -> None: + plan.append("-- identifiers") + plan.append("BEGIN;") + for identified in client.get_identifiers(): + i_old_id: int = identified.id + i_new_id: uuid = uuid.uuid4() + plan.append(f"UPDATE mdb_identifiers SET ID = '{i_new_id}' WHERE id = {i_old_id};") + plan.append(f"UPDATE mdb_identifier_creators SET id = UUID(), pid = '{i_new_id}' WHERE pid = {i_old_id};") + plan.append(f"UPDATE mdb_identifier_descriptions SET id = UUID(), pid = '{i_new_id}' WHERE pid = {i_old_id};") + plan.append(f"UPDATE mdb_identifier_titles SET id = UUID(), pid = '{i_new_id}' WHERE pid = {i_old_id};") + plan.append(f"UPDATE mdb_identifier_funders SET id = UUID(), pid = '{i_new_id}' WHERE pid = {i_old_id};") + plan.append(f"UPDATE mdb_identifier_licenses SET id = UUID(), pid = '{i_new_id}' WHERE pid = {i_old_id};") + plan.append("COMMIT;") + + +if __name__ == '__main__': + plan.append("SET FOREIGN_KEY_CHECKS=0;") + update_concepts() + update_units() + update_messages() + update_ontologies() + update_images() + update_containers() + update_databases() + update_identifiers() + plan.append("SET FOREIGN_KEY_CHECKS=1;") + print("\n".join(plan)) diff --git a/dbrepo-metadata-db/migration/16/requirements.txt b/dbrepo-metadata-db/migration/16/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3f6fcb0a570c02ae46084940ed2f5f285e9aed36 --- /dev/null +++ b/dbrepo-metadata-db/migration/16/requirements.txt @@ -0,0 +1 @@ +dbrepo==1.6.5rc6 \ No newline at end of file diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java index b3d699086ee93b943801a3139a2e2488dc9dcc8c..13fe028bcf33d2596a4b1fcd2bd307d3b35c1e8c 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/IdentifierEndpoint.java @@ -106,7 +106,7 @@ public class IdentifierEndpoint extends AbstractEndpoint { .filter(i -> !Objects.nonNull(qid) || qid.equals(i.getQueryId())) .filter(i -> !Objects.nonNull(vid) || vid.equals(i.getViewId())) .filter(i -> !Objects.nonNull(tid) || tid.equals(i.getTableId())) - .filter(i -> principal != null && i.getStatus().equals(IdentifierStatusType.DRAFT) ? i.getOwnedBy().equals(getId(principal)) : i.getStatus().equals(IdentifierStatusType.PUBLISHED)) + .filter(i -> principal != null && i.getStatus().equals(IdentifierStatusType.DRAFT) ? (i.getOwnedBy().equals(getId(principal)) || isSystem(principal)) : i.getStatus().equals(IdentifierStatusType.PUBLISHED)) .toList(); if (identifiers.isEmpty()) { return ResponseEntity.ok(List.of()); diff --git a/helm/dbrepo/files/create-event-listener.jar b/helm/dbrepo/files/create-event-listener.jar index f370c8825750a431296ef68c3d482a2e4eee9389..a33db1e9045a92823996c61a9223aafb0d25404e 100644 Binary files a/helm/dbrepo/files/create-event-listener.jar and b/helm/dbrepo/files/create-event-listener.jar differ diff --git a/lib/python/dbrepo/RestClient.py b/lib/python/dbrepo/RestClient.py index a0b4bf60c43f2065ffd2f73f5010407fb0d20287..f29f606fd6eb84ca5d67ab43acf44a649ca8cbb3 100644 --- a/lib/python/dbrepo/RestClient.py +++ b/lib/python/dbrepo/RestClient.py @@ -1,9 +1,8 @@ import logging import os +import requests import sys import time - -import requests from pandas import DataFrame from pydantic import TypeAdapter @@ -1966,7 +1965,7 @@ class RestClient: :raises FormatNotAvailable: If the service could not represent the output. :raises ResponseCodeError: If something went wrong with the retrieval of the identifiers. """ - url = f'/api/identifiers' + url = f'/api/identifier' if database_id is not None: url += f'?dbid={database_id}' if subset_id is not None: @@ -1993,6 +1992,34 @@ class RestClient: raise ResponseCodeError(f'Failed to get identifiers: response code: {response.status_code} is not ' f'200 (OK): {response.text}') + def get_images(self) -> List[ImageBrief] | str: + """ + Get list of container images. + + :returns: List of images, if successful. + """ + url = f'/api/image' + response = self._wrapper(method="get", url=url, headers={'Accept': 'application/json'}) + if response.status_code == 200: + body = response.json() + return TypeAdapter(List[ImageBrief]).validate_python(body) + raise ResponseCodeError(f'Failed to get images: response code: {response.status_code} is not ' + f'200 (OK): {response.text}') + + def get_messages(self) -> List[BannerMessage] | str: + """ + Get list of messages. + + :returns: List of messages, if successful. + """ + url = f'/api/message' + response = self._wrapper(method="get", url=url, headers={'Accept': 'application/json'}) + if response.status_code == 200: + body = response.json() + return TypeAdapter(List[BannerMessage]).validate_python(body) + raise ResponseCodeError(f'Failed to get messages: response code: {response.status_code} is not ' + f'200 (OK): {response.text}') + def update_table_column(self, database_id: int, table_id: int, column_id: int, concept_uri: str = None, unit_uri: str = None) -> Column: """ diff --git a/lib/python/dbrepo/api/dto.py b/lib/python/dbrepo/api/dto.py index bd0d13dc1894d1a83891abdcdd6fb438de27f3f4..656c06a9ff09a8fdc9eb5834692c86fbe5839a9f 100644 --- a/lib/python/dbrepo/api/dto.py +++ b/lib/python/dbrepo/api/dto.py @@ -3,9 +3,8 @@ from __future__ import annotations import datetime from dataclasses import field from enum import Enum -from typing import List, Optional, Annotated - from pydantic import BaseModel, PlainSerializer +from typing import List, Optional, Annotated Timestamp = Annotated[ datetime.datetime, PlainSerializer(lambda v: v.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', return_type=str) @@ -41,6 +40,16 @@ class ImageBrief(BaseModel): name: str version: str jdbc_method: str + default: bool + + +class BannerMessage(BaseModel): + id: int + type: str + link: Optional[str] = None + link_text: Optional[str] = None + display_start: Optional[Timestamp] = None + display_end: Optional[Timestamp] = None class CreateDatabase(BaseModel): @@ -629,13 +638,13 @@ class Identifier(BaseModel): id: int database_id: int type: IdentifierType - owner: UserBrief status: IdentifierStatusType publication_year: int publisher: str creators: List[Creator] titles: List[IdentifierTitle] descriptions: List[IdentifierDescription] + owned_by: str funders: Optional[List[IdentifierFunder]] = field(default_factory=list) doi: Optional[str] = None language: Optional[str] = None @@ -699,7 +708,7 @@ class ViewBrief(BaseModel): initial_view: bool query: str query_hash: str - owned_by: str + owner: UserBrief class ConceptBrief(BaseModel): @@ -1024,7 +1033,7 @@ class Database(BaseModel): preview_image: Optional[str] = None description: Optional[str] = None tables: Optional[List[Table]] = field(default_factory=list) - views: Optional[List[View]] = field(default_factory=list) + views: Optional[List[ViewBrief]] = field(default_factory=list) accesses: Optional[List[DatabaseAccess]] = field(default_factory=list) exchange_name: Optional[str] = None diff --git a/lib/python/pyproject.toml b/lib/python/pyproject.toml index b956d6afd0ff66f7ae83f38112b7443001e98da1..75113e70b03431450cf1a7ebef78a5abb7b17001 100644 --- a/lib/python/pyproject.toml +++ b/lib/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dbrepo" -version = "1.6.4" +version = "1.6.5rc6" description = "DBRepo Python Library" keywords = [ "DBRepo", diff --git a/lib/python/setup.py b/lib/python/setup.py index 53f4832404814c0e1468082add0b090ea106de84..df4528ab82988523af7c60ca20a4a37f4129b1ff 100644 --- a/lib/python/setup.py +++ b/lib/python/setup.py @@ -2,7 +2,7 @@ from distutils.core import setup setup(name="dbrepo", - version="1.6.4", + version="1.6.5rc6", description="A library for communicating with DBRepo", url="https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.6/", author="Martin Weise", diff --git a/lib/python/tests/test_unit_image.py b/lib/python/tests/test_unit_image.py new file mode 100644 index 0000000000000000000000000000000000000000..2802efc69073ba10b7c7286b87cdb531ffafbbb1 --- /dev/null +++ b/lib/python/tests/test_unit_image.py @@ -0,0 +1,31 @@ +import unittest + +import requests_mock + +from dbrepo.RestClient import RestClient + +from dbrepo.api.dto import ImageBrief + + +class ImageUnitTest(unittest.TestCase): + + def test_get_images_empty_succeeds(self): + with requests_mock.Mocker() as mock: + # mock + mock.get('/api/image', json=[]) + # test + response = RestClient().get_images() + self.assertEqual([], response) + + def test_get_images_succeeds(self): + with requests_mock.Mocker() as mock: + exp = [ImageBrief(id=1, name="mariadb", version="11.1.3", jdbc_method="mariadb", default=False)] + # mock + mock.get('/api/image', json=[exp[0].model_dump()]) + # test + response = RestClient().get_images() + self.assertEqual(exp, response) + + +if __name__ == "__main__": + unittest.main() diff --git a/lib/python/tests/test_unit_messages.py b/lib/python/tests/test_unit_messages.py new file mode 100644 index 0000000000000000000000000000000000000000..0bc16394a045514f1dd8000f8649e80dc4c2a9b5 --- /dev/null +++ b/lib/python/tests/test_unit_messages.py @@ -0,0 +1,31 @@ +import unittest + +import requests_mock + +from dbrepo.RestClient import RestClient + +from dbrepo.api.dto import ImageBrief + + +class ImageUnitTest(unittest.TestCase): + + def test_get_message_empty_succeeds(self): + with requests_mock.Mocker() as mock: + # mock + mock.get('/api/message', json=[]) + # test + response = RestClient().get_images() + self.assertEqual([], response) + + def test_get_images_succeeds(self): + with requests_mock.Mocker() as mock: + exp = [BannerMessage(id=1, type="info")] + # mock + mock.get('/api/message', json=[exp[0].model_dump()]) + # test + response = RestClient().get_images() + self.assertEqual(exp, response) + + +if __name__ == "__main__": + unittest.main()