Repository: TheUncleKai/bbutils

Issue:
Add classes for files and folders
Add some simple classes for files and folders.

Discussion:

Patch:
diff --git a/bbutil/__init__.py b/bbutil/__init__.py
index 7c516c2..9255076 100644
--- a/bbutil/__init__.py
+++ b/bbutil/__init__.py
@@ -16,13 +16,21 @@
 #    Copyright (C) 2017, Kai Raphahn <kai.raphahn@laburec.de>
 #
 
+from typing import Optional
+from bbutil.logging import Logging
+
 __all__ = [
     "database",
     "logging",
     "lang",
 
     "data",
-    "utils"
+    "file",
+    "utils",
+
+    "log",
+
+    "set_log"
 ]
 
 
@@ -67,3 +75,12 @@ __version__ = "{0:d}.{1:d}.{2:d}.{3:d}".format(__milestone__, __major__, __minor
 
 #: package maintainer
 __maintainer__ = __author__
+
+
+log: Optional[Logging] = None
+
+
+def set_log(module: Logging):
+    global log
+    log = module
+    return
diff --git a/bbutil/database/database.py b/bbutil/database/database.py
index 519bda4..4f3f868 100644
--- a/bbutil/database/database.py
+++ b/bbutil/database/database.py
@@ -21,10 +21,9 @@ from abc import ABCMeta
 from dataclasses import dataclass, field
 from typing import Optional, List
 
-from bbutil.logging import Logging
+import bbutil
 
 from bbutil.database.sqlite import SQLite
-from bbutil.database.types import Data
 from bbutil.database.table import Table
 
 __all__ = [
@@ -35,8 +34,6 @@ __all__ = [
 @dataclass
 class Database(metaclass=ABCMeta):
 
-    log: Optional[Logging] = None
-
     name: str = ""
     sqlite: Optional[SQLite] = None
     tables: List[Table] = field(default_factory=list)
@@ -70,14 +67,14 @@ class Database(metaclass=ABCMeta):
         self.init()
 
         if (self.name == "") or (self.filename == ""):
-            self.log.error("File- or database-name is missing!")
+            bbutil.log.error("File- or database-name is missing!")
             return False
 
-        self.sqlite = SQLite(name=self.name, filename=self.filename, log=self.log)
+        self.sqlite = SQLite(name=self.name, filename=self.filename)
 
         _check = self.prepare()
         if _check is False:
-            self.log.error("Preparation of tables has failed!")
+            bbutil.log.error("Preparation of tables has failed!")
             return False
 
         _check = self._open()
@@ -99,10 +96,10 @@ class Database(metaclass=ABCMeta):
 
     def stop(self) -> bool:
         if self.sqlite is None:
-            self.log.error("No SQLite connection established!")
+            bbutil.log.error("No SQLite connection established!")
             return False
 
-        self.log.inform(self.name, "Close connection!")
+        bbutil.log.inform(self.name, "Close connection!")
         _check = self.sqlite.disconnect()
         if _check is False:
             return False
diff --git a/bbutil/database/sqlite.py b/bbutil/database/sqlite.py
index ab61d91..24fd397 100644
--- a/bbutil/database/sqlite.py
+++ b/bbutil/database/sqlite.py
@@ -21,7 +21,8 @@ from dataclasses import dataclass, field
 from multiprocessing import Lock
 from typing import Optional, List, Union
 
-from bbutil.logging import Logging
+import bbutil
+
 from bbutil.database.types import Data
 
 __all__ = [
@@ -44,7 +45,6 @@ class Select(object):
 
 @dataclass
 class SQLite(object):
-    log: Optional[Logging] = None
 
     name: str = ""
     connection: Optional[sqlite3.Connection] = None
@@ -55,28 +55,21 @@ class SQLite(object):
     filename: str = ""
     lock: Optional[Lock] = None
 
-    def _check_log(self):
-        if self.log is None:
-            raise ValueError("Logging class is missing!")
-        return
-
     def _lock(self):
         if self.lock is None:
             self.lock = Lock()
 
-        self.log.debug1(self.name, "Acquire Lock")
+        bbutil.log.debug1(self.name, "Acquire Lock")
         self.lock.acquire()
         return
 
     def connect(self) -> bool:
-        self._check_log()
-
         if self.name == "":
-            self.log.error("Connection is unnamed!")
+            bbutil.log.error("Connection is unnamed!")
             return False
 
         if (self.filename == "") and (self.use_memory is False):
-            self.log.error("No filename given!")
+            bbutil.log.error("No filename given!")
             return False
 
         self._lock()
@@ -89,29 +82,27 @@ class SQLite(object):
                 self.connection = sqlite3.connect(':memory:',
                                                   detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
             except sqlite3.OperationalError as e:
-                self.log.error("Unable to create database in memory!")
-                self.log.exception(e)
+                bbutil.log.error("Unable to create database in memory!")
+                bbutil.log.exception(e)
                 return False
 
             self.filename = "memory"
 
             return True
 
-        self.log.inform(self.name, "Connect to {0:s}".format(self.filename))
+        bbutil.log.inform(self.name, "Connect to {0:s}".format(self.filename))
 
         try:
             self.connection = sqlite3.connect(self.filename,
                                               detect_types=sqlite3.PARSE_DECLTYPES | sqlite3.PARSE_COLNAMES)
         except sqlite3.OperationalError as e:
-            self.log.error("Unable to create database: {0:s}".format(self.filename))
-            self.log.exception(e)
+            bbutil.log.error("Unable to create database: {0:s}".format(self.filename))
+            bbutil.log.exception(e)
             return False
 
         return True
 
     def disconnect(self) -> bool:
-        self._check_log()
-
         if self.connection is None:
             return True
 
@@ -119,19 +110,19 @@ class SQLite(object):
             try:
                 self.connection.commit()
             except sqlite3.OperationalError as e:
-                self.log.error("Unable to commit to database!")
-                self.log.exception(e)
+                bbutil.log.error("Unable to commit to database!")
+                bbutil.log.exception(e)
                 return False
 
             self.commit = False
 
-        self.log.debug1(self.name, "Close {0:s}".format(self.filename))
+        bbutil.log.debug1(self.name, "Close {0:s}".format(self.filename))
 
         try:
             self.connection.close()
         except sqlite3.OperationalError as e:
-            self.log.error("Unable to close connection!")
-            self.log.exception(e)
+            bbutil.log.error("Unable to close connection!")
+            bbutil.log.exception(e)
             return False
 
         self.connection = None
@@ -145,22 +136,20 @@ class SQLite(object):
         return True
 
     def check_table(self, table_name: str) -> bool:
-        self._check_log()
-
         if self.connection is None:
-            self.log.error("No valid connection!")
+            bbutil.log.error("No valid connection!")
             return False
 
         c = self.connection.cursor()
         command = "SELECT name FROM sqlite_master WHERE type='table' AND name='{0:s}';".format(table_name)
 
-        self.log.debug1(self.name, "Check for table: {0:s}".format(table_name))
+        bbutil.log.debug1(self.name, "Check for table: {0:s}".format(table_name))
 
         try:
             c.execute(command)
         except sqlite3.OperationalError as e:
-            self.log.error("Unable to check for table: {0:s}".format(table_name))
-            self.log.exception(e)
+            bbutil.log.error("Unable to check for table: {0:s}".format(table_name))
+            bbutil.log.exception(e)
             return False
 
         result = c.fetchone()
@@ -169,10 +158,8 @@ class SQLite(object):
         return True
 
     def count_table(self, table_name: str) -> int:
-        self._check_log()
-
         if self.connection is None:
-            self.log.error("No valid connection!")
+            bbutil.log.error("No valid connection!")
             return -1
 
         c = self.connection.cursor()
@@ -181,21 +168,19 @@ class SQLite(object):
         try:
             c.execute(command)
         except sqlite3.OperationalError as e:
-            self.log.error("Unable to count rows: {0:s}".format(table_name))
-            self.log.exception(e)
+            bbutil.log.error("Unable to count rows: {0:s}".format(table_name))
+            bbutil.log.exception(e)
             return -1
 
         result = c.fetchall()
         (count,) = result[0]
 
-        self.log.debug1(self.name, "Count table: {0:s}, {1:d}".format(table_name, count))
+        bbutil.log.debug1(self.name, "Count table: {0:s}, {1:d}".format(table_name, count))
         return count
 
     def prepare_table(self, table_name: str, column_list: list, unique_list: list) -> bool:
-        self._check_log()
-
         if self.connection is None:
-            self.log.error("No valid connection!")
+            bbutil.log.error("No valid connection!")
             return False
 
         _check = self.check_table(table_name)
@@ -225,17 +210,18 @@ class SQLite(object):
         try:
             c.execute(command)
         except sqlite3.OperationalError as e:
-            self.log.error("Unable to create table: {0:s}".format(table_name))
-            self.log.exception(e)
+            bbutil.log.error("Unable to create table: {0:s}".format(table_name))
+            bbutil.log.exception(e)
             print(command)
             return False
 
-        self.log.debug1(self.name, "Create table: {0:s}".format(table_name))
+        bbutil.log.debug1(self.name, "Create table: {0:s}".format(table_name))
 
         self.connection.commit()
         return True
 
-    def _single_execute(self, table_name: str, names: list, data: Data) -> Optional[_Execute]:
+    @staticmethod
+    def _single_execute(table_name: str, names: list, data: Data) -> Optional[_Execute]:
         _data = []
         _names = ", ".join(names)
         _placeholder = ", ".join(['?'] * len(names))
@@ -246,14 +232,15 @@ class SQLite(object):
             try:
                 _value = getattr(data, _line)
             except AttributeError as e:
-                self.log.exception(e)
-                self.log.error("Data format does not fit database table!")
+                bbutil.log.exception(e)
+                bbutil.log.error("Data format does not fit database table!")
                 return None
             _data.append(_value)
         _execute = _Execute(sql=sql, data=_data)
         return _execute
 
-    def _many_execute(self, table_name: str, names: list, data_list: List[Data]) -> Optional[_Execute]:
+    @staticmethod
+    def _many_execute(table_name: str, names: list, data_list: List[Data]) -> Optional[_Execute]:
         _data = []
         _length = len(data_list)
         _names = ", ".join(names)
@@ -267,8 +254,8 @@ class SQLite(object):
                 try:
                     _ret = getattr(_item, _line)
                 except AttributeError as e:
-                    self.log.exception(e)
-                    self.log.error("Data format does not fit database table!")
+                    bbutil.log.exception(e)
+                    bbutil.log.error("Data format does not fit database table!")
                     return None
                 _value.append(_ret)
             _data.append(_value)
@@ -277,10 +264,8 @@ class SQLite(object):
         return _execute
 
     def insert(self, table_name: str, names: list, data: Union[Data, List[Data]]) -> int:
-        self._check_log()
-
         if self.connection is None:
-            self.log.error("No valid connection!")
+            bbutil.log.error("No valid connection!")
             return -1
 
         c = self.connection.cursor()
@@ -306,23 +291,23 @@ class SQLite(object):
         try:
             command(_execute.sql, _execute.data)
         except sqlite3.InterfaceError as e:
-            self.log.exception(e)
-            self.log.error("One or more values is an invalid format!")
-            self.log.error("SQL:  " + str(_execute.sql))
-            self.log.error("DATA: " + str(_execute.data))
+            bbutil.log.exception(e)
+            bbutil.log.error("One or more values is an invalid format!")
+            bbutil.log.error("SQL:  " + str(_execute.sql))
+            bbutil.log.error("DATA: " + str(_execute.data))
             return -1
         except OverflowError as e:
-            self.log.exception(e)
-            self.log.error("One or more values is too large!")
-            self.log.error("SQL:  " + str(_execute.sql))
-            self.log.error("DATA: " + str(_execute.data))
+            bbutil.log.exception(e)
+            bbutil.log.error("One or more values is too large!")
+            bbutil.log.error("SQL:  " + str(_execute.sql))
+            bbutil.log.error("DATA: " + str(_execute.data))
             return -1
         except sqlite3.IntegrityError:
             return -1
         except Exception as e:
-            self.log.exception(e)
-            self.log.error("SQL:  " + str(_execute.sql))
-            self.log.error("DATA: " + str(_execute.data))
+            bbutil.log.exception(e)
+            bbutil.log.error("SQL:  " + str(_execute.sql))
+            bbutil.log.error("DATA: " + str(_execute.data))
             return -1
 
         _counter = c.rowcount
@@ -332,10 +317,8 @@ class SQLite(object):
         return _counter
 
     def update(self, table_name: str, names: list, data: Data, sql_filter: str, filter_value=None) -> bool:
-        self._check_log()
-
         if self.connection is None:
-            self.log.error("No valid connection!")
+            bbutil.log.error("No valid connection!")
             return False
 
         c = self.connection.cursor()
@@ -362,14 +345,14 @@ class SQLite(object):
         except sqlite3.IntegrityError:
             return False
         except sqlite3.OperationalError as e:
-            self.log.error("SQL:  " + str(sql))
-            self.log.error("DATA: " + str(_data))
-            self.log.exception(e)
+            bbutil.log.error("SQL:  " + str(sql))
+            bbutil.log.error("DATA: " + str(_data))
+            bbutil.log.exception(e)
             return False
         except OverflowError as e:
-            self.log.exception(e)
-            self.log.error("SQL:  " + str(sql))
-            self.log.error("DATA: " + str(_data))
+            bbutil.log.exception(e)
+            bbutil.log.error("SQL:  " + str(sql))
+            bbutil.log.error("DATA: " + str(_data))
             return False
 
         self.commit = True
@@ -384,10 +367,8 @@ class SQLite(object):
         return
 
     def select(self, table_name: str, names: list, sql_filter: str, data: list) -> Optional[list]:
-        self._check_log()
-
         if self.connection is None:
-            self.log.error("No valid connection!")
+            bbutil.log.error("No valid connection!")
             return None
 
         c = self.connection.cursor()
@@ -401,21 +382,21 @@ class SQLite(object):
         if sql_filter != "":
             command = "SELECT {0:s} FROM {1:s} WHERE {2:s};".format(_selector, table_name, sql_filter)
 
-        self.log.debug1(table_name, command)
+        bbutil.log.debug1(table_name, command)
 
         try:
             self._select_execute(c, command, data)
         except sqlite3.OperationalError as e:
-            self.log.error("Unable to search table: {0:s}".format(table_name))
-            self.log.exception(e)
-            self.log.error("SQL:  " + str(command))
-            self.log.error("DATA: " + str(data))
+            bbutil.log.error("Unable to search table: {0:s}".format(table_name))
+            bbutil.log.exception(e)
+            bbutil.log.error("SQL:  " + str(command))
+            bbutil.log.error("DATA: " + str(data))
             return None
         except OverflowError as e:
-            self.log.error("Unable to search table due to overflow: {0:s}".format(table_name))
-            self.log.exception(e)
-            self.log.error("SQL:  " + str(command))
-            self.log.error("DATA: " + str(data))
+            bbutil.log.error("Unable to search table due to overflow: {0:s}".format(table_name))
+            bbutil.log.exception(e)
+            bbutil.log.error("SQL:  " + str(command))
+            bbutil.log.error("DATA: " + str(data))
             return None
 
         _fetchlist = []
diff --git a/bbutil/database/table.py b/bbutil/database/table.py
index 8949841..e6a1ff6 100644
--- a/bbutil/database/table.py
+++ b/bbutil/database/table.py
@@ -19,7 +19,7 @@
 from dataclasses import dataclass, field
 from typing import Optional, List, Dict, Any, Tuple
 
-from bbutil.logging import Logging
+import bbutil
 
 from bbutil.database import Column, Types, Data, DataType, select_interval
 from bbutil.database.sqlite import SQLite
@@ -33,8 +33,6 @@ __all__ = [
 @dataclass
 class Table(object):
 
-    log: Optional[Logging] = None
-
     name: str = ""
     _counter: int = 0
     keyword: str = ""
@@ -109,19 +107,19 @@ class Table(object):
     def _process_datalist(self, data_list: List[Tuple], verbose: bool = True) -> Optional[List[Data]]:
         if data_list is None:
             if (self.suppress_warnings is False) and (verbose is True):
-                self.log.warn(self.name, "No data!")
+                bbutil.log.warn(self.name, "No data!")
             return None
 
         _count = len(data_list)
         if _count == 0:
             if (self.suppress_warnings is False) and (verbose is True):
-                self.log.warn(self.name, "No data!")
+                bbutil.log.warn(self.name, "No data!")
             return None
 
         progress = None
         if verbose is True:
-            self.log.inform("Table", "Load {0:d} from {1:s}".format(_count, self.name))
-            progress = self.log.progress(_count, select_interval(_count))
+            bbutil.log.inform("Table", "Load {0:d} from {1:s}".format(_count, self.name))
+            progress = bbutil.log.progress(_count, select_interval(_count))
 
         _result = []
         _count = 0
@@ -135,9 +133,9 @@ class Table(object):
                 try:
                     _value = _data[_number]
                 except IndexError as e:
-                    self.log.error("Problem with data item {0:d}!".format(_count))
-                    self.log.error("Column {0:d} ({1:s}) not found!".format(_number, _col.name))
-                    self.log.exception(e)
+                    bbutil.log.error("Problem with data item {0:d}!".format(_count))
+                    bbutil.log.error("Column {0:d} ({1:s}) not found!".format(_number, _col.name))
+                    bbutil.log.exception(e)
                     return None
 
                 key_list.append(_col.name)
@@ -160,7 +158,7 @@ class Table(object):
                 progress.inc()
 
         if verbose is True:
-            self.log.clear()
+            bbutil.log.clear()
         return _result
 
     def select(self, sql_filter: str = "", names=None, data_values=None, verbose: bool = True) -> List[Data]:
@@ -182,7 +180,7 @@ class Table(object):
         _chunk_size = self._get_chunk_size(len(self.data))
         _split_list = self._split_list(self.data, _chunk_size)
         _max = len(_split_list) + 1
-        _progress = self.log.progress(_max)
+        _progress = bbutil.log.progress(_max)
 
         _counter = 0
         _stored = 0
@@ -192,11 +190,11 @@ class Table(object):
             _stored += self.sqlite.insert(self.name, self.names, _item_list)
             _progress.inc()
 
-        self.log.clear()
+        bbutil.log.clear()
         if _counter != _stored:
-            self.log.warn(self.name, "Entries {0:d}, Stored {1:d}".format(_counter, _stored))
+            bbutil.log.warn(self.name, "Entries {0:d}, Stored {1:d}".format(_counter, _stored))
         else:
-            self.log.inform(self.name, "Stored {0:d}".format(_counter))
+            bbutil.log.inform(self.name, "Stored {0:d}".format(_counter))
 
         return _stored
 
@@ -245,9 +243,11 @@ class Table(object):
         return _check
 
     def init(self) -> bool:
+        if bbutil.log is None:
+            return False
 
         if len(self.columns) == 0:
-            self.log.error("No columns: {0:s}".format(self.name))
+            bbutil.log.error("No columns: {0:s}".format(self.name))
             return False
 
         _columns = []
@@ -287,16 +287,16 @@ class Table(object):
         return
 
     def load(self) -> int:
-        self.log.inform(self.name, "Load {0:s}...".format(self.name))
+        bbutil.log.inform(self.name, "Load {0:s}...".format(self.name))
 
         _items = self.select()
         _count = len(_items)
 
         _max = _count + 1
-        _progress = self.log.progress(_max, select_interval(_max))
+        _progress = bbutil.log.progress(_max, select_interval(_max))
 
         for _item in _items:
             self.add(_item)
             _progress.inc()
-        self.log.clear()
+        bbutil.log.clear()
         return _count
diff --git a/bbutil/file.py b/bbutil/file.py
new file mode 100644
index 0000000..6f3c818
--- /dev/null
+++ b/bbutil/file.py
@@ -0,0 +1,171 @@
+#!/usr/bin/python3
+# coding=utf-8
+
+# Copyright (C) 2020, Siemens Healthcare Diagnostics Products GmbH
+# Licensed under the Siemens Inner Source License 1.2, see LICENSE.md.
+
+import abc
+import os.path
+
+from dataclasses import dataclass
+from abc import ABCMeta
+
+from bbutil.utils import full_path
+
+import bbutil
+
+__all__ = [
+    "File",
+    "Folder",
+    "Base"
+]
+
+
+@dataclass
+class Base(metaclass=ABCMeta):
+
+    path: str = ""
+    basename: str = ""
+
+    @property
+    def exists(self) -> bool:
+        _check = os.path.exists(self.fullpath)
+        return _check
+
+    @property
+    def fullpath(self) -> str:
+        _filename = full_path("{0:s}/{1:s}".format(self.path, self.basename))
+        return _filename
+
+    @property
+    def valid(self) -> bool:
+        if self.path == "":
+            return False
+
+        if self.basename == "":
+            return False
+        return True
+
+    @abc.abstractmethod
+    def init(self) -> bool:
+        pass
+
+    def clear(self):
+        self.basename = ""
+        self.path = ""
+
+        self.clear_object()
+        return
+
+    @property
+    def check(self) -> bool:
+        if self.valid is False:
+            bbutil.log.error("Given filename is not valid!")
+            return False
+
+        if self.exists is False:
+            bbutil.log.error("Given filename does not exists!")
+            return False
+        return True
+
+    def open(self, filename: str) -> bool:
+        self.basename = os.path.basename(filename)
+        self.path = os.path.dirname(filename)
+
+        if self.check is False:
+            return False
+
+        _check = self.open_file()
+        return _check
+
+    @abc.abstractmethod
+    def clear_object(self):
+        pass
+
+    @abc.abstractmethod
+    def open_file(self) -> bool:
+        pass
+
+    @abc.abstractmethod
+    def create(self) -> bool:
+        pass
+
+    def _remove_file(self) -> bool:
+        try:
+            os.remove(self.fullpath)
+        except OSError as e:
+            bbutil.log.exception(e)
+            return False
+
+        if self.exists is True:
+            bbutil.log.error("Unable to remove file!")
+            return False
+
+        return True
+
+    def _remove_folder(self) -> bool:
+        try:
+            os.rmdir(self.fullpath)
+        except OSError as e:
+            bbutil.log.exception(e)
+            return False
+
+        if self.exists is True:
+            bbutil.log.error("Unable to remove file!")
+            return False
+
+        return True
+
+    def remove(self) -> bool:
+        if self.valid is False:
+            return True
+
+        if self.exists is False:
+            return True
+
+        if os.path.isdir(self.fullpath) is True:
+            _check = self._remove_folder()
+        else:
+            _check = self._remove_file()
+
+        return _check
+
+
+class File(Base):
+
+    def init(self) -> bool:
+        return True
+
+    def clear_object(self):
+        return
+
+    def open_file(self) -> bool:
+        return True
+
+    def create(self) -> bool:
+        return True
+
+
+class Folder(Base):
+
+    def init(self) -> bool:
+        return True
+
+    def clear_object(self):
+        return
+
+    def open_file(self) -> bool:
+        return True
+
+    def create(self) -> bool:
+        if self.exists is False:
+            try:
+                os.mkdir(self.fullpath)
+            except OSError as e:
+                bbutil.log.exception(e)
+                return False
+
+        if self.exists is False:
+            bbutil.log.error("Unable to create: {0:s}".format(self.fullpath))
+            return False
+        return True


Test patch:
diff --git a/tests.json b/tests.json
index 0f5812f..d2214b2 100644
--- a/tests.json
+++ b/tests.json
@@ -248,7 +248,6 @@
                 "test_connect_04",
                 "test_connect_05",
                 "test_connect_06",
-                "test_connect_07",
                 "test_disconnect_01",
                 "test_disconnect_02",
                 "test_disconnect_03",
@@ -298,6 +297,7 @@
                 "test_init_01",
                 "test_init_02",
                 "test_init_03",
+                "test_init_04",
                 "test_select_01",
                 "test_select_02",
                 "test_select_03",
@@ -328,6 +328,33 @@
                 "test_stop_02",
                 "test_store_01"
             ]
+        },
+        {
+            "id": "File.File",
+            "path": "tests.file",
+            "classname": "TestFile",
+            "tests": [
+                "test_file_01",
+                "test_file_02",
+                "test_file_03",
+                "test_file_04",
+                "test_file_05",
+                "test_file_06",
+                "test_file_07",
+                "test_file_08",
+                "test_file_09",
+                "test_file_10",
+                "test_file_11",
+                "test_file_12",
+                "test_file_13",
+                "test_folder_01",
+                "test_folder_02",
+                "test_folder_03",
+                "test_folder_04",
+                "test_folder_05",
+                "test_folder_06",
+                "test_folder_07"
+            ]
         }
     ]
 }
diff --git a/tests/__init__.py b/tests/__init__.py
index adb5fcc..614f1cb 100755
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -17,9 +17,12 @@
 #
 
 __all__ = [
+    "database",
+    "helper",
     "lang",
     "logging",
 
     "data",
+    "file",
     "utils"
 ]
diff --git a/tests/database/database.py b/tests/database/database.py
index eb5ac12..7d2c6b9 100644
--- a/tests/database/database.py
+++ b/tests/database/database.py
@@ -22,10 +22,10 @@ import unittest.mock as mock
 
 from unittest.mock import Mock
 
-from tests.database.helper import set_log
-from tests.database.helper.database import TestData
+from tests.helper import set_log
+from tests.helper.database import TestData
 
-from tests.database.helper.sqlite import (mock_operational_error, sqlite_operational_error)
+from tests.helper.sqlite import (mock_operational_error, sqlite_operational_error)
 
 __all__ = [
     "TestDatabase"
@@ -35,7 +35,8 @@ __all__ = [
 class TestDatabase(unittest.TestCase):
     """Testing class for locking module."""
 
-    def tearDown(self):
+    def setUp(self):
+        set_log()
         return
 
     def assertHasAttr(self, obj, intended_attr):
@@ -105,7 +106,7 @@ class TestDatabase(unittest.TestCase):
     def test_stop_01(self):
         _filename = "{0:s}/test.sqlite".format(os.getcwd())
 
-        _database = TestData(filename=_filename, log=set_log())
+        _database = TestData(filename=_filename)
 
         _check1 = _database.stop()
 
diff --git a/tests/database/sqlite.py b/tests/database/sqlite.py
index 09eeeab..f369960 100644
--- a/tests/database/sqlite.py
+++ b/tests/database/sqlite.py
@@ -21,14 +21,16 @@ import unittest
 import unittest.mock as mock
 
 from unittest.mock import Mock
+
 from bbutil.database import SQLite
 from bbutil.utils import full_path
 
-from tests.database.helper.sqlite import (sqlite_operational_error, sqlite_integrity_error, sqlite_unknown_error,
-                                          mock_operational_error, get_table_01, get_data_01,
-                                          get_data_02, get_data_03, get_data_04, get_data_05, get_data_06, get_data_07,
-                                          get_data_08)
-from tests.database.helper import set_log, get_sqlite
+from tests.helper.sqlite import (sqlite_operational_error, sqlite_integrity_error, sqlite_unknown_error,
+                                 mock_operational_error, get_table_01, get_data_01,
+                                 get_data_02, get_data_03, get_data_04, get_data_05, get_data_06, get_data_07,
+                                 get_data_08)
+
+from tests.helper import get_sqlite, set_log
 
 __all__ = [
     "TestSQLite"
@@ -38,18 +40,18 @@ __all__ = [
 class TestSQLite(unittest.TestCase):
     """Testing class for locking module."""
 
-    def tearDown(self):
+    def setUp(self):
+        set_log()
         return
 
     def test_connect_01(self):
-        _log = set_log()
         _testfile = full_path("{0:s}/test.sqlite".format(os.getcwd()))
         _name = "Test"
 
         if os.path.exists(_testfile) is True:
             os.remove(_testfile)
 
-        _sqlite = SQLite(filename=_testfile, name="Test", log=_log)
+        _sqlite = SQLite(filename=_testfile, name="Test")
 
         _check1 = _sqlite.connect()
         _check2 = os.path.exists(_testfile)
@@ -65,11 +67,8 @@ class TestSQLite(unittest.TestCase):
         return
 
     def test_connect_02(self):
-
-        _log = set_log()
         _name = "Test"
-
-        _sqlite = SQLite(filename="", name="Test", log=_log)
+        _sqlite = SQLite(filename="", name="Test")
 
         _check1 = _sqlite.connect()
 
@@ -79,10 +78,7 @@ class TestSQLite(unittest.TestCase):
         return
 
     def test_connect_03(self):
-
-        _log = set_log()
-
-        _sqlite = SQLite(filename="", name="", log=_log)
+        _sqlite = SQLite(filename="", name="")
 
         _check1 = _sqlite.connect()
 
@@ -90,10 +86,7 @@ class TestSQLite(unittest.TestCase):
         return
 
     def test_connect_04(self):
-
-        _log = set_log()
-
-        _sqlite = SQLite(filename="", name="Test", log=_log, use_memory=True)
+        _sqlite = SQLite(filename="", name="Test", use_memory=True)
 
         _check1 = _sqlite.connect()
 
@@ -102,10 +95,7 @@ class TestSQLite(unittest.TestCase):
 
     @mock.patch('sqlite3.connect', new=mock_operational_error)
     def test_connect_05(self):
-
-        _log = set_log()
-
-        _sqlite = SQLite(filename="", name="Test", log=_log, use_memory=True)
+        _sqlite = SQLite(filename="", name="Test", use_memory=True)
 
         _check1 = _sqlite.connect()
 
@@ -114,14 +104,13 @@ class TestSQLite(unittest.TestCase):
 
     @mock.patch('sqlite3.connect', new=mock_operational_error)
     def test_connect_06(self):
-        _log = set_log()
         _testfile = full_path("{0:s}/test.sqlite".format(os.getcwd()))
         _name = "Test"
 
         if os.path.exists(_testfile) is True:
             os.remove(_testfile)
 
-        _sqlite = SQLite(filename=_testfile, name="Test", log=_log)
+        _sqlite = SQLite(filename=_testfile, name="Test")
 
         _check1 = _sqlite.connect()
         _check2 = os.path.exists(_testfile)
@@ -132,18 +121,6 @@ class TestSQLite(unittest.TestCase):
         self.assertFalse(_check2)
         return
 
-    def test_connect_07(self):
-        _testfile = full_path("{0:s}/test.sqlite".format(os.getcwd()))
-        _name = "Test"
-
-        if os.path.exists(_testfile) is True:
-            os.remove(_testfile)
-
-        _sqlite = SQLite(filename=_testfile, name="Test")
-
-        self.assertRaises(ValueError, _sqlite.connect)
-        return
-
     def test_disconnect_01(self):
         _sqlite = get_sqlite(filename="test.sqlite")
 
diff --git a/tests/database/table.py b/tests/database/table.py
index caed8b8..dfb5953 100644
--- a/tests/database/table.py
+++ b/tests/database/table.py
@@ -20,8 +20,8 @@ import unittest
 
 from bbutil.database import Table, Types
 
-from tests.database.helper import get_sqlite
-from tests.database.helper.table import TestData, get_table_01, get_table_02, get_table_03, get_table_04
+from tests.helper import get_sqlite, set_log
+from tests.helper.table import TestData, get_table_01, get_table_02, get_table_03, get_table_04
 
 __all__ = [
     "TestTable"
@@ -31,7 +31,8 @@ __all__ = [
 class TestTable(unittest.TestCase):
     """Testing class for locking module."""
 
-    def tearDown(self):
+    def setUp(self):
+        set_log()
         return
 
     def assertHasAttr(self, obj, intended_attr):
@@ -44,7 +45,7 @@ class TestTable(unittest.TestCase):
 
         _sqlite = get_sqlite(filename="test.sqlite", clean=True)
 
-        _table = Table(name="test01", sqlite=_sqlite, log=_sqlite.log)
+        _table = Table(name="test01", sqlite=_sqlite)
         _table.add_column(name="testid", data_type=Types.integer, unique=True, keyword=True)
         _table.add_column(name="use_test", data_type=Types.bool)
         _table.add_column(name="testname", data_type=Types.string)
@@ -83,7 +84,7 @@ class TestTable(unittest.TestCase):
 
         _sqlite = get_sqlite(filename="test.sqlite", clean=True)
 
-        _table = Table(name="test01", sqlite=_sqlite, log=_sqlite.log)
+        _table = Table(name="test01", sqlite=_sqlite)
         _table.add_column(name="testid", data_type=Types.integer, unique=True, keyword=True)
         _table.add_column(name="testid", data_type=Types.integer)
 
@@ -121,7 +122,7 @@ class TestTable(unittest.TestCase):
     def test_init_02(self):
 
         _sqlite = get_sqlite(filename="test_check_table.sqlite", path="testdata/database")
-        _table = Table(name="test01", sqlite=_sqlite, log=_sqlite.log)
+        _table = Table(name="test01", sqlite=_sqlite)
 
         _check1 = _sqlite.connect()
         _check2 = _table.init()
@@ -145,6 +146,14 @@ class TestTable(unittest.TestCase):
         self.assertFalse(_check2)
         return
 
+    def test_init_04(self):
+        _table = Table(name="Testos")
+
+        _check1 = _table.init()
+
+        self.assertFalse(_check1)
+        return
+
     def test_select_01(self):
 
         _sqlite = get_sqlite(filename="test_select.sqlite", path="testdata/database")
diff --git a/tests/file.py b/tests/file.py
new file mode 100644
index 0000000..cd53868
--- /dev/null
+++ b/tests/file.py
@@ -0,0 +1,330 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+#    Copyright (C) 2017, Kai Raphahn <kai.raphahn@laburec.de>
+#
+
+import os
+import unittest
+
+import unittest.mock as mock
+
+from bbutil.utils import full_path
+from bbutil.file import File, Folder
+
+from tests.helper import set_log
+from tests.helper.file import create_file
+
+__all__ = [
+    "TestFile"
+]
+
+oserror = OSError("Something strange did happen!")
+mock_oserror = mock.Mock(side_effect=oserror)
+mock_remove = mock.Mock()
+
+
+class TestFile(unittest.TestCase):
+    """Testing class for locking module."""
+
+    def setUp(self):
+        set_log()
+        return
+
+    def test_file_01(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+        _check1 = create_file(_testfile)
+
+        self.assertTrue(_check1)
+
+        _file = File(path=_path, basename=_basename)
+
+        self.assertTrue(_file.valid)
+        self.assertEqual(_file.fullpath, _testfile)
+        self.assertTrue(_file.exists)
+
+        os.remove(_file.fullpath)
+
+        self.assertFalse(_file.exists)
+        return
+
+    def test_file_02(self):
+        _basename = "testfiles.txt"
+
+        _file = File(basename=_basename)
+
+        self.assertFalse(_file.valid)
+        return
+
+    def test_file_03(self):
+        _path = os.getcwd()
+
+        _file = File(path=_path)
+
+        self.assertFalse(_file.valid)
+        return
+
+    def test_file_04(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        _file = File(path=_path, basename=_basename)
+
+        self.assertTrue(_file.valid)
+
+        _file.clear()
+
+        self.assertFalse(_file.valid)
+        self.assertFalse(_file.check)
+        return
+
+    def test_file_05(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+        _file = File(path=_path, basename=_basename)
+
+        self.assertTrue(_file.valid)
+        self.assertFalse(_file.check)
+        return
+
+    def test_file_06(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        _file = File(path=_path, basename=_basename)
+
+        _check1 = create_file(_testfile)
+        self.assertTrue(_check1)
+
+        self.assertTrue(_file.valid)
+        self.assertTrue(_file.check)
+        return
+
+    def test_file_07(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        _check1 = create_file(_testfile)
+        self.assertTrue(_check1)
+
+        _file = File()
+
+        _check2 = _file.open(_testfile)
+
+        os.remove(_testfile)
+
+        _check3 = _file.open(_testfile)
+
+        self.assertTrue(_check2)
+        self.assertFalse(_check3)
+        return
+
+    def test_file_08(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        _check = create_file(_testfile)
+        self.assertTrue(_check)
+
+        _file = File()
+
+        _check = _file.open(_testfile)
+        self.assertTrue(_check)
+
+        _check = _file.remove()
+        self.assertTrue(_check)
+        return
+
+    def test_file_09(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        if os.path.exists(_testfile) is True:
+            os.remove(_testfile)
+
+        _file = File(path=_path, basename=_basename)
+
+        _check = _file.remove()
+        self.assertTrue(_check)
+        return
+
+    def test_file_10(self):
+        _file = File()
+
+        _check = _file.remove()
+        self.assertTrue(_check)
+        return
+
+    @mock.patch('os.remove', new=mock_oserror)
+    def test_file_11(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        _check = create_file(_testfile)
+        self.assertTrue(_check)
+
+        _file = File()
+
+        _check = _file.open(_testfile)
+        self.assertTrue(_check)
+
+        _check = _file.remove()
+        self.assertFalse(_check)
+        return
+
+    @mock.patch('os.remove', new=mock_remove)
+    def test_file_12(self):
+        _basename = "testfiles.txt"
+        _path = os.getcwd()
+        _testfile = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        _check = create_file(_testfile)
+        self.assertTrue(_check)
+
+        _file = File()
+
+        _check = _file.open(_testfile)
+        self.assertTrue(_check)
+
+        _check = _file.remove()
+        self.assertFalse(_check)
+        return
+
+    def test_file_13(self):
+        _file = File()
+
+        self.assertTrue(_file.init())
+        self.assertTrue(_file.init())
+        self.assertTrue(_file.create())
+        return
+
+    def test_folder_01(self):
+        _basename = "testfolder"
+        _path = os.getcwd()
+        _testfolder = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        if os.path.exists(_testfolder):
+            os.rmdir(_testfolder)
+
+        _folder = Folder(path=_path, basename=_basename)
+
+        _check = _folder.create()
+        self.assertTrue(_check)
+
+        os.rmdir(_testfolder)
+        return
+
+    def test_folder_02(self):
+        _basename = "testfolder"
+        _path = os.getcwd()
+        _testfolder = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        if os.path.exists(_testfolder):
+            os.rmdir(_testfolder)
+
+        _folder = Folder(path=_path, basename=_basename)
+
+        _check = _folder.create()
+        self.assertTrue(_check)
+
+        _check = _folder.remove()
+        self.assertTrue(_check)
+        return
+
+    @mock.patch('os.rmdir', new=mock_oserror)
+    def test_folder_03(self):
+        _basename = "testfolder"
+        _path = os.getcwd()
+        _testfolder = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        if os.path.exists(_testfolder):
+            os.rmdir(_testfolder)
+
+        _folder = Folder(path=_path, basename=_basename)
+
+        _check = _folder.create()
+        self.assertTrue(_check)
+
+        _check = _folder.remove()
+        self.assertFalse(_check)
+        return
+
+    @mock.patch('os.rmdir', new=mock_remove)
+    def test_folder_04(self):
+        _basename = "testfolder"
+        _path = os.getcwd()
+        _testfolder = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        if os.path.exists(_testfolder):
+            os.rmdir(_testfolder)
+
+        _folder = Folder(path=_path, basename=_basename)
+
+        _check = _folder.create()
+        self.assertTrue(_check)
+
+        _check = _folder.remove()
+        self.assertFalse(_check)
+        return
+
+    @mock.patch('os.mkdir', new=mock_oserror)
+    def test_folder_05(self):
+        _basename = "testfolder"
+        _path = os.getcwd()
+        _testfolder = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        if os.path.exists(_testfolder):
+            os.rmdir(_testfolder)
+
+        _folder = Folder(path=_path, basename=_basename)
+
+        _check = _folder.create()
+        self.assertFalse(_check)
+        return
+
+    @mock.patch('os.mkdir', new=mock_remove)
+    def test_folder_06(self):
+        _basename = "testfolder"
+        _path = os.getcwd()
+        _testfolder = full_path("{0:s}/{1:s}".format(_path, _basename))
+
+        if os.path.exists(_testfolder):
+            os.rmdir(_testfolder)
+
+        _folder = Folder(path=_path, basename=_basename)
+
+        _check = _folder.create()
+        self.assertFalse(_check)
+        return
+
+    def test_folder_07(self):
+        _folder = Folder()
+        _folder.clear()
+
+        self.assertTrue(_folder.init())
+        self.assertTrue(_folder.open_file())
+        return
diff --git a/tests/database/helper/__init__.py b/tests/helper/__init__.py
similarity index 86%
rename from tests/database/helper/__init__.py
rename to tests/helper/__init__.py
index 1a05616..f23f667 100644
--- a/tests/database/helper/__init__.py
+++ b/tests/helper/__init__.py
@@ -18,16 +18,20 @@
 
 import os
 
+import bbutil
+from bbutil import Logging
+
 from bbutil.database import SQLite
-from bbutil.logging import Logging
 from bbutil.utils import full_path
 
 __all__ = [
+    "database",
+    "file",
     "sqlite",
     "table",
 
-    "set_log",
-    "get_sqlite"
+    "get_sqlite",
+    "set_log"
 ]
 
 _index = {
@@ -38,23 +42,27 @@ _index = {
 }
 
 
-def set_log() -> Logging:
-    _log = Logging()
-    _log.setup(app="Test", level=2, index=_index)
-
-    console = _log.get_writer("console")
-    _log.register(console)
-    _log.open()
-    return _log
-
-
 def get_sqlite(filename: str, path: str = os.getcwd(), clean: bool = False) -> SQLite:
-    _log = set_log()
     _testfile = full_path("{0:s}/{1:s}".format(path, filename))
     _name = "Test"
 
     if (os.path.exists(_testfile) is True) and (clean is True):
         os.remove(_testfile)
 
-    _sqlite = SQLite(filename=_testfile, name="Test", log=_log)
+    _sqlite = SQLite(filename=_testfile, name="Test")
     return _sqlite
+
+
+def set_log():
+    if bbutil.log is not None:
+        return
+
+    _log = Logging()
+    _log.setup(app="Test", level=2, index=_index)
+
+    console = _log.get_writer("console")
+    _log.register(console)
+    _log.open()
+
+    bbutil.set_log(_log)
+    return
diff --git a/tests/database/helper/database.py b/tests/helper/database.py
similarity index 90%
rename from tests/database/helper/database.py
rename to tests/helper/database.py
index c785cd1..0923a83 100644
--- a/tests/database/helper/database.py
+++ b/tests/helper/database.py
@@ -21,7 +21,6 @@ from typing import Optional
 
 from unittest.mock import Mock
 
-from tests.database.helper import set_log
 from bbutil.database import Types, Table, Database
 
 __all__ = [
@@ -38,7 +37,6 @@ class TestData(Database):
     mock_connection: Optional[Mock] = None
 
     def init(self):
-        self.log = set_log()
         self.name = "Testos"
         return
 
@@ -49,7 +47,7 @@ class TestData(Database):
         if self.mock_connection is not None:
             self.sqlite.connection = self.mock_connection
 
-        _table = Table(name="tester01", sqlite=self.sqlite, log=self.log)
+        _table = Table(name="tester01", sqlite=self.sqlite)
         _table.add_column(name="testid", data_type=Types.integer, primarykey=True)
         _table.add_column(name="use_test", data_type=Types.bool)
         _table.add_column(name="testname", data_type=Types.string)
@@ -57,7 +55,7 @@ class TestData(Database):
         self.tables.append(_table)
         self.table01 = _table
 
-        _table = Table(name="tester02", sqlite=self.sqlite, log=self.log)
+        _table = Table(name="tester02", sqlite=self.sqlite)
         _table.add_column(name="testid", data_type=Types.integer, primarykey=True)
         _table.add_column(name="use_test", data_type=Types.bool)
         _table.add_column(name="category", data_type=Types.string, keyword=True)
diff --git a/tests/helper/file.py b/tests/helper/file.py
new file mode 100644
index 0000000..f6397a9
--- /dev/null
+++ b/tests/helper/file.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+#
+#    Copyright (C) 2017, Kai Raphahn <kai.raphahn@laburec.de>
+#
+
+import os.path
+
+__all__ = [
+    "create_file"
+]
+
+
+def create_file(filename: str) -> bool:
+    _path = os.path.dirname(filename)
+    if os.path.exists(_path) is False:
+        return False
+
+    f = open(file=filename, mode="w")
+
+    f.write("TEST!\n")
+
+    f.close()
+    return True
diff --git a/tests/database/helper/sqlite.py b/tests/helper/sqlite.py
similarity index 100%
rename from tests/database/helper/sqlite.py
rename to tests/helper/sqlite.py
diff --git a/tests/database/helper/table.py b/tests/helper/table.py
similarity index 88%
rename from tests/database/helper/table.py
rename to tests/helper/table.py
index 8456f44..6b78386 100644
--- a/tests/database/helper/table.py
+++ b/tests/helper/table.py
@@ -29,7 +29,7 @@ __all__ = [
 
 
 def get_table_01(sqlite_object: SQLite) -> Table:
-    _table = Table(name="tester01", sqlite=sqlite_object, log=sqlite_object.log)
+    _table = Table(name="tester01", sqlite=sqlite_object)
     _table.add_column(name="testid", data_type=Types.integer, unique=True, keyword=True)
     _table.add_column(name="use_test", data_type=Types.bool)
     _table.add_column(name="testname", data_type=Types.string)
@@ -38,7 +38,7 @@ def get_table_01(sqlite_object: SQLite) -> Table:
 
 
 def get_table_02(sqlite_object: SQLite) -> Table:
-    _table = Table(name="tester01", sqlite=sqlite_object, log=sqlite_object.log)
+    _table = Table(name="tester01", sqlite=sqlite_object)
     _table.add_column(name="testid", data_type=Types.integer, unique=True, keyword=True)
     _table.add_column(name="use_test", data_type=Types.bool)
     _table.add_column(name="testname", data_type=Types.string)
@@ -48,7 +48,7 @@ def get_table_02(sqlite_object: SQLite) -> Table:
 
 
 def get_table_03(name: str, sqlite_object: SQLite) -> Table:
-    _table = Table(name=name, sqlite=sqlite_object, log=sqlite_object.log)
+    _table = Table(name=name, sqlite=sqlite_object)
     _table.add_column(name="testid", data_type=Types.integer, primarykey=True)
     _table.add_column(name="use_test", data_type=Types.bool)
     _table.add_column(name="testname", data_type=Types.string)
@@ -57,7 +57,7 @@ def get_table_03(name: str, sqlite_object: SQLite) -> Table:
 
 
 def get_table_04(sqlite_object: SQLite) -> Table:
-    _table = Table(name="tester01", sqlite=sqlite_object, log=sqlite_object.log)
+    _table = Table(name="tester01", sqlite=sqlite_object)
     _table.add_column(name="testid", data_type=Types.integer, primarykey=True)
     _table.add_column(name="use_test", data_type=Types.bool)
     _table.add_column(name="category", data_type=Types.string, keyword=True)