diff --git a/soitool/main_window.py b/soitool/main_window.py index 323ae98c48055f4811de6a0d7e2fa0851e564bc7..42fca26c7b8a25174e57328030c2164fb3c9ee10 100644 --- a/soitool/main_window.py +++ b/soitool/main_window.py @@ -235,15 +235,15 @@ class MainWindow(QMainWindow): in a new tab, which is selected. """ # Get file-name from dialog - file_name = QFileDialog().getOpenFileName( + file_path = QFileDialog().getOpenFileName( self, "Ã…pne SOI", os.getcwd(), "Text/JSON-filer (SOI_*.txt SOI_*.json)", )[0] - if len(file_name) > 0: - soi = import_soi(file_name) + if len(file_path) > 0: + soi = import_soi(file_path) # Create and select tab tab = SOIWorkspaceWidget(soi) diff --git a/soitool/serialize_export_import_soi.py b/soitool/serialize_export_import_soi.py index 50b980682037f4ac964a6b0fed3e29595104f1d6..b4d6c145e3ff906d0f2e338ec2efccd67298e8f1 100644 --- a/soitool/serialize_export_import_soi.py +++ b/soitool/serialize_export_import_soi.py @@ -22,13 +22,13 @@ def serialize_soi(soi): Raises ------ - ValueError + TypeError Raises error if parameter 'soi' is not a SOI. """ # If parameter 'soi' is not a SOI if not isinstance(soi, SOI): - raise ValueError( - "Invalid value for parameter 'soi': " + "'{}'.".format(soi) + raise TypeError( + "Invalid type for parameter 'soi': " + "'{}'.".format(soi) ) # Create dict with relevant module-information @@ -58,6 +58,7 @@ def serialize_soi(soi): "title": soi.title, "description": soi.description, "date": soi.date, + "version": soi.version, "valid": {"from": soi.valid_from, "to": soi.valid_to}, "icon": soi.icon, "classification": soi.classification, @@ -107,14 +108,29 @@ def export_soi(soi, compressed=True): file.close() -def import_soi(file_name): +def import_soi(file_path): """Import compressed or uncompressed serialized SOI. Reads content of file and decompresses it for .txt-files. Creates an SOI-object based on the file content. + + Parameters + ---------- + file_path : string + full path to file containing serialized SOI + + Returns + ------- + soitool.soi.SOI + SOI-object + + Raises + ------ + TypeError + If 'type' of module or attachment is not implemented. """ - with open(file_name, "r") as file: - if file_name[-4::] == ".txt": + with open(file_path, "r") as file: + if file_path[-4::] == ".txt": serialized = literal_eval(decompress(file.read())) else: serialized = literal_eval(file.read()) @@ -131,7 +147,7 @@ def import_soi(file_name): {"widget": TableModule(size, content), "meta": module["meta"]} ) else: - raise ValueError( + raise TypeError( "Module-type '{}' is not recognized.".format(module_type) ) @@ -150,15 +166,15 @@ def import_soi(file_name): } ) else: - raise ValueError( + raise TypeError( "Module-type '{}' is not recognized.".format(module_type) ) # Create SOI soi = SOI( serialized["title"], serialized["description"], - "1", # version - None, # date + serialized["version"], + serialized["date"], serialized["valid"]["from"], serialized["valid"]["to"], serialized["icon"], diff --git a/soitool/soi.py b/soitool/soi.py index 75c502f7e2f86d3d1baea1cddf9c2cb527a8b3c4..796d98e3d0e577bdfb9f26fadc191aca16ada291 100644 --- a/soitool/soi.py +++ b/soitool/soi.py @@ -378,6 +378,8 @@ class SOI: """ if self.placement_strategy == "auto": self.reorganize_rectpack() + elif self.placement_strategy == "manual": + return else: raise Exception( "Unknown placement strategy: {}".format( diff --git a/test/test_codebook_to_pdf.py b/test/test_codebook_to_pdf.py index 5aa94bee06349f604c97f6065d7c10d0dc8112ab..4d2b8c49ed27943ec4d994bca49e15c5aca3bb96 100644 --- a/test/test_codebook_to_pdf.py +++ b/test/test_codebook_to_pdf.py @@ -43,14 +43,6 @@ class ExportTest(unittest.TestCase): # Assert file exists self.assertTrue(os.path.exists(file_path_small)) - self.addCleanup( - partial(delete_generated_files, file_path_full, file_path_small) - ) - - -def delete_generated_files(file_path1, file_path2): - """Delete generated PDF-files.""" - if os.path.exists(file_path1): - os.remove(file_path1) - if os.path.exists(file_path2): - os.remove(file_path2) + # Delete files + os.remove(file_path_full) + os.remove(file_path_small) diff --git a/test/test_serialize_export_import.py b/test/test_serialize_export_import.py new file mode 100644 index 0000000000000000000000000000000000000000..563c70e36c0b8f2cdde62cb932f3e3c54c18a1c2 --- /dev/null +++ b/test/test_serialize_export_import.py @@ -0,0 +1,178 @@ +"""Test serializing, exporting and importing of SOI.""" +import os +import unittest +import json +from pathlib import Path +from datetime import datetime +from schema import Schema, And, Or +from PySide2.QtWidgets import QApplication +from PySide2 import QtGui +from soitool.soi import SOI +from soitool.modules.module_table import TableModule +from soitool.serialize_export_import_soi import ( + serialize_soi, + export_soi, + import_soi, +) + +if isinstance(QtGui.qApp, type(None)): + app = QApplication([]) +else: + app = QtGui.qApp + +SOITOOL_ROOT_PATH = Path(__file__).parent.parent + +# SOI content +TITLE = "testSOI" +DESCRIPTION = "This is a description" +VERSION = "1" +DATE = "01.01.2020" +VALID_FROM = "01.01.2020" +VALID_TO = "02.01.2020" +ICON = "soitool/media/HVlogo.png" +CLASSIFICATION = "Ugradert" +ORIENTATION = "portrait" +PLACEMENT_STRATEGY = "manual" +ALGORITHM_BIN = "BFF" +ALGORITHM_PACK = "MaxRectsBl" +ALGORITHM_SORT = "area" +MODULES = [ + { + "widget": TableModule( + size={"width": 50, "height": 75}, + content=[["H1"], ["Row1"], ["Row2"]], + ), + "meta": {"x": 0, "y": 0, "page": 1, "name": "Table1"}, + }, + { + "widget": TableModule( + size={"width": 100, "height": 75}, + content=[["H1", "H2"], ["Row1Col1", "Row1Col2"]], + ), + "meta": {"x": 200, "y": 150, "page": 1, "name": "Table1"}, + }, +] +# Valid schema for serialized SOI +SCHEMA = Schema( + { + "title": And(str, len), + "description": str, + "date": Or(str, None), + "version": And(str, len), + "valid": {"from": Or(str, None), "to": Or(str, None)}, + "icon": Or(str, None), + "classification": And(str, len), + "orientation": And(str, len, Or("portrait", "landscape")), + "placement_strategy": And(str, len, Or("manual", "auto")), + "algorithm_bin": And(str, len, Or("BFF", "BBF")), + "algorithm_pack": And( + str, len, Or("MaxRectsBl", "SkylineBl", "GuillotineBssfSas") + ), + "algorithm_sort": And(str, len, Or("none", "area", "width", "height")), + "modules": [ + { + "type": And(str, len), + "data": { + "size": { + "width": And(Or(int, float), lambda w: w > 0), + "height": And(Or(int, float), lambda h: h > 0), + }, + "content": object, + }, + "meta": { + "x": And(Or(int, float), lambda x: x >= 0), + "y": And(Or(int, float), lambda y: y >= 0), + "page": And(int, lambda page: page >= 0), + "name": And(str, len), + }, + } + ], + "attachments": [ + { + "type": And(str, len), + "data": { + "size": { + "width": And(Or(int, float), lambda w: w > 0), + "height": And(Or(int, float), lambda h: h > 0), + }, + "content": object, + }, + "meta": { + "x": And(Or(int, float), lambda x: x >= 0), + "y": And(Or(int, float), lambda y: y >= 0), + "page": And(int, lambda page: page >= 0), + "name": And(str, len), + }, + } + ], + } +) + + +class SerializeTest(unittest.TestCase): + """Testcase for functions in module 'serialize_export_import.py'.""" + + def setUp(self): + """Create SOI-object and generate filepath for exported SOI.""" + self.soi = SOI( + title=TITLE, + description=DESCRIPTION, + version=VERSION, + date=DATE, + valid_from=VALID_FROM, + valid_to=VALID_TO, + icon=ICON, + classification=CLASSIFICATION, + orientation=ORIENTATION, + placement_strategy=PLACEMENT_STRATEGY, + algorithm_bin=ALGORITHM_BIN, + algorithm_pack=ALGORITHM_PACK, + algorithm_sort=ALGORITHM_SORT, + modules=MODULES, + attachments=MODULES, + ) + date = datetime.now().strftime("%Y_%m_%d") + file_name = f"SOI_{TITLE}_{date}.json" + self.file_path = os.path.join(SOITOOL_ROOT_PATH, file_name) + + def test_export_soi(self): + """Export SOI and check if file was created.""" + # Export SOI + export_soi(self.soi, compressed=False) + # Assert file exists + self.assertTrue(os.path.exists(self.file_path)) + + def test_serialize_soi(self): + """Serialize SOI and check its validity against schema.""" + # Serialize SOI and load as dict + serialized = serialize_soi(self.soi) + json_data = json.loads(serialized) + # Assert serialized SOI format matches schema + self.assertTrue(SCHEMA.is_valid(json_data)) + + def test_import_soi(self): + """Import serialized SOI, check SOI content and delete SOI-file.""" + # Import SOI + soi = import_soi(self.file_path) + + # Assert SOI content is correct + self.assertEqual(TITLE, soi.title) + self.assertEqual(DESCRIPTION, soi.description) + self.assertEqual(VERSION, soi.version) + self.assertEqual(DATE, soi.date) + self.assertEqual(VALID_FROM, soi.valid_from) + self.assertEqual(VALID_TO, soi.valid_to) + self.assertEqual(ICON, soi.icon) + self.assertEqual(CLASSIFICATION, soi.classification) + self.assertEqual(ORIENTATION, soi.orientation) + self.assertEqual(PLACEMENT_STRATEGY, soi.placement_strategy) + self.assertEqual(ALGORITHM_BIN, soi.algorithm_bin) + self.assertEqual(ALGORITHM_PACK, soi.algorithm_pack) + self.assertEqual(ALGORITHM_SORT, soi.algorithm_sort) + self.assertEqual(type(MODULES[0]["widget"]), TableModule) + self.assertEqual(type(MODULES[1]["widget"]), TableModule) + self.assertEqual(MODULES[0]["meta"], soi.modules[0]["meta"]) + self.assertEqual(MODULES[1]["meta"], soi.modules[1]["meta"]) + + # Delete exported SOI-file + os.remove(self.file_path)