Module soitool.serialize_export_import_soi

Includes functionality for serializing, exporting and importing SOI.

Expand source code
"""Includes functionality for serializing, exporting and importing SOI."""
import json
from datetime import datetime
from schema import Schema, And, Or
from soitool.soi import SOI
from soitool.compressor import compress, decompress
from soitool.modules.module_table import TableModule
from soitool.modules.module_authentication_board import (
    AuthenticationBoardModule,
)
from soitool.modules.module_subtractorcodes import SubtractorcodesModule
from soitool.modules.module_freetext import FreeTextModule
from soitool.modules.module_frequency_table import FrequencyTableModule
from soitool.modules.module_phonebook import PhonebookModule
from soitool.modules.module_predefined_codes import PredefinedCodesModule
from soitool.modules.module_code_phrase import CodePhraseModule

# Valid schema for serialized SOI
SERIALIZED_SOI_SCHEMA = Schema(
    {
        "title": And(str, len),
        "description": str,
        "version": And(str, len),
        "date": Or(str, None),
        "valid": {"from_date": Or(str, None), "to_date": Or(str, None)},
        "icon": Or(str, None),
        "classification": And(
            str,
            len,
            Or(
                "UGRADERT",
                "BEGRENSET",
                "KONFIDENSIELT",
                "HEMMELIG",
                "STRENGT HEMMELIG",
            ),
        ),
        "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": 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": 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),
                },
            }
        ],
    }
)


def serialize_soi(soi):
    """Serialize SOI to JSON-string.

    Parameters
    ----------
    soi : soitool.soi.SOI
        SOI to serialize.

    Returns
    -------
    String
        JSON-string containing all SOI-information.

    Raises
    ------
    TypeError
        Raises error if parameter 'soi' is not an SOI.
    """
    # If parameter 'soi' is not an SOI
    if not isinstance(soi, SOI):
        raise TypeError(
            "Invalid type for parameter 'soi': " + "'{}'.".format(soi)
        )

    # Create dict with relevant module-information
    modules = []
    for module in soi.modules:
        modules.append(
            {
                "type": module["widget"].type,
                "data": module["widget"].get_data(),
                "meta": module["meta"],
            }
        )

    # Create dict with relevant attachment-information
    attachments = []
    for attachment in soi.attachments:
        attachments.append(
            {
                "type": attachment["widget"].type,
                "data": attachment["widget"].get_data(),
                "meta": attachment["meta"],
            }
        )

    # Create dict with all relevant SOI-information
    serialized = {
        "title": soi.title,
        "description": soi.description,
        "version": soi.version,
        "date": soi.date,
        "valid": {"from_date": soi.valid_from, "to_date": soi.valid_to},
        "icon": soi.icon,
        "classification": soi.classification,
        "orientation": soi.orientation,
        "placement_strategy": soi.placement_strategy,
        "algorithm_bin": soi.algorithm_bin,
        "algorithm_pack": soi.algorithm_pack,
        "algorithm_sort": soi.algorithm_sort,
        "modules": modules,
        "attachments": attachments,
    }

    return json.dumps(serialized)


def export_soi(soi, compressed=True):
    """Export SOI.

    A .txt-file is created to contain compressed SOI.
    A .json-file is created to contain uncompressed SOI.

    Parameters
    ----------
    soi: soitool.soi.SOI
        SOI to export
    compressed : bool, optional
        Serialized SOI will be compressed if True (default).
    """
    # Serialize SOI
    serialized = serialize_soi(soi)

    file_name = generate_soi_filename(soi)

    if compressed:
        serialized = compress(serialized)
        file = open(file_name + ".txt", "w")
    else:
        file = open(file_name + ".json", "w")

    file.write(str(serialized))
    file.close()


def import_soi(file_path, database):
    """Import compressed or uncompressed serialized SOI.

    Reads content of file and decompresses it for .txt-files.
    Constructs an SOI-object based on the file content.

    Parameters
    ----------
    file_path : string
        Full path to a file containing serialized SOI.
    database : soitool.database.Database
        Database-instance passed to specific modules.

    Returns
    -------
    soitool.soi.SOI
        SOI-instance.

    Raises
    ------
    ValueError
        If file content is invalid against SERIALIZED_SOI_SCHEMA.
    TypeError
        If 'type' of module or attachment is not implemented.
    """
    with open(file_path, "r") as file:
        serialized = file.read()

        if file_path.endswith(".txt"):
            return construct_soi_from_serialized(
                serialized, database, compressed=True
            )

        return construct_soi_from_serialized(serialized, database)


def construct_soi_from_serialized(serialized, database, compressed=False):
    """Construct an SOI-object from a serialized SOI.

    Parameters
    ----------
    serialized : string
        Serialized SOI.
    database : soitool.database.Database
        Database-instance passed to specific modules.
    compressed : bool, optional
        True if serialized SOI is compressed, by default False.

    Returns
    -------
    soitool.soi.SOI
        SOI-instance.

    Raises
    ------
    ValueError
        If 'serialized' is invalid against SERIALIZED_SOI_SCHEMA.
    TypeError
        If 'type' of module or attachment is not implemented.
    """
    if compressed:
        serialized = json.loads(decompress(serialized))
    else:
        serialized = json.loads(serialized)
    # Raise error if file content is invalid
    if not SERIALIZED_SOI_SCHEMA.is_valid(serialized):
        raise ValueError("Serialized SOI does not have correct format.")

    # Construct modules and attachments
    modules = construct_modules_from_serialized(
        serialized["modules"], database
    )
    attachments = construct_modules_from_serialized(
        serialized["attachments"], database
    )

    # Create SOI
    soi = SOI(
        serialized["title"],
        serialized["description"],
        serialized["version"],
        serialized["date"],
        serialized["valid"]["from_date"],
        serialized["valid"]["to_date"],
        serialized["icon"],
        serialized["classification"],
        serialized["orientation"],
        serialized["placement_strategy"],
        serialized["algorithm_bin"],
        serialized["algorithm_pack"],
        serialized["algorithm_sort"],
        modules,
        attachments,
    )

    return soi


def construct_modules_from_serialized(serialized_modules, database):
    """Instantiate modules from serialized format.

    Parameters
    ----------
    serialized_modules : list
        Containing dicts with serialized modules or attachment-modules.
    database : soitool.database.Database
        Database-instance passed to specific modules.

    Returns
    -------
    list
        Containing dicts with instantiated modules or attachment-modules.

    Raises
    ------
    TypeError
        If type of module is not recognized.
    """
    modules = []

    for module in serialized_modules:
        module_type = module["type"]
        data = module["data"]

        if module_type == "TableModule":
            modules.append(
                {"widget": TableModule(data), "meta": module["meta"]}
            )
        elif module_type == "AuthenticationBoardModule":
            modules.append(
                {
                    "widget": AuthenticationBoardModule(data),
                    "meta": module["meta"],
                }
            )
        elif module_type == "SubtractorcodesModule":
            modules.append(
                {
                    "widget": SubtractorcodesModule(data),
                    "meta": module["meta"],
                }
            )
        elif module_type == "FreeTextModule":
            modules.append(
                {"widget": FreeTextModule(data), "meta": module["meta"]}
            )
        elif module_type == "PhonebookModule":
            modules.append(
                {"widget": PhonebookModule(data), "meta": module["meta"]}
            )
        elif module_type == "FrequencyTableModule":
            modules.append(
                {"widget": FrequencyTableModule(data), "meta": module["meta"]}
            )
        elif module_type == "PredefinedCodesModule":
            modules.append(
                {
                    "widget": PredefinedCodesModule(database, data),
                    "meta": module["meta"],
                }
            )
        elif module_type == "CodePhraseModule":
            modules.append(
                {
                    "widget": CodePhraseModule(database, data),
                    "meta": module["meta"],
                }
            )
        else:
            raise TypeError(
                "Module-type '{}' is not recognized.".format(module_type)
            )

    return modules


def generate_soi_filename(soi):
    """Generate filename for SOI without extension.

    Parameters
    ----------
    soi : SOI
        SOI to generate filename for.

    Returns
    -------
    str
        Filename for the SOI of the format 'SOI_title_YYYY_mm_dd'.
    """
    title = soi.title
    parsed_date = datetime.strptime(soi.date, "%Y-%m-%d")
    date_string = parsed_date.strftime("%Y_%m_%d")
    return f"SOI_{title}_{date_string}"

Functions

def construct_modules_from_serialized(serialized_modules, database)

Instantiate modules from serialized format.

Parameters

serialized_modules : list
Containing dicts with serialized modules or attachment-modules.
database : Database
Database-instance passed to specific modules.

Returns

list
Containing dicts with instantiated modules or attachment-modules.

Raises

TypeError
If type of module is not recognized.
Expand source code
def construct_modules_from_serialized(serialized_modules, database):
    """Instantiate modules from serialized format.

    Parameters
    ----------
    serialized_modules : list
        Containing dicts with serialized modules or attachment-modules.
    database : soitool.database.Database
        Database-instance passed to specific modules.

    Returns
    -------
    list
        Containing dicts with instantiated modules or attachment-modules.

    Raises
    ------
    TypeError
        If type of module is not recognized.
    """
    modules = []

    for module in serialized_modules:
        module_type = module["type"]
        data = module["data"]

        if module_type == "TableModule":
            modules.append(
                {"widget": TableModule(data), "meta": module["meta"]}
            )
        elif module_type == "AuthenticationBoardModule":
            modules.append(
                {
                    "widget": AuthenticationBoardModule(data),
                    "meta": module["meta"],
                }
            )
        elif module_type == "SubtractorcodesModule":
            modules.append(
                {
                    "widget": SubtractorcodesModule(data),
                    "meta": module["meta"],
                }
            )
        elif module_type == "FreeTextModule":
            modules.append(
                {"widget": FreeTextModule(data), "meta": module["meta"]}
            )
        elif module_type == "PhonebookModule":
            modules.append(
                {"widget": PhonebookModule(data), "meta": module["meta"]}
            )
        elif module_type == "FrequencyTableModule":
            modules.append(
                {"widget": FrequencyTableModule(data), "meta": module["meta"]}
            )
        elif module_type == "PredefinedCodesModule":
            modules.append(
                {
                    "widget": PredefinedCodesModule(database, data),
                    "meta": module["meta"],
                }
            )
        elif module_type == "CodePhraseModule":
            modules.append(
                {
                    "widget": CodePhraseModule(database, data),
                    "meta": module["meta"],
                }
            )
        else:
            raise TypeError(
                "Module-type '{}' is not recognized.".format(module_type)
            )

    return modules
def construct_soi_from_serialized(serialized, database, compressed=False)

Construct an SOI-object from a serialized SOI.

Parameters

serialized : string
Serialized SOI.
database : Database
Database-instance passed to specific modules.
compressed : bool, optional
True if serialized SOI is compressed, by default False.

Returns

SOI
SOI-instance.

Raises

ValueError
If 'serialized' is invalid against SERIALIZED_SOI_SCHEMA.
TypeError
If 'type' of module or attachment is not implemented.
Expand source code
def construct_soi_from_serialized(serialized, database, compressed=False):
    """Construct an SOI-object from a serialized SOI.

    Parameters
    ----------
    serialized : string
        Serialized SOI.
    database : soitool.database.Database
        Database-instance passed to specific modules.
    compressed : bool, optional
        True if serialized SOI is compressed, by default False.

    Returns
    -------
    soitool.soi.SOI
        SOI-instance.

    Raises
    ------
    ValueError
        If 'serialized' is invalid against SERIALIZED_SOI_SCHEMA.
    TypeError
        If 'type' of module or attachment is not implemented.
    """
    if compressed:
        serialized = json.loads(decompress(serialized))
    else:
        serialized = json.loads(serialized)
    # Raise error if file content is invalid
    if not SERIALIZED_SOI_SCHEMA.is_valid(serialized):
        raise ValueError("Serialized SOI does not have correct format.")

    # Construct modules and attachments
    modules = construct_modules_from_serialized(
        serialized["modules"], database
    )
    attachments = construct_modules_from_serialized(
        serialized["attachments"], database
    )

    # Create SOI
    soi = SOI(
        serialized["title"],
        serialized["description"],
        serialized["version"],
        serialized["date"],
        serialized["valid"]["from_date"],
        serialized["valid"]["to_date"],
        serialized["icon"],
        serialized["classification"],
        serialized["orientation"],
        serialized["placement_strategy"],
        serialized["algorithm_bin"],
        serialized["algorithm_pack"],
        serialized["algorithm_sort"],
        modules,
        attachments,
    )

    return soi
def export_soi(soi, compressed=True)

Export SOI.

A .txt-file is created to contain compressed SOI. A .json-file is created to contain uncompressed SOI.

Parameters

soi : SOI
SOI to export
compressed : bool, optional
Serialized SOI will be compressed if True (default).
Expand source code
def export_soi(soi, compressed=True):
    """Export SOI.

    A .txt-file is created to contain compressed SOI.
    A .json-file is created to contain uncompressed SOI.

    Parameters
    ----------
    soi: soitool.soi.SOI
        SOI to export
    compressed : bool, optional
        Serialized SOI will be compressed if True (default).
    """
    # Serialize SOI
    serialized = serialize_soi(soi)

    file_name = generate_soi_filename(soi)

    if compressed:
        serialized = compress(serialized)
        file = open(file_name + ".txt", "w")
    else:
        file = open(file_name + ".json", "w")

    file.write(str(serialized))
    file.close()
def generate_soi_filename(soi)

Generate filename for SOI without extension.

Parameters

soi : SOI
SOI to generate filename for.

Returns

str
Filename for the SOI of the format 'SOI_title_YYYY_mm_dd'.
Expand source code
def generate_soi_filename(soi):
    """Generate filename for SOI without extension.

    Parameters
    ----------
    soi : SOI
        SOI to generate filename for.

    Returns
    -------
    str
        Filename for the SOI of the format 'SOI_title_YYYY_mm_dd'.
    """
    title = soi.title
    parsed_date = datetime.strptime(soi.date, "%Y-%m-%d")
    date_string = parsed_date.strftime("%Y_%m_%d")
    return f"SOI_{title}_{date_string}"
def import_soi(file_path, database)

Import compressed or uncompressed serialized SOI.

Reads content of file and decompresses it for .txt-files. Constructs an SOI-object based on the file content.

Parameters

file_path : string
Full path to a file containing serialized SOI.
database : Database
Database-instance passed to specific modules.

Returns

SOI
SOI-instance.

Raises

ValueError
If file content is invalid against SERIALIZED_SOI_SCHEMA.
TypeError
If 'type' of module or attachment is not implemented.
Expand source code
def import_soi(file_path, database):
    """Import compressed or uncompressed serialized SOI.

    Reads content of file and decompresses it for .txt-files.
    Constructs an SOI-object based on the file content.

    Parameters
    ----------
    file_path : string
        Full path to a file containing serialized SOI.
    database : soitool.database.Database
        Database-instance passed to specific modules.

    Returns
    -------
    soitool.soi.SOI
        SOI-instance.

    Raises
    ------
    ValueError
        If file content is invalid against SERIALIZED_SOI_SCHEMA.
    TypeError
        If 'type' of module or attachment is not implemented.
    """
    with open(file_path, "r") as file:
        serialized = file.read()

        if file_path.endswith(".txt"):
            return construct_soi_from_serialized(
                serialized, database, compressed=True
            )

        return construct_soi_from_serialized(serialized, database)
def serialize_soi(soi)

Serialize SOI to JSON-string.

Parameters

soi : SOI
SOI to serialize.

Returns

String
JSON-string containing all SOI-information.

Raises

TypeError
Raises error if parameter 'soi' is not an SOI.
Expand source code
def serialize_soi(soi):
    """Serialize SOI to JSON-string.

    Parameters
    ----------
    soi : soitool.soi.SOI
        SOI to serialize.

    Returns
    -------
    String
        JSON-string containing all SOI-information.

    Raises
    ------
    TypeError
        Raises error if parameter 'soi' is not an SOI.
    """
    # If parameter 'soi' is not an SOI
    if not isinstance(soi, SOI):
        raise TypeError(
            "Invalid type for parameter 'soi': " + "'{}'.".format(soi)
        )

    # Create dict with relevant module-information
    modules = []
    for module in soi.modules:
        modules.append(
            {
                "type": module["widget"].type,
                "data": module["widget"].get_data(),
                "meta": module["meta"],
            }
        )

    # Create dict with relevant attachment-information
    attachments = []
    for attachment in soi.attachments:
        attachments.append(
            {
                "type": attachment["widget"].type,
                "data": attachment["widget"].get_data(),
                "meta": attachment["meta"],
            }
        )

    # Create dict with all relevant SOI-information
    serialized = {
        "title": soi.title,
        "description": soi.description,
        "version": soi.version,
        "date": soi.date,
        "valid": {"from_date": soi.valid_from, "to_date": soi.valid_to},
        "icon": soi.icon,
        "classification": soi.classification,
        "orientation": soi.orientation,
        "placement_strategy": soi.placement_strategy,
        "algorithm_bin": soi.algorithm_bin,
        "algorithm_pack": soi.algorithm_pack,
        "algorithm_sort": soi.algorithm_sort,
        "modules": modules,
        "attachments": attachments,
    }

    return json.dumps(serialized)