Module database

Expand source code
import datetime
import os
import re
import sqlite3 as sql
import tempfile
from src.logic.log import MyLogger
#from icecream import ic
from typing import List, Tuple, Dict, Any, Optional, Union
from pathlib import Path
from omegaconf import OmegaConf
from src.backend.db import CREATE_TABLE_APPARAT, CREATE_TABLE_MESSAGES, CREATE_TABLE_MEDIA, CREATE_TABLE_APPKONTOS, CREATE_TABLE_FILES, CREATE_TABLE_PROF, CREATE_TABLE_USER, CREATE_TABLE_SUBJECTS
from src.logic.constants import SEMAP_MEDIA_ACCOUNTS
from src.logic.dataclass import ApparatData, BookData
from src.errors import NoResultError, AppPresentError
from src.utils import load_pickle, dump_pickle,create_blob
config = OmegaConf.load("config.yaml")
logger = MyLogger(__name__)


class Database:
    """
    Initialize the database and create the tables if they do not exist.
    """
    def __init__(self, db_path: str = None):
        """
        Default constructor for the database class

        Args:
            db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
        """
        if db_path is None:
            self.db_path = config.database.path + config.database.name
            self.db_path = self.db_path.replace("~", str(Path.home()))
        else:
            self.db_path = db_path
        if self.get_db_contents() is None:
            logger.log_critical("Database does not exist, creating tables")
            self.create_tables()
    def get_db_contents(self)->Union[List[Tuple], None]:
        """
        Get the contents of the 

        Returns:
            Union[List[Tuple], None]: _description_
        """
        try:
            with sql.connect(self.db_path) as conn:
                cursor = conn.cursor()
                cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
                return cursor.fetchall()
        except sql.OperationalError:
            return None
    def connect(self)->sql.Connection:
        """
        Connect to the database

        Returns:
            sql.Connection: The active connection to the database
        """
        return sql.connect(self.db_path)
    def close_connection(self, conn: sql.Connection):
        """
        closes the connection to the database
        
        Args:
        ----
            - conn (sql.Connection): the connection to be closed
        """
        conn.close()
    def create_tables(self):
        """
        Create the tables in the database
        """
        conn = self.connect()
        cursor = conn.cursor()
        cursor.execute(CREATE_TABLE_APPARAT)
        cursor.execute(CREATE_TABLE_MESSAGES)
        cursor.execute(CREATE_TABLE_MEDIA)
        cursor.execute(CREATE_TABLE_APPKONTOS)
        cursor.execute(CREATE_TABLE_FILES)
        cursor.execute(CREATE_TABLE_PROF)
        cursor.execute(CREATE_TABLE_USER)
        cursor.execute(CREATE_TABLE_SUBJECTS)
        conn.commit()
        self.close_connection(conn)
    def insertInto(self, query:str, params:Tuple) -> None:
        """
        Insert sent data into the database

        Args:
            query (str): The query to be executed
            params (Tuple): the parameters to be inserted into the database
        """
        conn = self.connect()
        cursor = conn.cursor()
        logger.log_info(f"Inserting {params} into database with query {query}")
        cursor.execute(query, params)
        conn.commit()
        self.close_connection(conn)
    def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
        """
        Query the Database for the sent query.

        Args:
            query (str): The query to be executed
            args (Tuple, optional): The arguments for the query. Defaults to ().
            one (bool, optional): Return the first result only. Defaults to False.

        Returns:
            Union[Typle|List[Tuple]]: Returns the result of the query
        """
        conn = self.connect()
        cursor = conn.cursor()
        logger.log_info(f"Querying database with query {query}, args: {args}")
        cursor.execute(query, args)
        rv = cursor.fetchall()
        conn.commit()
        self.close_connection(conn)
        return (rv[0] if rv else None) if one else rv

    # Books
    def addBookToDatabase(self, bookdata:BookData,app_id:Union[str,int], prof_id:Union[str,int]):
        """
        Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

        Args:
            bookdata (BookData): The metadata of the book to be added
            app_id (str): The apparat id where the book should be added to
            prof_id (str): The id of the professor where the book should be added to.
        """
        conn = self.connect()
        cursor = conn.cursor()
        t_query = (
            f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
        )
        # print(t_query)
        result = cursor.execute(t_query).fetchall()
        result = [load_pickle(i[0]) for i in result]
        if bookdata in result:
            print("Bookdata already in database")
            # check if the book was deleted in the apparat
            query = (
                "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
            )
            params = (app_id, prof_id, dump_pickle(bookdata))
            result = cursor.execute(query, params).fetchone()
            if result[0] == 1:
                print("Book was deleted, updating bookdata")
                query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
                params = (app_id, prof_id, dump_pickle(bookdata))
                cursor.execute(query, params)
                conn.commit()
            return

        query = (
            "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
        )
        converted = dump_pickle(bookdata)
        params = (converted, app_id, prof_id, 0)
        cursor.execute(query, params)
        conn.commit()
        self.close_connection(conn)
    def getBookIdBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->int:
        """
        Get a book id based on the signature of the book.

        Args:
            app_id (str): The apparat id the book should be associated with
            prof_id (str): The professor id the book should be associated with
            signature (str): The signature of the book

        Returns:
            int: The id of the book
        """
        result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
        books = [(load_pickle(i[0]),i[1]) for i in result]
        book = [i for i in books if i[0].signature == signature][0][1]
        return book
    def getBookBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->BookData:
        """
        Get the book based on the signature of the book.

        Args:
            app_id (str): The apparat id the book should be associated with
            prof_id (str): The professor id the book should be associated with
            signature (str): The signature of the book

        Returns:
            BookData: The total metadata of the book wrapped in a BookData object
        """
        result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
        books = [load_pickle(i[0]) for i in result]
        book = [i for i in books if i.signature == signature][0]
        return book
    def getLastBookId(self)->int:
        """
        Get the last book id in the database

        Returns:
            int: ID of the last book in the database
        """
        return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
    def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
        """
        Search a book in the database based on the sent data.

        Args:
            data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
            - signature: The signature of the book
            - title: The title of the book

        Returns:
            list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
        """
        rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
        #ic(rdata, len(rdata))
        mode = 0
        if len(data)== 1:
            if "signature" in data.keys():
                mode = 1
            elif "title" in data.keys():
                mode = 2
        elif len(data) == 2:
            mode = 3
        else:
            return None
        ret = []
        for book in rdata:
            bookdata = load_pickle(book[1])
            app_id = book[2]
            prof_id = book[3]
            if mode == 1:
                if data["signature"] in bookdata.signature:
                    ret.append((bookdata,app_id,prof_id))
            elif mode == 2:
                if data["title"] in bookdata.title:
                    ret.append((bookdata,app_id,prof_id))
            elif mode == 3:
                if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
                    ret.append((bookdata,app_id,prof_id))
        #ic(ret)
        return ret
    def setAvailability(self, book_id:str, available:str):
        """
        Set the availability of a book in the database

        Args:
            book_id (str): The id of the book
            available (str): The availability of the book
        """
        self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
    def getBookId(self, bookdata:BookData, app_id:Union[str,int], prof_id:Union[str,int])->int:
        """
        Get the id of a book based on the metadata of the book

        Args:
            bookdata (BookData): The wrapped metadata of the book
            app_id (str): The apparat id the book should be associated with
            prof_id (str): The professor id the book should be associated with

        Returns:
            int: ID of the book
        """
        result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
        return result[0]
    def getBook(self,book_id:int)->BookData:
        """
        Get the book based on the id in the database

        Args:
            book_id (int): The id of the book

        Returns:
            BookData: The metadata of the book wrapped in a BookData object
        """
        return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
    def getBooks(self, app_id:Union[str,int], prof_id:Union[str,int], deleted=0)->list[dict[int, BookData, int]]:
        """
        Get the Books based on the apparat id and the professor id

        Args:
            app_id (str): The ID of the apparat
            prof_id (str): The ID of the professor
            deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.

        Returns:
            list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
        """
        qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
        ret_result = []
        for result_a in qdata:
            data = {"id": int, "bookdata": BookData, "available": int}
            data["id"] = result_a[0]
            data["bookdata"] = load_pickle(result_a[1])
            data["available"] = result_a[2]
            ret_result.append(data)
        return ret_result
    def updateBookdata(self, book_id, bookdata:BookData):
        """
        Update the bookdata in the database

        Args:
            book_id (str): The id of the book
            bookdata (BookData): The new metadata of the book
        """
        self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
    def deleteBook(self, book_id):
        """
        Delete a book from the database

        Args:
            book_id (str): ID of the book
        """
        self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))

    # File Interactions
    def getBlob(self, filename, app_id:Union[str,int]):
        """
        Get a blob from the database

        Args:
            filename (str): The name of the file
            app_id (str): ID of the apparat

        Returns:
            bytes: The file stored in 
        """
        return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
    def insertFile(self, file: list[dict], app_id:Union[str,int], prof_id:Union[str,int]):
        """Instert a list of files into the database

        Args:
            file (list[dict]): a list containing all the files to be inserted
            Structured: [{"name": "filename", "path": "path", "type": "filetype"}]
            app_id (int): the id of the apparat
            prof_id (str): the id of the professor
        """
        for f in file:
            filename = f["name"]
            path = f["path"]
            filetyp = f["type"]
            if path == "Database":
                continue
            blob = create_blob(path)
            query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
            self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
    def recreateFile(self, filename:str, app_id:Union[str,int],filetype:str)->str:
        """Recreate a file from the database

        Args:
            filename (str): the name of the file
            app_id (Union[str,int]): the id of the apparat
            filetype (str): the extension of the file to be created

        Returns:
            str: The filename of the recreated file 
        """
        blob = self.getBlob(filename, app_id)
        tempdir = config.database.tempdir
        tempdir = tempdir.replace("~", str(Path.home()))
        tempdir_path = Path(tempdir)        
        if not os.path.exists(tempdir_path):
            os.mkdir(tempdir_path)
        file = tempfile.NamedTemporaryFile(
            delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
        )
        file.write(blob)
        print("file created")
        return file.name
    def getFiles(self, app_id:Union[str,int], prof_id:int)->list[tuple]:
        """Get all the files associated with the apparat and the professor

        Args:
            app_id (Union[str,int]): The id of the apparat
            prof_id (Union[str,int]): the id of the professor

        Returns:
            list[tuple]: a list of tuples containing the filename and the filetype for the corresponding apparat and professor
        """
        return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))

    def getSemersters(self)->list[str]:
        """Return all the unique semesters in the database

        Returns:
            list: a list of strings containing the semesters
        """
        data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
        return [i[0] for i in data]

    def getSubjects(self):
        """Get all the subjects in the database

        Returns:
            list[tuple]: a list of tuples containing the subjects
        """
        return self.query_db("SELECT * FROM subjects")

    # Messages
    def addMessage(self, message:dict,user:str, app_id:Union[str,int]):
        """add a Message to the database

        Args:
            message (dict): the message to be added
            user (str): the user who added the message
            app_id (Union[str,int]): the id of the apparat
        """
        def __getUserId(user):
            return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
        user_id = __getUserId(user)
        self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],app_id))
    def getMessages(self, date:str)->list[dict[str, str, str, str]]:
        """Get all the messages for a specific date

        Args:
            date (str): a date.datetime object formatted as a string in the format "YYYY-MM-DD"
        
        Returns:
            list[dict[str, str, str, str]]: a list of dictionaries containing the message, the user who added the message, the apparat id and the id of the message
        """
        def __get_user_name(user_id):
            return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
        messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
        ret = [
            {
                "message": i[2],
                "user": __get_user_name(i[4]),
                "appnr": i[5],
                "id": i[0]
            }
            for i in messages
        ]
        return ret
    def deleteMessage(self, message_id):
        """Delete a message from the database

        Args:
            message_id (str): the id of the message
        """
        self.query_db("DELETE FROM messages WHERE id=?", (message_id,))

    # Prof data
    def getProfNameById(self, prof_id:Union[str,int],add_title:bool=False)->str:
        """Get a professor name based on the id

        Args:
            prof_id (Union[str,int]): The id of the professor
            add_title (bool, optional): wether to add the title or no. Defaults to False.

        Returns:
            str: The name of the professor
        """
        prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
        if add_title:
            return f"{self.getTitleById(prof_id)}{prof[0]}"
        else:
            return prof[0]
    def getTitleById(self, prof_id:Union[str,int])->str:
        """get the title of a professor based on the id

        Args:
            prof_id (Union[str,int]): the id of the professor

        Returns:
            str: the title of the professor, with an added whitespace at the end, if no title is present, an empty string is returned
        """
        title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
        return f"{title} " if title is not None else ""
    def getProfByName(self, prof_name:str)->tuple:
        """get all the data of a professor based on the name

        Args:
            prof_name (str): the name of the professor

        Returns:
            tuple: the data of the professor
        """
        return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
    def getProfId(self, prof_name:str)->Optional[int]:
        """Get the id of a professor based on the name

        Args:
            prof_name (str): the name of the professor

        Returns:
            Optional[int]: the id of the professor, if the professor is not found, None is returned
        """
        
        data = self.getProfByName(prof_name.replace(",", ""))
        if data is None:
            return None
        else:
            return data[0]
    def getSpecificProfData(self, prof_id:Union[str,int], fields:List[str])->tuple:
        """A customisable function to get specific data of a professor based on the id

        Args:
            prof_id (Union[str,int]): the id of the professor
            fields (List[str]): a list of fields to be returned

        Returns:
            tuple: a tuple containing the requested data
        """
        query = "SELECT "
        for field in fields:
            query += f"{field},"
        query = query[:-1]
        query += " FROM prof WHERE id=?"
        return self.query_db(query, (prof_id,), one=True)[0]
    def getProfData(self, profname:str):
        """Get mail, telephone number and title of a professor based on the name

        Args:
            profname (str): name of the professor

        Returns:
            tuple: the mail, telephone number and title of the professor
        """
        data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
        return data
    def createProf(self, prof_details:dict):
        """Create a professor in the database

        Args:
            prof_details (dict): a dictionary containing the details of the professor
        """
        prof_title = prof_details["prof_title"]
        prof_fname = prof_details["profname"].split(",")[1]
        prof_fname = prof_fname.strip()
        prof_lname = prof_details["profname"].split(",")[0]
        prof_lname = prof_lname.strip()
        prof_fullname = prof_details["profname"].replace(",", "")
        prof_mail = prof_details["prof_mail"]
        prof_tel = prof_details["prof_tel"]
        params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
        query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
        self.insertInto(query=query, params=params)
    def getProfs(self)->list[tuple]:
        """Return all the professors in the database

        Returns:
            list[tuple]: a list containing all the professors in individual tuples
        """
        return self.query_db("SELECT * FROM prof")

    # Apparat
    def getAllAparats(self,deleted=0)->list[tuple]:
        """Get all the apparats in the database

        Args:
            deleted (int, optional): Switch the result to use . Defaults to 0.

        Returns:
            list[tuple]: a list of tuples containing the apparats
        """
        return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
    def getApparatData(self, appnr, appname)->ApparatData:
        """Get the Apparat data based on the apparat number and the name

        Args:
            appnr (str): the apparat number
            appname (str): the name of the apparat

        Raises:
            NoResultError: an error is raised if no result is found

        Returns:
            ApparatData: the appended data of the apparat wrapped in an ApparatData object
        """
        result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
        if result is None:
            raise NoResultError("No result found")
        apparat = ApparatData()
        apparat.appname = result[1]
        apparat.appnr = result[4]
        apparat.dauerapp = True if result[7] == 1 else False
        prof_data = self.getProfData(self.getProfNameById(result[2]))
        apparat.profname = self.getProfNameById(result[2])
        apparat.prof_mail = prof_data[0]
        apparat.prof_tel = prof_data[1]
        apparat.prof_title = prof_data[2]
        apparat.app_fach = result[3]
        apparat.erstellsemester = result[5]
        apparat.semester = result[8]
        apparat.deleted = result[9]
        apparat.apparat_adis_id = result[11]
        apparat.prof_adis_id = result[12]
        return apparat
    def getUnavailableApparatNumbers(self)->List[int]:
        """Get a list of all the apparat numbers in the database that are currently in use

        Returns:
            List[int]: the list of used apparat numbers
        """
        numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
        numbers = [i[0] for i in numbers]
        logger.log_info(f"Currently used apparat numbers: {numbers}")
        return numbers
    def setNewSemesterDate(self, app_id:Union[str,int], newDate, dauerapp=False):
        """Set the new semester date for an apparat

        Args:
            app_id (Union[str,int]): the id of the apparat
            newDate (str): the new date
            dauerapp (bool, optional): if the apparat was changed to dauerapparat. Defaults to False.
        """
        date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
        if dauerapp:
            self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,app_id))
        else:
            self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,app_id))
    def getApparatId(self, apparat_name)->Optional[int]:
        """get the id of an apparat based on the name

        Args:
            apparat_name (str): the name of the apparat e.g. "Semesterapparat 1"

        Returns:
            Optional[int]: the id of the apparat, if the apparat is not found, None is returned
        """
        data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
        if data is None:
            return None
        else:
            return data[0]
    def createApparat(self, apparat:ApparatData)->int:
        """create the apparat in the database

        Args:
            apparat (ApparatData): the wrapped metadata of the apparat

        Raises:
            AppPresentError: an error describing that the apparats chosen id is already present in the database

        Returns:
            Optional[int]: the id of the apparat
        """
                
        prof_id = self.getProfId(apparat.profname)
        app_id = self.getApparatId(apparat.appname)
        if app_id:
            raise AppPresentError(app_id)

        self.createProf(apparat.get_prof_details())
        prof_id = self.getProfId(apparat.profname)
        #ic(prof_id)
        query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
        logger.log_info(query)
        self.query_db(query)
        return self.getApparatId(apparat.appname)
    def getApparatsByProf(self, prof_id:Union[str,int])->list[tuple]:
        """Get all apparats based on the professor id

        Args:
            prof_id (Union[str,int]): the id of the professor

        Returns:
            list[tuple]: a list of tuples containing the apparats
        """
        return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
    def getApparatsBySemester(self, semester:str)->dict[list]:
        """get all apparats based on the semester

        Args:
            semester (str): the selected semester

        Returns:
            dict[list]: a list off all created and deleted apparats for the selected semester
        """
        data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
        conn = self.connect()
        cursor = conn.cursor()
        c_tmp = []
        for i in data:
            c_tmp.append((i[0], self.getProfNameById(i[1])))
        query = (
            f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
        )
        result = cursor.execute(query).fetchall()
        d_tmp = []
        for i in result:
            d_tmp.append((i[0], self.getProfNameById(i[1])))
        # group the apparats by prof
        c_ret = {}
        for i in c_tmp:
            if i[1] not in c_ret.keys():
                c_ret[i[1]] = [i[0]]
            else:
                c_ret[i[1]].append(i[0])
        d_ret = {}
        for i in d_tmp:
            if i[1] not in d_ret.keys():
                d_ret[i[1]] = [i[0]]
            else:
                d_ret[i[1]].append(i[0])
        self.close_connection(conn)
        return {"created": c_ret, "deleted": d_ret}
    def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
        """get a list of all apparats created and deleted by semester

        Returns:
            tuple[list[str],list[int]]: a tuple containing two lists, the first list contains the semesters, the second list contains the amount of apparats created and deleted for the corresponding semester
        """
        conn = self.connect()
        cursor = conn.cursor()
        semesters = self.getSemersters()
        created = []
        deleted = []
        for semester in semesters:
            query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
            result = cursor.execute(query).fetchone()
            created.append(result[0])
            query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
            result = cursor.execute(query).fetchone()
            deleted.append(result[0])
        # store data in a tuple
        ret = []
        e_tuple = ()
        for sem in semesters:
            e_tuple = (
                sem,
                created[semesters.index(sem)],
                deleted[semesters.index(sem)],
            )
            ret.append(e_tuple)
        self.close_connection(conn)
        return ret 
    def deleteApparat(self, app_id:Union[str,int], semester:str):
        """Delete an apparat from the database

        Args:
            app_id (Union[str, int]): the id of the apparat
            semester (str): the semester the apparat should be deleted from
        """
        self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,app_id)) 
    def isEternal(self, id):
        """check if the apparat is eternal (dauerapparat)

        Args:
            id (int): the id of the apparat to be checked

        Returns:
            int: the state of the apparat
        """
        return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
    def getApparatName(self, app_id:Union[str,int], prof_id:Union[str,int]):
        """get the name of the apparat based on the id

        Args:
            app_id (Union[str,int]): the id of the apparat
            prof_id (Union[str,int]): the id of the professor

        Returns:
            str: the name of the apparat
        """
        return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
    def updateApparat(self, apparat_data:ApparatData):
        """Update an apparat in the database

        Args:
            apparat_data (ApparatData): the new metadata of the apparat
        """
        query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
        params = (
            apparat_data.appname,
            apparat_data.app_fach,
            apparat_data.dauerapp,
            self.getProfId(apparat_data.profname),
            apparat_data.appnr,        
        )
        self.query_db(query, params)
    def checkApparatExists(self, apparat_name:str):
        """check if the apparat is already present in the database based on the name

        Args:
            apparat_name (str): the name of the apparat

        Returns:
            bool: True if the apparat is present, False if not
        """
        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
    def checkApparatExistsById(self, app_id:Union[str,int])->bool:
        """a check to see if the apparat is already present in the database, based on the id

        Args:
            app_id (Union[str, int]): the id of the apparat

        Returns:
            bool: True if the apparat is present, False if not
        """
        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (app_id,), one=True) else False
    # Statistics
    def statistic_request(self, **kwargs: Any):
        """Take n amount of kwargs and return the result of the query
        """
        def __query(query):
            """execute the query and return the result

            Args:
                query (str): the constructed query

            Returns:
                list: the result of the query
            """
            conn = self.connect()
            cursor = conn.cursor()
            result = cursor.execute(query).fetchall()
            for result_a in result:
                orig_value = result_a
                prof_name = self.getProfNameById(result_a[2])
                # replace the prof_id with the prof_name
                result_a = list(result_a)
                result_a[2] = prof_name
                result_a = tuple(result_a)
                result[result.index(orig_value)] = result_a
            self.close_connection(conn)
            return result
        if "deletable" in kwargs.keys():
            query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
            return __query(query)
        if "dauer" in kwargs.keys():
            kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
        query = "SELECT * FROM semesterapparat WHERE "
        for key, value in kwargs.items() if kwargs.items() is not None else {}:
            print(key, value)
            query += f"{key}='{value}' AND "
            print(query)
        # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
        if "deletesemester" in kwargs.keys():
            query = query.replace(
                f"deletesemester='{kwargs['deletesemester']}' AND ", ""
            )
        if "endsemester" in kwargs.keys():
            if "erstellsemester" in kwargs.keys():
                query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
                query = query.replace(
                    f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
                )
            else:
                query = query.replace(
                    f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
                )
                print("replaced")
            query = query.replace(
                "xyz",
                f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
            )
        # remove all x="" parts from the query where x is a key in kwargs
        query = query[:-5]
        print(query)
        return __query(query)

    # Admin data
    def getUser(self):
        """Get a single user from the database"""
        return self.query_db("SELECT * FROM user", one=True)
    def getUsers(self)->list[tuple]:
        """Return a list of tuples of all the users in the database"""
        return self.query_db("SELECT * FROM user")

    def login(self, user, hashed_password):
        """try to login the user.
        The salt for the user will be requested from the database and then added to the hashed password. The password will then be compared to the password in the database 

        Args:
            user (str): username that tries to login
            hashed_password (str): the password the user tries to login with

        Returns:
            bool: True if the login was successful, False if not
        """
        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
        if salt is None:
            return False
        hashed_password = salt + hashed_password
        password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
        if password == hashed_password:
            return True
        else:
            return False
    def changePassword(self, user, new_password):
        """change the password of a user.
        The password will be added with the salt and then committed to the database

        Args:
            user (str): username
            new_password (str): the hashed password
        """
        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
        new_password = salt + new_password
        self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
    def getRole(self, user):
        """get the role of the user

        Args:
            user (str): username

        Returns:
            str: the name of the role
        """
        return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
    def getRoles(self)->list[tuple]:
        """get all the roles in the database

        Returns:
            list[str]: a list of all the roles
        """
        return self.query_db("SELECT role FROM user")
    def checkUsername(self, user)->bool:
        """a check to see if the username is already present in the database

        Args:
            user (str): the username

        Returns:
            bool: True if the username is present, False if not
        """
        data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
        return True if data is not None else False
    def createUser(self, user, password, role, salt):
        """create an user from the AdminCommands class.

        Args:
            user (str): the username of the user
            password (str): a hashed password
            role (str): the role of the user
            salt (str): a salt for the password
        """
        self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
    def deleteUser(self, user):
        """delete an unser

        Args:
            user (str): username of the user
        """
        self.query_db("DELETE FROM user WHERE username=?", (user,))
    def updateUser(self, username, data:dict[str, str]):
        """changge the data of a user

        Args:
            username (str): the username of the user
            data (dict[str, str]): the data to be changed
        """
        conn = self.connect()
        cursor = conn.cursor()
        query = "UPDATE user SET "
        for key,value in data.items():
            if key == "username":
                continue
            query += f"{key}='{value}',"
        query = query[:-1]
        query += " WHERE username=?"
        params = (username,)
        cursor.execute(query, params)
        conn.commit()
        self.close_connection(conn)
    def getFacultyMember(self, name:str)->tuple:
        """get a faculty member based on the name

        Args:
            name (str): the name to be searched for

        Returns:
            tuple: a tuple containing the data of the faculty member
        """
        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
    def updateFacultyMember(self, data:dict, oldlname:str, oldfname:str):
        """update the data of a faculty member

        Args:
            data (dict): a dictionary containing the data to be updated
            oldlname (str): the old last name of the faculty member
            oldfname (str): the old first name of the faculty member
        """
        placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
        query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
        data["oldlname"] = oldlname
        data["oldfname"] = oldfname
        self.query_db(query, data)
    def getFacultyMembers(self):
        """get a list of all faculty members

        Returns:
            list[tuple]: a list of tuples containing the faculty members
        """
        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")

Classes

class Database (db_path: str = None)

Initialize the database and create the tables if they do not exist.

Default constructor for the database class

Args

db_path : str, optional
Optional Path for testing / specific purposes. Defaults to None.
Expand source code
class Database:
    """
    Initialize the database and create the tables if they do not exist.
    """
    def __init__(self, db_path: str = None):
        """
        Default constructor for the database class

        Args:
            db_path (str, optional): Optional Path for testing / specific purposes. Defaults to None.
        """
        if db_path is None:
            self.db_path = config.database.path + config.database.name
            self.db_path = self.db_path.replace("~", str(Path.home()))
        else:
            self.db_path = db_path
        if self.get_db_contents() is None:
            logger.log_critical("Database does not exist, creating tables")
            self.create_tables()
    def get_db_contents(self)->Union[List[Tuple], None]:
        """
        Get the contents of the 

        Returns:
            Union[List[Tuple], None]: _description_
        """
        try:
            with sql.connect(self.db_path) as conn:
                cursor = conn.cursor()
                cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
                return cursor.fetchall()
        except sql.OperationalError:
            return None
    def connect(self)->sql.Connection:
        """
        Connect to the database

        Returns:
            sql.Connection: The active connection to the database
        """
        return sql.connect(self.db_path)
    def close_connection(self, conn: sql.Connection):
        """
        closes the connection to the database
        
        Args:
        ----
            - conn (sql.Connection): the connection to be closed
        """
        conn.close()
    def create_tables(self):
        """
        Create the tables in the database
        """
        conn = self.connect()
        cursor = conn.cursor()
        cursor.execute(CREATE_TABLE_APPARAT)
        cursor.execute(CREATE_TABLE_MESSAGES)
        cursor.execute(CREATE_TABLE_MEDIA)
        cursor.execute(CREATE_TABLE_APPKONTOS)
        cursor.execute(CREATE_TABLE_FILES)
        cursor.execute(CREATE_TABLE_PROF)
        cursor.execute(CREATE_TABLE_USER)
        cursor.execute(CREATE_TABLE_SUBJECTS)
        conn.commit()
        self.close_connection(conn)
    def insertInto(self, query:str, params:Tuple) -> None:
        """
        Insert sent data into the database

        Args:
            query (str): The query to be executed
            params (Tuple): the parameters to be inserted into the database
        """
        conn = self.connect()
        cursor = conn.cursor()
        logger.log_info(f"Inserting {params} into database with query {query}")
        cursor.execute(query, params)
        conn.commit()
        self.close_connection(conn)
    def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
        """
        Query the Database for the sent query.

        Args:
            query (str): The query to be executed
            args (Tuple, optional): The arguments for the query. Defaults to ().
            one (bool, optional): Return the first result only. Defaults to False.

        Returns:
            Union[Typle|List[Tuple]]: Returns the result of the query
        """
        conn = self.connect()
        cursor = conn.cursor()
        logger.log_info(f"Querying database with query {query}, args: {args}")
        cursor.execute(query, args)
        rv = cursor.fetchall()
        conn.commit()
        self.close_connection(conn)
        return (rv[0] if rv else None) if one else rv

    # Books
    def addBookToDatabase(self, bookdata:BookData,app_id:Union[str,int], prof_id:Union[str,int]):
        """
        Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

        Args:
            bookdata (BookData): The metadata of the book to be added
            app_id (str): The apparat id where the book should be added to
            prof_id (str): The id of the professor where the book should be added to.
        """
        conn = self.connect()
        cursor = conn.cursor()
        t_query = (
            f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
        )
        # print(t_query)
        result = cursor.execute(t_query).fetchall()
        result = [load_pickle(i[0]) for i in result]
        if bookdata in result:
            print("Bookdata already in database")
            # check if the book was deleted in the apparat
            query = (
                "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
            )
            params = (app_id, prof_id, dump_pickle(bookdata))
            result = cursor.execute(query, params).fetchone()
            if result[0] == 1:
                print("Book was deleted, updating bookdata")
                query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
                params = (app_id, prof_id, dump_pickle(bookdata))
                cursor.execute(query, params)
                conn.commit()
            return

        query = (
            "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
        )
        converted = dump_pickle(bookdata)
        params = (converted, app_id, prof_id, 0)
        cursor.execute(query, params)
        conn.commit()
        self.close_connection(conn)
    def getBookIdBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->int:
        """
        Get a book id based on the signature of the book.

        Args:
            app_id (str): The apparat id the book should be associated with
            prof_id (str): The professor id the book should be associated with
            signature (str): The signature of the book

        Returns:
            int: The id of the book
        """
        result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
        books = [(load_pickle(i[0]),i[1]) for i in result]
        book = [i for i in books if i[0].signature == signature][0][1]
        return book
    def getBookBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->BookData:
        """
        Get the book based on the signature of the book.

        Args:
            app_id (str): The apparat id the book should be associated with
            prof_id (str): The professor id the book should be associated with
            signature (str): The signature of the book

        Returns:
            BookData: The total metadata of the book wrapped in a BookData object
        """
        result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
        books = [load_pickle(i[0]) for i in result]
        book = [i for i in books if i.signature == signature][0]
        return book
    def getLastBookId(self)->int:
        """
        Get the last book id in the database

        Returns:
            int: ID of the last book in the database
        """
        return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
    def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
        """
        Search a book in the database based on the sent data.

        Args:
            data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
            - signature: The signature of the book
            - title: The title of the book

        Returns:
            list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
        """
        rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
        #ic(rdata, len(rdata))
        mode = 0
        if len(data)== 1:
            if "signature" in data.keys():
                mode = 1
            elif "title" in data.keys():
                mode = 2
        elif len(data) == 2:
            mode = 3
        else:
            return None
        ret = []
        for book in rdata:
            bookdata = load_pickle(book[1])
            app_id = book[2]
            prof_id = book[3]
            if mode == 1:
                if data["signature"] in bookdata.signature:
                    ret.append((bookdata,app_id,prof_id))
            elif mode == 2:
                if data["title"] in bookdata.title:
                    ret.append((bookdata,app_id,prof_id))
            elif mode == 3:
                if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
                    ret.append((bookdata,app_id,prof_id))
        #ic(ret)
        return ret
    def setAvailability(self, book_id:str, available:str):
        """
        Set the availability of a book in the database

        Args:
            book_id (str): The id of the book
            available (str): The availability of the book
        """
        self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
    def getBookId(self, bookdata:BookData, app_id:Union[str,int], prof_id:Union[str,int])->int:
        """
        Get the id of a book based on the metadata of the book

        Args:
            bookdata (BookData): The wrapped metadata of the book
            app_id (str): The apparat id the book should be associated with
            prof_id (str): The professor id the book should be associated with

        Returns:
            int: ID of the book
        """
        result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
        return result[0]
    def getBook(self,book_id:int)->BookData:
        """
        Get the book based on the id in the database

        Args:
            book_id (int): The id of the book

        Returns:
            BookData: The metadata of the book wrapped in a BookData object
        """
        return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
    def getBooks(self, app_id:Union[str,int], prof_id:Union[str,int], deleted=0)->list[dict[int, BookData, int]]:
        """
        Get the Books based on the apparat id and the professor id

        Args:
            app_id (str): The ID of the apparat
            prof_id (str): The ID of the professor
            deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.

        Returns:
            list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
        """
        qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
        ret_result = []
        for result_a in qdata:
            data = {"id": int, "bookdata": BookData, "available": int}
            data["id"] = result_a[0]
            data["bookdata"] = load_pickle(result_a[1])
            data["available"] = result_a[2]
            ret_result.append(data)
        return ret_result
    def updateBookdata(self, book_id, bookdata:BookData):
        """
        Update the bookdata in the database

        Args:
            book_id (str): The id of the book
            bookdata (BookData): The new metadata of the book
        """
        self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
    def deleteBook(self, book_id):
        """
        Delete a book from the database

        Args:
            book_id (str): ID of the book
        """
        self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))

    # File Interactions
    def getBlob(self, filename, app_id:Union[str,int]):
        """
        Get a blob from the database

        Args:
            filename (str): The name of the file
            app_id (str): ID of the apparat

        Returns:
            bytes: The file stored in 
        """
        return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
    def insertFile(self, file: list[dict], app_id:Union[str,int], prof_id:Union[str,int]):
        """Instert a list of files into the database

        Args:
            file (list[dict]): a list containing all the files to be inserted
            Structured: [{"name": "filename", "path": "path", "type": "filetype"}]
            app_id (int): the id of the apparat
            prof_id (str): the id of the professor
        """
        for f in file:
            filename = f["name"]
            path = f["path"]
            filetyp = f["type"]
            if path == "Database":
                continue
            blob = create_blob(path)
            query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
            self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
    def recreateFile(self, filename:str, app_id:Union[str,int],filetype:str)->str:
        """Recreate a file from the database

        Args:
            filename (str): the name of the file
            app_id (Union[str,int]): the id of the apparat
            filetype (str): the extension of the file to be created

        Returns:
            str: The filename of the recreated file 
        """
        blob = self.getBlob(filename, app_id)
        tempdir = config.database.tempdir
        tempdir = tempdir.replace("~", str(Path.home()))
        tempdir_path = Path(tempdir)        
        if not os.path.exists(tempdir_path):
            os.mkdir(tempdir_path)
        file = tempfile.NamedTemporaryFile(
            delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
        )
        file.write(blob)
        print("file created")
        return file.name
    def getFiles(self, app_id:Union[str,int], prof_id:int)->list[tuple]:
        """Get all the files associated with the apparat and the professor

        Args:
            app_id (Union[str,int]): The id of the apparat
            prof_id (Union[str,int]): the id of the professor

        Returns:
            list[tuple]: a list of tuples containing the filename and the filetype for the corresponding apparat and professor
        """
        return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))

    def getSemersters(self)->list[str]:
        """Return all the unique semesters in the database

        Returns:
            list: a list of strings containing the semesters
        """
        data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
        return [i[0] for i in data]

    def getSubjects(self):
        """Get all the subjects in the database

        Returns:
            list[tuple]: a list of tuples containing the subjects
        """
        return self.query_db("SELECT * FROM subjects")

    # Messages
    def addMessage(self, message:dict,user:str, app_id:Union[str,int]):
        """add a Message to the database

        Args:
            message (dict): the message to be added
            user (str): the user who added the message
            app_id (Union[str,int]): the id of the apparat
        """
        def __getUserId(user):
            return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
        user_id = __getUserId(user)
        self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],app_id))
    def getMessages(self, date:str)->list[dict[str, str, str, str]]:
        """Get all the messages for a specific date

        Args:
            date (str): a date.datetime object formatted as a string in the format "YYYY-MM-DD"
        
        Returns:
            list[dict[str, str, str, str]]: a list of dictionaries containing the message, the user who added the message, the apparat id and the id of the message
        """
        def __get_user_name(user_id):
            return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
        messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
        ret = [
            {
                "message": i[2],
                "user": __get_user_name(i[4]),
                "appnr": i[5],
                "id": i[0]
            }
            for i in messages
        ]
        return ret
    def deleteMessage(self, message_id):
        """Delete a message from the database

        Args:
            message_id (str): the id of the message
        """
        self.query_db("DELETE FROM messages WHERE id=?", (message_id,))

    # Prof data
    def getProfNameById(self, prof_id:Union[str,int],add_title:bool=False)->str:
        """Get a professor name based on the id

        Args:
            prof_id (Union[str,int]): The id of the professor
            add_title (bool, optional): wether to add the title or no. Defaults to False.

        Returns:
            str: The name of the professor
        """
        prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
        if add_title:
            return f"{self.getTitleById(prof_id)}{prof[0]}"
        else:
            return prof[0]
    def getTitleById(self, prof_id:Union[str,int])->str:
        """get the title of a professor based on the id

        Args:
            prof_id (Union[str,int]): the id of the professor

        Returns:
            str: the title of the professor, with an added whitespace at the end, if no title is present, an empty string is returned
        """
        title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
        return f"{title} " if title is not None else ""
    def getProfByName(self, prof_name:str)->tuple:
        """get all the data of a professor based on the name

        Args:
            prof_name (str): the name of the professor

        Returns:
            tuple: the data of the professor
        """
        return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
    def getProfId(self, prof_name:str)->Optional[int]:
        """Get the id of a professor based on the name

        Args:
            prof_name (str): the name of the professor

        Returns:
            Optional[int]: the id of the professor, if the professor is not found, None is returned
        """
        
        data = self.getProfByName(prof_name.replace(",", ""))
        if data is None:
            return None
        else:
            return data[0]
    def getSpecificProfData(self, prof_id:Union[str,int], fields:List[str])->tuple:
        """A customisable function to get specific data of a professor based on the id

        Args:
            prof_id (Union[str,int]): the id of the professor
            fields (List[str]): a list of fields to be returned

        Returns:
            tuple: a tuple containing the requested data
        """
        query = "SELECT "
        for field in fields:
            query += f"{field},"
        query = query[:-1]
        query += " FROM prof WHERE id=?"
        return self.query_db(query, (prof_id,), one=True)[0]
    def getProfData(self, profname:str):
        """Get mail, telephone number and title of a professor based on the name

        Args:
            profname (str): name of the professor

        Returns:
            tuple: the mail, telephone number and title of the professor
        """
        data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
        return data
    def createProf(self, prof_details:dict):
        """Create a professor in the database

        Args:
            prof_details (dict): a dictionary containing the details of the professor
        """
        prof_title = prof_details["prof_title"]
        prof_fname = prof_details["profname"].split(",")[1]
        prof_fname = prof_fname.strip()
        prof_lname = prof_details["profname"].split(",")[0]
        prof_lname = prof_lname.strip()
        prof_fullname = prof_details["profname"].replace(",", "")
        prof_mail = prof_details["prof_mail"]
        prof_tel = prof_details["prof_tel"]
        params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
        query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
        self.insertInto(query=query, params=params)
    def getProfs(self)->list[tuple]:
        """Return all the professors in the database

        Returns:
            list[tuple]: a list containing all the professors in individual tuples
        """
        return self.query_db("SELECT * FROM prof")

    # Apparat
    def getAllAparats(self,deleted=0)->list[tuple]:
        """Get all the apparats in the database

        Args:
            deleted (int, optional): Switch the result to use . Defaults to 0.

        Returns:
            list[tuple]: a list of tuples containing the apparats
        """
        return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
    def getApparatData(self, appnr, appname)->ApparatData:
        """Get the Apparat data based on the apparat number and the name

        Args:
            appnr (str): the apparat number
            appname (str): the name of the apparat

        Raises:
            NoResultError: an error is raised if no result is found

        Returns:
            ApparatData: the appended data of the apparat wrapped in an ApparatData object
        """
        result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
        if result is None:
            raise NoResultError("No result found")
        apparat = ApparatData()
        apparat.appname = result[1]
        apparat.appnr = result[4]
        apparat.dauerapp = True if result[7] == 1 else False
        prof_data = self.getProfData(self.getProfNameById(result[2]))
        apparat.profname = self.getProfNameById(result[2])
        apparat.prof_mail = prof_data[0]
        apparat.prof_tel = prof_data[1]
        apparat.prof_title = prof_data[2]
        apparat.app_fach = result[3]
        apparat.erstellsemester = result[5]
        apparat.semester = result[8]
        apparat.deleted = result[9]
        apparat.apparat_adis_id = result[11]
        apparat.prof_adis_id = result[12]
        return apparat
    def getUnavailableApparatNumbers(self)->List[int]:
        """Get a list of all the apparat numbers in the database that are currently in use

        Returns:
            List[int]: the list of used apparat numbers
        """
        numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
        numbers = [i[0] for i in numbers]
        logger.log_info(f"Currently used apparat numbers: {numbers}")
        return numbers
    def setNewSemesterDate(self, app_id:Union[str,int], newDate, dauerapp=False):
        """Set the new semester date for an apparat

        Args:
            app_id (Union[str,int]): the id of the apparat
            newDate (str): the new date
            dauerapp (bool, optional): if the apparat was changed to dauerapparat. Defaults to False.
        """
        date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
        if dauerapp:
            self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,app_id))
        else:
            self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,app_id))
    def getApparatId(self, apparat_name)->Optional[int]:
        """get the id of an apparat based on the name

        Args:
            apparat_name (str): the name of the apparat e.g. "Semesterapparat 1"

        Returns:
            Optional[int]: the id of the apparat, if the apparat is not found, None is returned
        """
        data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
        if data is None:
            return None
        else:
            return data[0]
    def createApparat(self, apparat:ApparatData)->int:
        """create the apparat in the database

        Args:
            apparat (ApparatData): the wrapped metadata of the apparat

        Raises:
            AppPresentError: an error describing that the apparats chosen id is already present in the database

        Returns:
            Optional[int]: the id of the apparat
        """
                
        prof_id = self.getProfId(apparat.profname)
        app_id = self.getApparatId(apparat.appname)
        if app_id:
            raise AppPresentError(app_id)

        self.createProf(apparat.get_prof_details())
        prof_id = self.getProfId(apparat.profname)
        #ic(prof_id)
        query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
        logger.log_info(query)
        self.query_db(query)
        return self.getApparatId(apparat.appname)
    def getApparatsByProf(self, prof_id:Union[str,int])->list[tuple]:
        """Get all apparats based on the professor id

        Args:
            prof_id (Union[str,int]): the id of the professor

        Returns:
            list[tuple]: a list of tuples containing the apparats
        """
        return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
    def getApparatsBySemester(self, semester:str)->dict[list]:
        """get all apparats based on the semester

        Args:
            semester (str): the selected semester

        Returns:
            dict[list]: a list off all created and deleted apparats for the selected semester
        """
        data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
        conn = self.connect()
        cursor = conn.cursor()
        c_tmp = []
        for i in data:
            c_tmp.append((i[0], self.getProfNameById(i[1])))
        query = (
            f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
        )
        result = cursor.execute(query).fetchall()
        d_tmp = []
        for i in result:
            d_tmp.append((i[0], self.getProfNameById(i[1])))
        # group the apparats by prof
        c_ret = {}
        for i in c_tmp:
            if i[1] not in c_ret.keys():
                c_ret[i[1]] = [i[0]]
            else:
                c_ret[i[1]].append(i[0])
        d_ret = {}
        for i in d_tmp:
            if i[1] not in d_ret.keys():
                d_ret[i[1]] = [i[0]]
            else:
                d_ret[i[1]].append(i[0])
        self.close_connection(conn)
        return {"created": c_ret, "deleted": d_ret}
    def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
        """get a list of all apparats created and deleted by semester

        Returns:
            tuple[list[str],list[int]]: a tuple containing two lists, the first list contains the semesters, the second list contains the amount of apparats created and deleted for the corresponding semester
        """
        conn = self.connect()
        cursor = conn.cursor()
        semesters = self.getSemersters()
        created = []
        deleted = []
        for semester in semesters:
            query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
            result = cursor.execute(query).fetchone()
            created.append(result[0])
            query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
            result = cursor.execute(query).fetchone()
            deleted.append(result[0])
        # store data in a tuple
        ret = []
        e_tuple = ()
        for sem in semesters:
            e_tuple = (
                sem,
                created[semesters.index(sem)],
                deleted[semesters.index(sem)],
            )
            ret.append(e_tuple)
        self.close_connection(conn)
        return ret 
    def deleteApparat(self, app_id:Union[str,int], semester:str):
        """Delete an apparat from the database

        Args:
            app_id (Union[str, int]): the id of the apparat
            semester (str): the semester the apparat should be deleted from
        """
        self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,app_id)) 
    def isEternal(self, id):
        """check if the apparat is eternal (dauerapparat)

        Args:
            id (int): the id of the apparat to be checked

        Returns:
            int: the state of the apparat
        """
        return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
    def getApparatName(self, app_id:Union[str,int], prof_id:Union[str,int]):
        """get the name of the apparat based on the id

        Args:
            app_id (Union[str,int]): the id of the apparat
            prof_id (Union[str,int]): the id of the professor

        Returns:
            str: the name of the apparat
        """
        return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
    def updateApparat(self, apparat_data:ApparatData):
        """Update an apparat in the database

        Args:
            apparat_data (ApparatData): the new metadata of the apparat
        """
        query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
        params = (
            apparat_data.appname,
            apparat_data.app_fach,
            apparat_data.dauerapp,
            self.getProfId(apparat_data.profname),
            apparat_data.appnr,        
        )
        self.query_db(query, params)
    def checkApparatExists(self, apparat_name:str):
        """check if the apparat is already present in the database based on the name

        Args:
            apparat_name (str): the name of the apparat

        Returns:
            bool: True if the apparat is present, False if not
        """
        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
    def checkApparatExistsById(self, app_id:Union[str,int])->bool:
        """a check to see if the apparat is already present in the database, based on the id

        Args:
            app_id (Union[str, int]): the id of the apparat

        Returns:
            bool: True if the apparat is present, False if not
        """
        return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (app_id,), one=True) else False
    # Statistics
    def statistic_request(self, **kwargs: Any):
        """Take n amount of kwargs and return the result of the query
        """
        def __query(query):
            """execute the query and return the result

            Args:
                query (str): the constructed query

            Returns:
                list: the result of the query
            """
            conn = self.connect()
            cursor = conn.cursor()
            result = cursor.execute(query).fetchall()
            for result_a in result:
                orig_value = result_a
                prof_name = self.getProfNameById(result_a[2])
                # replace the prof_id with the prof_name
                result_a = list(result_a)
                result_a[2] = prof_name
                result_a = tuple(result_a)
                result[result.index(orig_value)] = result_a
            self.close_connection(conn)
            return result
        if "deletable" in kwargs.keys():
            query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
            return __query(query)
        if "dauer" in kwargs.keys():
            kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
        query = "SELECT * FROM semesterapparat WHERE "
        for key, value in kwargs.items() if kwargs.items() is not None else {}:
            print(key, value)
            query += f"{key}='{value}' AND "
            print(query)
        # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
        if "deletesemester" in kwargs.keys():
            query = query.replace(
                f"deletesemester='{kwargs['deletesemester']}' AND ", ""
            )
        if "endsemester" in kwargs.keys():
            if "erstellsemester" in kwargs.keys():
                query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
                query = query.replace(
                    f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
                )
            else:
                query = query.replace(
                    f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
                )
                print("replaced")
            query = query.replace(
                "xyz",
                f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
            )
        # remove all x="" parts from the query where x is a key in kwargs
        query = query[:-5]
        print(query)
        return __query(query)

    # Admin data
    def getUser(self):
        """Get a single user from the database"""
        return self.query_db("SELECT * FROM user", one=True)
    def getUsers(self)->list[tuple]:
        """Return a list of tuples of all the users in the database"""
        return self.query_db("SELECT * FROM user")

    def login(self, user, hashed_password):
        """try to login the user.
        The salt for the user will be requested from the database and then added to the hashed password. The password will then be compared to the password in the database 

        Args:
            user (str): username that tries to login
            hashed_password (str): the password the user tries to login with

        Returns:
            bool: True if the login was successful, False if not
        """
        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
        if salt is None:
            return False
        hashed_password = salt + hashed_password
        password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
        if password == hashed_password:
            return True
        else:
            return False
    def changePassword(self, user, new_password):
        """change the password of a user.
        The password will be added with the salt and then committed to the database

        Args:
            user (str): username
            new_password (str): the hashed password
        """
        salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
        new_password = salt + new_password
        self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
    def getRole(self, user):
        """get the role of the user

        Args:
            user (str): username

        Returns:
            str: the name of the role
        """
        return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
    def getRoles(self)->list[tuple]:
        """get all the roles in the database

        Returns:
            list[str]: a list of all the roles
        """
        return self.query_db("SELECT role FROM user")
    def checkUsername(self, user)->bool:
        """a check to see if the username is already present in the database

        Args:
            user (str): the username

        Returns:
            bool: True if the username is present, False if not
        """
        data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
        return True if data is not None else False
    def createUser(self, user, password, role, salt):
        """create an user from the AdminCommands class.

        Args:
            user (str): the username of the user
            password (str): a hashed password
            role (str): the role of the user
            salt (str): a salt for the password
        """
        self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
    def deleteUser(self, user):
        """delete an unser

        Args:
            user (str): username of the user
        """
        self.query_db("DELETE FROM user WHERE username=?", (user,))
    def updateUser(self, username, data:dict[str, str]):
        """changge the data of a user

        Args:
            username (str): the username of the user
            data (dict[str, str]): the data to be changed
        """
        conn = self.connect()
        cursor = conn.cursor()
        query = "UPDATE user SET "
        for key,value in data.items():
            if key == "username":
                continue
            query += f"{key}='{value}',"
        query = query[:-1]
        query += " WHERE username=?"
        params = (username,)
        cursor.execute(query, params)
        conn.commit()
        self.close_connection(conn)
    def getFacultyMember(self, name:str)->tuple:
        """get a faculty member based on the name

        Args:
            name (str): the name to be searched for

        Returns:
            tuple: a tuple containing the data of the faculty member
        """
        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
    def updateFacultyMember(self, data:dict, oldlname:str, oldfname:str):
        """update the data of a faculty member

        Args:
            data (dict): a dictionary containing the data to be updated
            oldlname (str): the old last name of the faculty member
            oldfname (str): the old first name of the faculty member
        """
        placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
        query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
        data["oldlname"] = oldlname
        data["oldfname"] = oldfname
        self.query_db(query, data)
    def getFacultyMembers(self):
        """get a list of all faculty members

        Returns:
            list[tuple]: a list of tuples containing the faculty members
        """
        return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")

Methods

def addBookToDatabase(self, bookdata: src.logic.dataclass.BookData, app_id: Union[str, int], prof_id: Union[str, int])

Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

Args

bookdata : BookData
The metadata of the book to be added
app_id : str
The apparat id where the book should be added to
prof_id : str
The id of the professor where the book should be added to.
Expand source code
def addBookToDatabase(self, bookdata:BookData,app_id:Union[str,int], prof_id:Union[str,int]):
    """
    Add books to the database. Both app_id and prof_id are required to add the book to the database, as the app_id and prof_id are used to select the books later on.

    Args:
        bookdata (BookData): The metadata of the book to be added
        app_id (str): The apparat id where the book should be added to
        prof_id (str): The id of the professor where the book should be added to.
    """
    conn = self.connect()
    cursor = conn.cursor()
    t_query = (
        f"SELECT bookdata FROM media WHERE app_id={app_id} AND prof_id={prof_id}"
    )
    # print(t_query)
    result = cursor.execute(t_query).fetchall()
    result = [load_pickle(i[0]) for i in result]
    if bookdata in result:
        print("Bookdata already in database")
        # check if the book was deleted in the apparat
        query = (
            "SELECT deleted FROM media WHERE app_id=? AND prof_id=? AND bookdata=?"
        )
        params = (app_id, prof_id, dump_pickle(bookdata))
        result = cursor.execute(query, params).fetchone()
        if result[0] == 1:
            print("Book was deleted, updating bookdata")
            query = "UPDATE media SET deleted=0 WHERE app_id=? AND prof_id=? AND bookdata=?"
            params = (app_id, prof_id, dump_pickle(bookdata))
            cursor.execute(query, params)
            conn.commit()
        return

    query = (
        "INSERT INTO media (bookdata, app_id, prof_id,deleted) VALUES (?, ?, ?,?)"
    )
    converted = dump_pickle(bookdata)
    params = (converted, app_id, prof_id, 0)
    cursor.execute(query, params)
    conn.commit()
    self.close_connection(conn)
def addMessage(self, message: dict, user: str, app_id: Union[str, int])

add a Message to the database

Args

message : dict
the message to be added
user : str
the user who added the message
app_id : Union[str,int]
the id of the apparat
Expand source code
def addMessage(self, message:dict,user:str, app_id:Union[str,int]):
    """add a Message to the database

    Args:
        message (dict): the message to be added
        user (str): the user who added the message
        app_id (Union[str,int]): the id of the apparat
    """
    def __getUserId(user):
        return self.query_db("SELECT id FROM user WHERE username=?", (user,), one=True)[0]
    user_id = __getUserId(user)
    self.query_db("INSERT INTO messages (message, user_id, remind_at,appnr) VALUES (?,?,?,?)", (message["message"],user_id,message["remind_at"],app_id))
def changePassword(self, user, new_password)

change the password of a user. The password will be added with the salt and then committed to the database

Args

user : str
username
new_password : str
the hashed password
Expand source code
def changePassword(self, user, new_password):
    """change the password of a user.
    The password will be added with the salt and then committed to the database

    Args:
        user (str): username
        new_password (str): the hashed password
    """
    salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
    new_password = salt + new_password
    self.query_db("UPDATE user SET password=? WHERE username=?", (new_password,user))
def checkApparatExists(self, apparat_name: str)

check if the apparat is already present in the database based on the name

Args

apparat_name : str
the name of the apparat

Returns

bool
True if the apparat is present, False if not
Expand source code
def checkApparatExists(self, apparat_name:str):
    """check if the apparat is already present in the database based on the name

    Args:
        apparat_name (str): the name of the apparat

    Returns:
        bool: True if the apparat is present, False if not
    """
    return True if self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True) else False
def checkApparatExistsById(self, app_id: Union[str, int]) ‑> bool

a check to see if the apparat is already present in the database, based on the id

Args

app_id : Union[str, int]
the id of the apparat

Returns

bool
True if the apparat is present, False if not
Expand source code
def checkApparatExistsById(self, app_id:Union[str,int])->bool:
    """a check to see if the apparat is already present in the database, based on the id

    Args:
        app_id (Union[str, int]): the id of the apparat

    Returns:
        bool: True if the apparat is present, False if not
    """
    return True if self.query_db("SELECT appnr FROM semesterapparat WHERE appnr=?", (app_id,), one=True) else False
def checkUsername(self, user) ‑> bool

a check to see if the username is already present in the database

Args

user : str
the username

Returns

bool
True if the username is present, False if not
Expand source code
def checkUsername(self, user)->bool:
    """a check to see if the username is already present in the database

    Args:
        user (str): the username

    Returns:
        bool: True if the username is present, False if not
    """
    data = self.query_db("SELECT username FROM user WHERE username=?", (user,), one=True)
    return True if data is not None else False
def close_connection(self, conn: sqlite3.Connection)

closes the connection to the database

Args:

- conn (sql.Connection): the connection to be closed
Expand source code
def close_connection(self, conn: sql.Connection):
    """
    closes the connection to the database
    
    Args:
    ----
        - conn (sql.Connection): the connection to be closed
    """
    conn.close()
def connect(self) ‑> sqlite3.Connection

Connect to the database

Returns

sql.Connection
The active connection to the database
Expand source code
def connect(self)->sql.Connection:
    """
    Connect to the database

    Returns:
        sql.Connection: The active connection to the database
    """
    return sql.connect(self.db_path)
def createApparat(self, apparat: src.logic.dataclass.ApparatData) ‑> int

create the apparat in the database

Args

apparat : ApparatData
the wrapped metadata of the apparat

Raises

AppPresentError
an error describing that the apparats chosen id is already present in the database

Returns

Optional[int]
the id of the apparat
Expand source code
def createApparat(self, apparat:ApparatData)->int:
    """create the apparat in the database

    Args:
        apparat (ApparatData): the wrapped metadata of the apparat

    Raises:
        AppPresentError: an error describing that the apparats chosen id is already present in the database

    Returns:
        Optional[int]: the id of the apparat
    """
            
    prof_id = self.getProfId(apparat.profname)
    app_id = self.getApparatId(apparat.appname)
    if app_id:
        raise AppPresentError(app_id)

    self.createProf(apparat.get_prof_details())
    prof_id = self.getProfId(apparat.profname)
    #ic(prof_id)
    query = f"INSERT OR IGNORE INTO semesterapparat (appnr, name, erstellsemester, dauer, prof_id, fach,deletion_status,konto) VALUES ('{apparat.appnr}', '{apparat.appname}', '{apparat.semester}', '{apparat.dauerapp}', {prof_id}, '{apparat.app_fach}', '{0}', '{SEMAP_MEDIA_ACCOUNTS[apparat.appnr]}')"
    logger.log_info(query)
    self.query_db(query)
    return self.getApparatId(apparat.appname)
def createProf(self, prof_details: dict)

Create a professor in the database

Args

prof_details : dict
a dictionary containing the details of the professor
Expand source code
def createProf(self, prof_details:dict):
    """Create a professor in the database

    Args:
        prof_details (dict): a dictionary containing the details of the professor
    """
    prof_title = prof_details["prof_title"]
    prof_fname = prof_details["profname"].split(",")[1]
    prof_fname = prof_fname.strip()
    prof_lname = prof_details["profname"].split(",")[0]
    prof_lname = prof_lname.strip()
    prof_fullname = prof_details["profname"].replace(",", "")
    prof_mail = prof_details["prof_mail"]
    prof_tel = prof_details["prof_tel"]
    params = (prof_title, prof_fname, prof_lname, prof_mail, prof_tel, prof_fullname)
    query = "INSERT OR IGNORE INTO prof (titel, fname, lname, mail, telnr, fullname) VALUES (?, ?, ?, ?, ?, ?)"
    self.insertInto(query=query, params=params)
def createUser(self, user, password, role, salt)

create an user from the AdminCommands class.

Args

user : str
the username of the user
password : str
a hashed password
role : str
the role of the user
salt : str
a salt for the password
Expand source code
def createUser(self, user, password, role, salt):
    """create an user from the AdminCommands class.

    Args:
        user (str): the username of the user
        password (str): a hashed password
        role (str): the role of the user
        salt (str): a salt for the password
    """
    self.query_db("INSERT OR IGNORE INTO user (username, password, role, salt) VALUES (?,?,?,?)", (user,password,role,salt))
def create_tables(self)

Create the tables in the database

Expand source code
def create_tables(self):
    """
    Create the tables in the database
    """
    conn = self.connect()
    cursor = conn.cursor()
    cursor.execute(CREATE_TABLE_APPARAT)
    cursor.execute(CREATE_TABLE_MESSAGES)
    cursor.execute(CREATE_TABLE_MEDIA)
    cursor.execute(CREATE_TABLE_APPKONTOS)
    cursor.execute(CREATE_TABLE_FILES)
    cursor.execute(CREATE_TABLE_PROF)
    cursor.execute(CREATE_TABLE_USER)
    cursor.execute(CREATE_TABLE_SUBJECTS)
    conn.commit()
    self.close_connection(conn)
def deleteApparat(self, app_id: Union[str, int], semester: str)

Delete an apparat from the database

Args

app_id : Union[str, int]
the id of the apparat
semester : str
the semester the apparat should be deleted from
Expand source code
def deleteApparat(self, app_id:Union[str,int], semester:str):
    """Delete an apparat from the database

    Args:
        app_id (Union[str, int]): the id of the apparat
        semester (str): the semester the apparat should be deleted from
    """
    self.query_db("UPDATE semesterapparat SET deletion_status=1, deleted_date=? WHERE appnr=?", (semester,app_id)) 
def deleteBook(self, book_id)

Delete a book from the database

Args

book_id : str
ID of the book
Expand source code
def deleteBook(self, book_id):
    """
    Delete a book from the database

    Args:
        book_id (str): ID of the book
    """
    self.query_db("UPDATE media SET deleted=1 WHERE id=?", (book_id,))
def deleteMessage(self, message_id)

Delete a message from the database

Args

message_id : str
the id of the message
Expand source code
def deleteMessage(self, message_id):
    """Delete a message from the database

    Args:
        message_id (str): the id of the message
    """
    self.query_db("DELETE FROM messages WHERE id=?", (message_id,))
def deleteUser(self, user)

delete an unser

Args

user : str
username of the user
Expand source code
def deleteUser(self, user):
    """delete an unser

    Args:
        user (str): username of the user
    """
    self.query_db("DELETE FROM user WHERE username=?", (user,))
def getAllAparats(self, deleted=0) ‑> list[tuple]

Get all the apparats in the database

Args

deleted : int, optional
Switch the result to use . Defaults to 0.

Returns

list[tuple]
a list of tuples containing the apparats
Expand source code
def getAllAparats(self,deleted=0)->list[tuple]:
    """Get all the apparats in the database

    Args:
        deleted (int, optional): Switch the result to use . Defaults to 0.

    Returns:
        list[tuple]: a list of tuples containing the apparats
    """
    return self.query_db("SELECT * FROM semesterapparat WHERE deletion_status=?", (deleted,))
def getApparatCountBySemester(self) ‑> tuple[list[str], list[int]]

get a list of all apparats created and deleted by semester

Returns

tuple[list[str],list[int]]
a tuple containing two lists, the first list contains the semesters, the second list contains the amount of apparats created and deleted for the corresponding semester
Expand source code
def getApparatCountBySemester(self)->tuple[list[str],list[int]]:
    """get a list of all apparats created and deleted by semester

    Returns:
        tuple[list[str],list[int]]: a tuple containing two lists, the first list contains the semesters, the second list contains the amount of apparats created and deleted for the corresponding semester
    """
    conn = self.connect()
    cursor = conn.cursor()
    semesters = self.getSemersters()
    created = []
    deleted = []
    for semester in semesters:
        query = f"SELECT COUNT(*) FROM semesterapparat WHERE erstellsemester='{semester}'"
        result = cursor.execute(query).fetchone()
        created.append(result[0])
        query = f"SELECT COUNT(*) FROM semesterapparat WHERE deletion_status=1 AND deleted_date='{semester}'"
        result = cursor.execute(query).fetchone()
        deleted.append(result[0])
    # store data in a tuple
    ret = []
    e_tuple = ()
    for sem in semesters:
        e_tuple = (
            sem,
            created[semesters.index(sem)],
            deleted[semesters.index(sem)],
        )
        ret.append(e_tuple)
    self.close_connection(conn)
    return ret 
def getApparatData(self, appnr, appname) ‑> src.logic.dataclass.ApparatData

Get the Apparat data based on the apparat number and the name

Args

appnr : str
the apparat number
appname : str
the name of the apparat

Raises

NoResultError
an error is raised if no result is found

Returns

ApparatData
the appended data of the apparat wrapped in an ApparatData object
Expand source code
def getApparatData(self, appnr, appname)->ApparatData:
    """Get the Apparat data based on the apparat number and the name

    Args:
        appnr (str): the apparat number
        appname (str): the name of the apparat

    Raises:
        NoResultError: an error is raised if no result is found

    Returns:
        ApparatData: the appended data of the apparat wrapped in an ApparatData object
    """
    result = self.query_db("SELECT * FROM semesterapparat WHERE appnr=? AND name=?", (appnr,appname), one=True)
    if result is None:
        raise NoResultError("No result found")
    apparat = ApparatData()
    apparat.appname = result[1]
    apparat.appnr = result[4]
    apparat.dauerapp = True if result[7] == 1 else False
    prof_data = self.getProfData(self.getProfNameById(result[2]))
    apparat.profname = self.getProfNameById(result[2])
    apparat.prof_mail = prof_data[0]
    apparat.prof_tel = prof_data[1]
    apparat.prof_title = prof_data[2]
    apparat.app_fach = result[3]
    apparat.erstellsemester = result[5]
    apparat.semester = result[8]
    apparat.deleted = result[9]
    apparat.apparat_adis_id = result[11]
    apparat.prof_adis_id = result[12]
    return apparat
def getApparatId(self, apparat_name) ‑> Optional[int]

get the id of an apparat based on the name

Args

apparat_name : str
the name of the apparat e.g. "Semesterapparat 1"

Returns

Optional[int]
the id of the apparat, if the apparat is not found, None is returned
Expand source code
def getApparatId(self, apparat_name)->Optional[int]:
    """get the id of an apparat based on the name

    Args:
        apparat_name (str): the name of the apparat e.g. "Semesterapparat 1"

    Returns:
        Optional[int]: the id of the apparat, if the apparat is not found, None is returned
    """
    data = self.query_db("SELECT appnr FROM semesterapparat WHERE name=?", (apparat_name,), one=True)
    if data is None:
        return None
    else:
        return data[0]
def getApparatName(self, app_id: Union[str, int], prof_id: Union[str, int])

get the name of the apparat based on the id

Args

app_id : Union[str,int]
the id of the apparat
prof_id : Union[str,int]
the id of the professor

Returns

str
the name of the apparat
Expand source code
def getApparatName(self, app_id:Union[str,int], prof_id:Union[str,int]):
    """get the name of the apparat based on the id

    Args:
        app_id (Union[str,int]): the id of the apparat
        prof_id (Union[str,int]): the id of the professor

    Returns:
        str: the name of the apparat
    """
    return self.query_db("SELECT name FROM semesterapparat WHERE appnr=? AND prof_id=?", (app_id,prof_id), one=True)[0]
def getApparatsByProf(self, prof_id: Union[str, int]) ‑> list[tuple]

Get all apparats based on the professor id

Args

prof_id : Union[str,int]
the id of the professor

Returns

list[tuple]
a list of tuples containing the apparats
Expand source code
def getApparatsByProf(self, prof_id:Union[str,int])->list[tuple]:
    """Get all apparats based on the professor id

    Args:
        prof_id (Union[str,int]): the id of the professor

    Returns:
        list[tuple]: a list of tuples containing the apparats
    """
    return self.query_db("SELECT * FROM semesterapparat WHERE prof_id=?", (prof_id,))
def getApparatsBySemester(self, semester: str) ‑> dict[list]

get all apparats based on the semester

Args

semester : str
the selected semester

Returns

dict[list]
a list off all created and deleted apparats for the selected semester
Expand source code
def getApparatsBySemester(self, semester:str)->dict[list]:
    """get all apparats based on the semester

    Args:
        semester (str): the selected semester

    Returns:
        dict[list]: a list off all created and deleted apparats for the selected semester
    """
    data = self.query_db("SELECT name, prof_id FROM semesterapparat WHERE erstellsemester=?", (semester,))
    conn = self.connect()
    cursor = conn.cursor()
    c_tmp = []
    for i in data:
        c_tmp.append((i[0], self.getProfNameById(i[1])))
    query = (
        f"SELECT name,prof_id FROM semesterapparat WHERE deleted_date='{semester}'"
    )
    result = cursor.execute(query).fetchall()
    d_tmp = []
    for i in result:
        d_tmp.append((i[0], self.getProfNameById(i[1])))
    # group the apparats by prof
    c_ret = {}
    for i in c_tmp:
        if i[1] not in c_ret.keys():
            c_ret[i[1]] = [i[0]]
        else:
            c_ret[i[1]].append(i[0])
    d_ret = {}
    for i in d_tmp:
        if i[1] not in d_ret.keys():
            d_ret[i[1]] = [i[0]]
        else:
            d_ret[i[1]].append(i[0])
    self.close_connection(conn)
    return {"created": c_ret, "deleted": d_ret}
def getBlob(self, filename, app_id: Union[str, int])

Get a blob from the database

Args

filename : str
The name of the file
app_id : str
ID of the apparat

Returns

bytes
The file stored in
Expand source code
def getBlob(self, filename, app_id:Union[str,int]):
    """
    Get a blob from the database

    Args:
        filename (str): The name of the file
        app_id (str): ID of the apparat

    Returns:
        bytes: The file stored in 
    """
    return self.query_db("SELECT fileblob FROM files WHERE filename=? AND app_id=?", (filename,app_id), one=True)[0]
def getBook(self, book_id: int) ‑> src.logic.dataclass.BookData

Get the book based on the id in the database

Args

book_id : int
The id of the book

Returns

BookData
The metadata of the book wrapped in a BookData object
Expand source code
def getBook(self,book_id:int)->BookData:
    """
    Get the book based on the id in the database

    Args:
        book_id (int): The id of the book

    Returns:
        BookData: The metadata of the book wrapped in a BookData object
    """
    return load_pickle(self.query_db("SELECT bookdata FROM media WHERE id=?", (book_id,), one=True)[0])
def getBookBasedOnSignature(self, app_id: Union[str, int], prof_id: Union[str, int], signature: str) ‑> src.logic.dataclass.BookData

Get the book based on the signature of the book.

Args

app_id : str
The apparat id the book should be associated with
prof_id : str
The professor id the book should be associated with
signature : str
The signature of the book

Returns

BookData
The total metadata of the book wrapped in a BookData object
Expand source code
def getBookBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->BookData:
    """
    Get the book based on the signature of the book.

    Args:
        app_id (str): The apparat id the book should be associated with
        prof_id (str): The professor id the book should be associated with
        signature (str): The signature of the book

    Returns:
        BookData: The total metadata of the book wrapped in a BookData object
    """
    result = self.query_db("SELECT bookdata FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
    books = [load_pickle(i[0]) for i in result]
    book = [i for i in books if i.signature == signature][0]
    return book
def getBookId(self, bookdata: src.logic.dataclass.BookData, app_id: Union[str, int], prof_id: Union[str, int]) ‑> int

Get the id of a book based on the metadata of the book

Args

bookdata : BookData
The wrapped metadata of the book
app_id : str
The apparat id the book should be associated with
prof_id : str
The professor id the book should be associated with

Returns

int
ID of the book
Expand source code
def getBookId(self, bookdata:BookData, app_id:Union[str,int], prof_id:Union[str,int])->int:
    """
    Get the id of a book based on the metadata of the book

    Args:
        bookdata (BookData): The wrapped metadata of the book
        app_id (str): The apparat id the book should be associated with
        prof_id (str): The professor id the book should be associated with

    Returns:
        int: ID of the book
    """
    result = self.query_db("SELECT id FROM media WHERE bookdata=? AND app_id=? AND prof_id=?", (dump_pickle(bookdata),app_id,prof_id), one=True)
    return result[0]
def getBookIdBasedOnSignature(self, app_id: Union[str, int], prof_id: Union[str, int], signature: str) ‑> int

Get a book id based on the signature of the book.

Args

app_id : str
The apparat id the book should be associated with
prof_id : str
The professor id the book should be associated with
signature : str
The signature of the book

Returns

int
The id of the book
Expand source code
def getBookIdBasedOnSignature(self, app_id:Union[str,int], prof_id:Union[str,int],signature:str)->int:
    """
    Get a book id based on the signature of the book.

    Args:
        app_id (str): The apparat id the book should be associated with
        prof_id (str): The professor id the book should be associated with
        signature (str): The signature of the book

    Returns:
        int: The id of the book
    """
    result = self.query_db("SELECT bookdata, id FROM media WHERE app_id=? AND prof_id=?", (app_id,prof_id))
    books = [(load_pickle(i[0]),i[1]) for i in result]
    book = [i for i in books if i[0].signature == signature][0][1]
    return book
def getBooks(self, app_id: Union[str, int], prof_id: Union[str, int], deleted=0) ‑> list[dict[int, src.logic.dataclass.BookData, int]]

Get the Books based on the apparat id and the professor id

Args

app_id : str
The ID of the apparat
prof_id : str
The ID of the professor
deleted : int, optional
The state of the book. Set to 1 to include deleted ones. Defaults to 0.

Returns

list[dict[int, BookData, int]]
A list of dictionaries containing the id, the metadata of the book and the availability of the book
Expand source code
def getBooks(self, app_id:Union[str,int], prof_id:Union[str,int], deleted=0)->list[dict[int, BookData, int]]:
    """
    Get the Books based on the apparat id and the professor id

    Args:
        app_id (str): The ID of the apparat
        prof_id (str): The ID of the professor
        deleted (int, optional): The state of the book. Set to 1 to include deleted ones. Defaults to 0.

    Returns:
        list[dict[int, BookData, int]]: A list of dictionaries containing the id, the metadata of the book and the availability of the book
    """
    qdata = self.query_db(f"SELECT id,bookdata,available FROM media WHERE (app_id={app_id} AND prof_id={prof_id}) AND (deleted={deleted if deleted == 0 else '1 OR deleted=0'})")
    ret_result = []
    for result_a in qdata:
        data = {"id": int, "bookdata": BookData, "available": int}
        data["id"] = result_a[0]
        data["bookdata"] = load_pickle(result_a[1])
        data["available"] = result_a[2]
        ret_result.append(data)
    return ret_result
def getFacultyMember(self, name: str) ‑> tuple

get a faculty member based on the name

Args

name : str
the name to be searched for

Returns

tuple
a tuple containing the data of the faculty member
Expand source code
def getFacultyMember(self, name:str)->tuple:
    """get a faculty member based on the name

    Args:
        name (str): the name to be searched for

    Returns:
        tuple: a tuple containing the data of the faculty member
    """
    return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof WHERE fullname=?", (name,), one=True)
def getFacultyMembers(self)

get a list of all faculty members

Returns

list[tuple]
a list of tuples containing the faculty members
Expand source code
def getFacultyMembers(self):
    """get a list of all faculty members

    Returns:
        list[tuple]: a list of tuples containing the faculty members
    """
    return self.query_db("SELECT titel, fname,lname,mail,telnr,fullname FROM prof")
def getFiles(self, app_id: Union[str, int], prof_id: int) ‑> list[tuple]

Get all the files associated with the apparat and the professor

Args

app_id : Union[str,int]
The id of the apparat
prof_id : Union[str,int]
the id of the professor

Returns

list[tuple]
a list of tuples containing the filename and the filetype for the corresponding apparat and professor
Expand source code
def getFiles(self, app_id:Union[str,int], prof_id:int)->list[tuple]:
    """Get all the files associated with the apparat and the professor

    Args:
        app_id (Union[str,int]): The id of the apparat
        prof_id (Union[str,int]): the id of the professor

    Returns:
        list[tuple]: a list of tuples containing the filename and the filetype for the corresponding apparat and professor
    """
    return self.query_db("SELECT filename, filetyp FROM files WHERE app_id=? AND prof_id=?", (app_id,prof_id))
def getLastBookId(self) ‑> int

Get the last book id in the database

Returns

int
ID of the last book in the database
Expand source code
def getLastBookId(self)->int:
    """
    Get the last book id in the database

    Returns:
        int: ID of the last book in the database
    """
    return self.query_db("SELECT id FROM media ORDER BY id DESC", one=True)[0]
def getMessages(self, date: str) ‑> list[dict[str, str, str, str]]

Get all the messages for a specific date

Args

date : str
a date.datetime object formatted as a string in the format "YYYY-MM-DD"

Returns

list[dict[str, str, str, str]]
a list of dictionaries containing the message, the user who added the message, the apparat id and the id of the message
Expand source code
def getMessages(self, date:str)->list[dict[str, str, str, str]]:
    """Get all the messages for a specific date

    Args:
        date (str): a date.datetime object formatted as a string in the format "YYYY-MM-DD"
    
    Returns:
        list[dict[str, str, str, str]]: a list of dictionaries containing the message, the user who added the message, the apparat id and the id of the message
    """
    def __get_user_name(user_id):
        return self.query_db("SELECT username FROM user WHERE id=?", (user_id,), one=True)[0]
    messages = self.query_db("SELECT * FROM messages WHERE remind_at=?", (date,))
    ret = [
        {
            "message": i[2],
            "user": __get_user_name(i[4]),
            "appnr": i[5],
            "id": i[0]
        }
        for i in messages
    ]
    return ret
def getProfByName(self, prof_name: str) ‑> tuple

get all the data of a professor based on the name

Args

prof_name : str
the name of the professor

Returns

tuple
the data of the professor
Expand source code
def getProfByName(self, prof_name:str)->tuple:
    """get all the data of a professor based on the name

    Args:
        prof_name (str): the name of the professor

    Returns:
        tuple: the data of the professor
    """
    return self.query_db("SELECT * FROM prof WHERE fullname=?", (prof_name,), one=True)
def getProfData(self, profname: str)

Get mail, telephone number and title of a professor based on the name

Args

profname : str
name of the professor

Returns

tuple
the mail, telephone number and title of the professor
Expand source code
def getProfData(self, profname:str):
    """Get mail, telephone number and title of a professor based on the name

    Args:
        profname (str): name of the professor

    Returns:
        tuple: the mail, telephone number and title of the professor
    """
    data = self.query_db("SELECT mail, telnr, titel FROM prof WHERE fullname=?", (profname.replace(",",""),), one=True)
    return data
def getProfId(self, prof_name: str) ‑> Optional[int]

Get the id of a professor based on the name

Args

prof_name : str
the name of the professor

Returns

Optional[int]
the id of the professor, if the professor is not found, None is returned
Expand source code
def getProfId(self, prof_name:str)->Optional[int]:
    """Get the id of a professor based on the name

    Args:
        prof_name (str): the name of the professor

    Returns:
        Optional[int]: the id of the professor, if the professor is not found, None is returned
    """
    
    data = self.getProfByName(prof_name.replace(",", ""))
    if data is None:
        return None
    else:
        return data[0]
def getProfNameById(self, prof_id: Union[str, int], add_title: bool = False) ‑> str

Get a professor name based on the id

Args

prof_id : Union[str,int]
The id of the professor
add_title : bool, optional
wether to add the title or no. Defaults to False.

Returns

str
The name of the professor
Expand source code
def getProfNameById(self, prof_id:Union[str,int],add_title:bool=False)->str:
    """Get a professor name based on the id

    Args:
        prof_id (Union[str,int]): The id of the professor
        add_title (bool, optional): wether to add the title or no. Defaults to False.

    Returns:
        str: The name of the professor
    """
    prof = self.query_db("SELECT fullname FROM prof WHERE id=?", (prof_id,), one=True)
    if add_title:
        return f"{self.getTitleById(prof_id)}{prof[0]}"
    else:
        return prof[0]
def getProfs(self) ‑> list[tuple]

Return all the professors in the database

Returns

list[tuple]
a list containing all the professors in individual tuples
Expand source code
def getProfs(self)->list[tuple]:
    """Return all the professors in the database

    Returns:
        list[tuple]: a list containing all the professors in individual tuples
    """
    return self.query_db("SELECT * FROM prof")
def getRole(self, user)

get the role of the user

Args

user : str
username

Returns

str
the name of the role
Expand source code
def getRole(self, user):
    """get the role of the user

    Args:
        user (str): username

    Returns:
        str: the name of the role
    """
    return self.query_db("SELECT role FROM user WHERE username=?", (user,), one=True)[0]
def getRoles(self) ‑> list[tuple]

get all the roles in the database

Returns

list[str]
a list of all the roles
Expand source code
def getRoles(self)->list[tuple]:
    """get all the roles in the database

    Returns:
        list[str]: a list of all the roles
    """
    return self.query_db("SELECT role FROM user")
def getSemersters(self) ‑> list[str]

Return all the unique semesters in the database

Returns

list
a list of strings containing the semesters
Expand source code
def getSemersters(self)->list[str]:
    """Return all the unique semesters in the database

    Returns:
        list: a list of strings containing the semesters
    """
    data = self.query_db("SELECT DISTINCT erstellsemester FROM semesterapparat")
    return [i[0] for i in data]
def getSpecificProfData(self, prof_id: Union[str, int], fields: List[str]) ‑> tuple

A customisable function to get specific data of a professor based on the id

Args

prof_id : Union[str,int]
the id of the professor
fields : List[str]
a list of fields to be returned

Returns

tuple
a tuple containing the requested data
Expand source code
def getSpecificProfData(self, prof_id:Union[str,int], fields:List[str])->tuple:
    """A customisable function to get specific data of a professor based on the id

    Args:
        prof_id (Union[str,int]): the id of the professor
        fields (List[str]): a list of fields to be returned

    Returns:
        tuple: a tuple containing the requested data
    """
    query = "SELECT "
    for field in fields:
        query += f"{field},"
    query = query[:-1]
    query += " FROM prof WHERE id=?"
    return self.query_db(query, (prof_id,), one=True)[0]
def getSubjects(self)

Get all the subjects in the database

Returns

list[tuple]
a list of tuples containing the subjects
Expand source code
def getSubjects(self):
    """Get all the subjects in the database

    Returns:
        list[tuple]: a list of tuples containing the subjects
    """
    return self.query_db("SELECT * FROM subjects")
def getTitleById(self, prof_id: Union[str, int]) ‑> str

get the title of a professor based on the id

Args

prof_id : Union[str,int]
the id of the professor

Returns

str
the title of the professor, with an added whitespace at the end, if no title is present, an empty string is returned
Expand source code
def getTitleById(self, prof_id:Union[str,int])->str:
    """get the title of a professor based on the id

    Args:
        prof_id (Union[str,int]): the id of the professor

    Returns:
        str: the title of the professor, with an added whitespace at the end, if no title is present, an empty string is returned
    """
    title = self.query_db("SELECT titel FROM prof WHERE id=?", (prof_id,), one=True)[0]
    return f"{title} " if title is not None else ""
def getUnavailableApparatNumbers(self) ‑> List[int]

Get a list of all the apparat numbers in the database that are currently in use

Returns

List[int]
the list of used apparat numbers
Expand source code
def getUnavailableApparatNumbers(self)->List[int]:
    """Get a list of all the apparat numbers in the database that are currently in use

    Returns:
        List[int]: the list of used apparat numbers
    """
    numbers = self.query_db("SELECT appnr FROM semesterapparat WHERE deletion_status=0")    
    numbers = [i[0] for i in numbers]
    logger.log_info(f"Currently used apparat numbers: {numbers}")
    return numbers
def getUser(self)

Get a single user from the database

Expand source code
def getUser(self):
    """Get a single user from the database"""
    return self.query_db("SELECT * FROM user", one=True)
def getUsers(self) ‑> list[tuple]

Return a list of tuples of all the users in the database

Expand source code
def getUsers(self)->list[tuple]:
    """Return a list of tuples of all the users in the database"""
    return self.query_db("SELECT * FROM user")
def get_db_contents(self) ‑> Optional[List[Tuple]]

Get the contents of the

Returns

Union[List[Tuple], None]
description
Expand source code
def get_db_contents(self)->Union[List[Tuple], None]:
    """
    Get the contents of the 

    Returns:
        Union[List[Tuple], None]: _description_
    """
    try:
        with sql.connect(self.db_path) as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT * FROM sqlite_master WHERE type='table'")
            return cursor.fetchall()
    except sql.OperationalError:
        return None
def insertFile(self, file: list[dict], app_id: Union[str, int], prof_id: Union[str, int])

Instert a list of files into the database

Args

file : list[dict]
a list containing all the files to be inserted
Structured
[{"name": "filename", "path": "path", "type": "filetype"}]
app_id : int
the id of the apparat
prof_id : str
the id of the professor
Expand source code
def insertFile(self, file: list[dict], app_id:Union[str,int], prof_id:Union[str,int]):
    """Instert a list of files into the database

    Args:
        file (list[dict]): a list containing all the files to be inserted
        Structured: [{"name": "filename", "path": "path", "type": "filetype"}]
        app_id (int): the id of the apparat
        prof_id (str): the id of the professor
    """
    for f in file:
        filename = f["name"]
        path = f["path"]
        filetyp = f["type"]
        if path == "Database":
            continue
        blob = create_blob(path)
        query = "INSERT OR IGNORE INTO files (filename, fileblob, app_id, filetyp,prof_id) VALUES (?, ?, ?, ?,?)"
        self.query_db(query, (filename, blob, app_id, filetyp,prof_id))
def insertInto(self, query: str, params: Tuple) ‑> None

Insert sent data into the database

Args

query : str
The query to be executed
params : Tuple
the parameters to be inserted into the database
Expand source code
def insertInto(self, query:str, params:Tuple) -> None:
    """
    Insert sent data into the database

    Args:
        query (str): The query to be executed
        params (Tuple): the parameters to be inserted into the database
    """
    conn = self.connect()
    cursor = conn.cursor()
    logger.log_info(f"Inserting {params} into database with query {query}")
    cursor.execute(query, params)
    conn.commit()
    self.close_connection(conn)
def isEternal(self, id)

check if the apparat is eternal (dauerapparat)

Args

id : int
the id of the apparat to be checked

Returns

int
the state of the apparat
Expand source code
def isEternal(self, id):
    """check if the apparat is eternal (dauerapparat)

    Args:
        id (int): the id of the apparat to be checked

    Returns:
        int: the state of the apparat
    """
    return self.query_db("SELECT dauer FROM semesterapparat WHERE appnr=?", (id,), one=True)
def login(self, user, hashed_password)

try to login the user. The salt for the user will be requested from the database and then added to the hashed password. The password will then be compared to the password in the database

Args

user : str
username that tries to login
hashed_password : str
the password the user tries to login with

Returns

bool
True if the login was successful, False if not
Expand source code
def login(self, user, hashed_password):
    """try to login the user.
    The salt for the user will be requested from the database and then added to the hashed password. The password will then be compared to the password in the database 

    Args:
        user (str): username that tries to login
        hashed_password (str): the password the user tries to login with

    Returns:
        bool: True if the login was successful, False if not
    """
    salt = self.query_db("SELECT salt FROM user WHERE username=?", (user,), one=True)[0]
    if salt is None:
        return False
    hashed_password = salt + hashed_password
    password = self.query_db("SELECT password FROM user WHERE username=?", (user,), one=True)[0]
    if password == hashed_password:
        return True
    else:
        return False
def query_db(self, query: str, args: Tuple = (), one: bool = False) ‑> Union[Tuple, List[Tuple]]

Query the Database for the sent query.

Args

query : str
The query to be executed
args : Tuple, optional
The arguments for the query. Defaults to ().
one : bool, optional
Return the first result only. Defaults to False.

Returns

Union[Typle|List[Tuple]]: Returns the result of the query

Expand source code
def query_db(self, query: str, args: Tuple = (), one: bool = False)->Union[Tuple, List[Tuple]]:
    """
    Query the Database for the sent query.

    Args:
        query (str): The query to be executed
        args (Tuple, optional): The arguments for the query. Defaults to ().
        one (bool, optional): Return the first result only. Defaults to False.

    Returns:
        Union[Typle|List[Tuple]]: Returns the result of the query
    """
    conn = self.connect()
    cursor = conn.cursor()
    logger.log_info(f"Querying database with query {query}, args: {args}")
    cursor.execute(query, args)
    rv = cursor.fetchall()
    conn.commit()
    self.close_connection(conn)
    return (rv[0] if rv else None) if one else rv
def recreateFile(self, filename: str, app_id: Union[str, int], filetype: str) ‑> str

Recreate a file from the database

Args

filename : str
the name of the file
app_id : Union[str,int]
the id of the apparat
filetype : str
the extension of the file to be created

Returns

str
The filename of the recreated file
Expand source code
def recreateFile(self, filename:str, app_id:Union[str,int],filetype:str)->str:
    """Recreate a file from the database

    Args:
        filename (str): the name of the file
        app_id (Union[str,int]): the id of the apparat
        filetype (str): the extension of the file to be created

    Returns:
        str: The filename of the recreated file 
    """
    blob = self.getBlob(filename, app_id)
    tempdir = config.database.tempdir
    tempdir = tempdir.replace("~", str(Path.home()))
    tempdir_path = Path(tempdir)        
    if not os.path.exists(tempdir_path):
        os.mkdir(tempdir_path)
    file = tempfile.NamedTemporaryFile(
        delete=False, dir=tempdir_path, mode="wb", suffix=f".{filetype}"
    )
    file.write(blob)
    print("file created")
    return file.name
def searchBook(self, data: dict[str, str]) ‑> list[tuple[src.logic.dataclass.BookData, int]]

Search a book in the database based on the sent data.

Args

data : dict[str, str]
A dictionary containing the data to be searched for. The dictionary can contain the following:
  • signature: The signature of the book
  • title: The title of the book

Returns

list[tuple[BookData, int]]
A list of tuples containing the wrapped Metadata and the id of the book
Expand source code
def searchBook(self, data:dict[str, str])->list[tuple[BookData, int]]:
    """
    Search a book in the database based on the sent data.

    Args:
        data (dict[str, str]): A dictionary containing the data to be searched for. The dictionary can contain the following: 
        - signature: The signature of the book
        - title: The title of the book

    Returns:
        list[tuple[BookData, int]]: A list of tuples containing the wrapped Metadata and the id of the book
    """
    rdata = self.query_db("SELECT * FROM media WHERE deleted=0")
    #ic(rdata, len(rdata))
    mode = 0
    if len(data)== 1:
        if "signature" in data.keys():
            mode = 1
        elif "title" in data.keys():
            mode = 2
    elif len(data) == 2:
        mode = 3
    else:
        return None
    ret = []
    for book in rdata:
        bookdata = load_pickle(book[1])
        app_id = book[2]
        prof_id = book[3]
        if mode == 1:
            if data["signature"] in bookdata.signature:
                ret.append((bookdata,app_id,prof_id))
        elif mode == 2:
            if data["title"] in bookdata.title:
                ret.append((bookdata,app_id,prof_id))
        elif mode == 3:
            if data["signature"] in bookdata.signature and data["title"] in bookdata.title:
                ret.append((bookdata,app_id,prof_id))
    #ic(ret)
    return ret
def setAvailability(self, book_id: str, available: str)

Set the availability of a book in the database

Args

book_id : str
The id of the book
available : str
The availability of the book
Expand source code
def setAvailability(self, book_id:str, available:str):
    """
    Set the availability of a book in the database

    Args:
        book_id (str): The id of the book
        available (str): The availability of the book
    """
    self.query_db("UPDATE media SET available=? WHERE id=?", (available,book_id))
def setNewSemesterDate(self, app_id: Union[str, int], newDate, dauerapp=False)

Set the new semester date for an apparat

Args

app_id : Union[str,int]
the id of the apparat
newDate : str
the new date
dauerapp : bool, optional
if the apparat was changed to dauerapparat. Defaults to False.
Expand source code
def setNewSemesterDate(self, app_id:Union[str,int], newDate, dauerapp=False):
    """Set the new semester date for an apparat

    Args:
        app_id (Union[str,int]): the id of the apparat
        newDate (str): the new date
        dauerapp (bool, optional): if the apparat was changed to dauerapparat. Defaults to False.
    """
    date = datetime.datetime.strptime(newDate, "%d.%m.%Y").strftime("%Y-%m-%d")
    if dauerapp:
        self.query_db("UPDATE semesterapparat SET verlängerung_bis=?, dauerapp=? WHERE appnr=?", (date,dauerapp,app_id))
    else:
        self.query_db("UPDATE semesterapparat SET endsemester=? WHERE appnr=?", (date,app_id))
def statistic_request(self, **kwargs: Any)

Take n amount of kwargs and return the result of the query

Expand source code
def statistic_request(self, **kwargs: Any):
    """Take n amount of kwargs and return the result of the query
    """
    def __query(query):
        """execute the query and return the result

        Args:
            query (str): the constructed query

        Returns:
            list: the result of the query
        """
        conn = self.connect()
        cursor = conn.cursor()
        result = cursor.execute(query).fetchall()
        for result_a in result:
            orig_value = result_a
            prof_name = self.getProfNameById(result_a[2])
            # replace the prof_id with the prof_name
            result_a = list(result_a)
            result_a[2] = prof_name
            result_a = tuple(result_a)
            result[result.index(orig_value)] = result_a
        self.close_connection(conn)
        return result
    if "deletable" in kwargs.keys():
        query = f"SELECT * FROM semesterapparat WHERE deletion_status=0 AND dauer=0 AND (erstellsemester!='{kwargs['deletesemester']}' OR verlängerung_bis!='{kwargs['deletesemester']}')"
        return __query(query)
    if "dauer" in kwargs.keys():
        kwargs["dauer"] = kwargs["dauer"].replace("Ja", "1").replace("Nein", "0")
    query = "SELECT * FROM semesterapparat WHERE "
    for key, value in kwargs.items() if kwargs.items() is not None else {}:
        print(key, value)
        query += f"{key}='{value}' AND "
        print(query)
    # remove deletesemester part from normal query, as this will be added to the database upon deleting the apparat
    if "deletesemester" in kwargs.keys():
        query = query.replace(
            f"deletesemester='{kwargs['deletesemester']}' AND ", ""
        )
    if "endsemester" in kwargs.keys():
        if "erstellsemester" in kwargs.keys():
            query = query.replace(f"endsemester='{kwargs['endsemester']}' AND ", "")
            query = query.replace(
                f"erstellsemester='{kwargs['erstellsemester']} AND ", "xyz"
            )
        else:
            query = query.replace(
                f"endsemester='{kwargs['endsemester']}' AND ", "xyz"
            )
            print("replaced")
        query = query.replace(
            "xyz",
            f"(erstellsemester='{kwargs['endsemester']}' OR verlängerung_bis='{kwargs['endsemester']}') AND ",
        )
    # remove all x="" parts from the query where x is a key in kwargs
    query = query[:-5]
    print(query)
    return __query(query)
def updateApparat(self, apparat_data: src.logic.dataclass.ApparatData)

Update an apparat in the database

Args

apparat_data : ApparatData
the new metadata of the apparat
Expand source code
def updateApparat(self, apparat_data:ApparatData):
    """Update an apparat in the database

    Args:
        apparat_data (ApparatData): the new metadata of the apparat
    """
    query = f"UPDATE semesterapparat SET name = ?, fach = ?, dauer = ?, prof_id = ? WHERE appnr = ?"
    params = (
        apparat_data.appname,
        apparat_data.app_fach,
        apparat_data.dauerapp,
        self.getProfId(apparat_data.profname),
        apparat_data.appnr,        
    )
    self.query_db(query, params)
def updateBookdata(self, book_id, bookdata: src.logic.dataclass.BookData)

Update the bookdata in the database

Args

book_id : str
The id of the book
bookdata : BookData
The new metadata of the book
Expand source code
def updateBookdata(self, book_id, bookdata:BookData):
    """
    Update the bookdata in the database

    Args:
        book_id (str): The id of the book
        bookdata (BookData): The new metadata of the book
    """
    self.query_db("UPDATE media SET bookdata=? WHERE id=?", (dump_pickle(bookdata),book_id))
def updateFacultyMember(self, data: dict, oldlname: str, oldfname: str)

update the data of a faculty member

Args

data : dict
a dictionary containing the data to be updated
oldlname : str
the old last name of the faculty member
oldfname : str
the old first name of the faculty member
Expand source code
def updateFacultyMember(self, data:dict, oldlname:str, oldfname:str):
    """update the data of a faculty member

    Args:
        data (dict): a dictionary containing the data to be updated
        oldlname (str): the old last name of the faculty member
        oldfname (str): the old first name of the faculty member
    """
    placeholders = ", ".join([f"{i}=:{i} " for i in data.keys()])
    query = f"UPDATE prof SET {placeholders} WHERE lname = :oldlname AND fname = :oldfname"
    data["oldlname"] = oldlname
    data["oldfname"] = oldfname
    self.query_db(query, data)
def updateUser(self, username, data: dict[str, str])

changge the data of a user

Args

username : str
the username of the user
data : dict[str, str]
the data to be changed
Expand source code
def updateUser(self, username, data:dict[str, str]):
    """changge the data of a user

    Args:
        username (str): the username of the user
        data (dict[str, str]): the data to be changed
    """
    conn = self.connect()
    cursor = conn.cursor()
    query = "UPDATE user SET "
    for key,value in data.items():
        if key == "username":
            continue
        query += f"{key}='{value}',"
    query = query[:-1]
    query += " WHERE username=?"
    params = (username,)
    cursor.execute(query, params)
    conn.commit()
    self.close_connection(conn)