Compare commits
4 commits
main
...
feature/ad
Author | SHA1 | Date | |
---|---|---|---|
|
831b801696 | ||
|
60e2d3e044 | ||
|
b40984d8b0 | ||
|
5bc5b810e0 |
13 changed files with 1025 additions and 0 deletions
9
.env
Normal file
9
.env
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
PYTHONPATH=${PWD}/src/modules/
|
||||||
|
|
||||||
|
ITEMSDB_SQLITE3_DATABASEDIR=${PWD}/build/database
|
||||||
|
ITEMSDB_SQLITE3_FILENAME=itemsdb.db
|
||||||
|
ITEMSDB_PREFIX=test2_
|
||||||
|
|
||||||
|
ITEMSDB_LOGLEVEL=debug
|
||||||
|
ITEMSDB_DEBUG_MODULES=itemsdb.sqlite3
|
||||||
|
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#
|
||||||
|
build/
|
||||||
|
.venv/
|
||||||
|
**/__pycache__/
|
13
Pipfile
Normal file
13
Pipfile
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
pytest = "*"
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
peewee = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.11"
|
54
README.md
54
README.md
|
@ -8,3 +8,57 @@ dependency graph.
|
||||||
For further information see the [Items database
|
For further information see the [Items database
|
||||||
documentation](https://code.matthiess.it/collaboration/itemsdb-doc).
|
documentation](https://code.matthiess.it/collaboration/itemsdb-doc).
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* Python3 >= 3.9
|
||||||
|
* python packages
|
||||||
|
* pip
|
||||||
|
* pipenv
|
||||||
|
* [See pipenv install inctructions](https://pipenv.pypa.io/en/latest/installation.html#make-sure-you-have-python-and-pip)
|
||||||
|
* sqlite3
|
||||||
|
* git
|
||||||
|
* Access to the project git repository
|
||||||
|
|
||||||
|
|
||||||
|
## Install Python requirements
|
||||||
|
|
||||||
|
* The required python packages will be installed with the `pipenv` command.
|
||||||
|
* Install this python package with your native os package installer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt-get install pipenv
|
||||||
|
```
|
||||||
|
|
||||||
|
* Change to the root of your git repository worktree and execute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipenv install
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install all python package dependencies for running this
|
||||||
|
application.
|
||||||
|
|
||||||
|
* To install the development requirements you should execute this command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipenv install --dev
|
||||||
|
```
|
||||||
|
|
||||||
|
* To get an overview of the installed packages and theire depenmmdencies run
|
||||||
|
"`pipenv graph`"
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipenv graph
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Activate the pipenv environment
|
||||||
|
|
||||||
|
Change to the project git repository worktree and execute "`pipenv shell`" to
|
||||||
|
activate the previously installed python virtual environment.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipenv shell
|
||||||
|
```
|
||||||
|
|
||||||
|
This is similar to execute "`. $venv_base_dir/bin/activate`".
|
||||||
|
|
118
src/modules/itemsdb/__init__.py
Normal file
118
src/modules/itemsdb/__init__.py
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
from enum import StrEnum, auto, verify, UNIQUE
|
||||||
|
from uuid import uuid4 as new_uuid
|
||||||
|
|
||||||
|
from .version import __version__
|
||||||
|
from .log import info, debug, error, pf
|
||||||
|
|
||||||
|
version = __version__
|
||||||
|
|
||||||
|
|
||||||
|
from .database import *
|
||||||
|
from .sqlite3 import *
|
||||||
|
|
||||||
|
@verify(UNIQUE)
|
||||||
|
class ItemsDBError(StrEnum):
|
||||||
|
PARAMETER_TYPE = auto()
|
||||||
|
ITEM_TYPE = auto()
|
||||||
|
LINK_TYPE = auto()
|
||||||
|
SCHEMA_TYPE = auto()
|
||||||
|
ITEM = auto()
|
||||||
|
LINK = auto()
|
||||||
|
SCHEMA = auto()
|
||||||
|
VERSION = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class ItemsDBException(Exception):
|
||||||
|
def __init__(self, msg, error_code, **exc_args):
|
||||||
|
self.msg = msg
|
||||||
|
self.error_code = error_code
|
||||||
|
for arg_k, arg_v in exc_args.items():
|
||||||
|
if arg_k in ["msg", "error_code"]:
|
||||||
|
continue
|
||||||
|
self.__setattr__(arg_k, arg_v)
|
||||||
|
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
msg = ""
|
||||||
|
for n in self.__dir__():
|
||||||
|
if n in ["add_note", "args", "with_traceback"]:
|
||||||
|
continue
|
||||||
|
msg = f"{n}: {pf(self.__getattribute__(n))}\n"
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BaseObject():
|
||||||
|
def __init__(self, item_type, item_data, **item_args):
|
||||||
|
self.type = item_type
|
||||||
|
self.data = item_data
|
||||||
|
self.id = item_args.get("id", None)
|
||||||
|
self.version = item_args.get("version", None)
|
||||||
|
|
||||||
|
|
||||||
|
def __new_id__(self):
|
||||||
|
try:
|
||||||
|
item_id = self.id
|
||||||
|
if not item_id:
|
||||||
|
self.id = new_uuid()
|
||||||
|
except AttributeError:
|
||||||
|
self.id = new_uuid()
|
||||||
|
|
||||||
|
def __new_version__(self):
|
||||||
|
try:
|
||||||
|
item_version = self.version
|
||||||
|
if not item_version:
|
||||||
|
self.version = new_uuid()
|
||||||
|
except AttributeError:
|
||||||
|
self.version = new_uuid()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseObject):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Link(BaseObject):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BaseSchema():
|
||||||
|
def __init_(self, name, schema):
|
||||||
|
e_msg = ""
|
||||||
|
if not isinstance(name, str):
|
||||||
|
e_msg += (
|
||||||
|
f"Schema name must be from type 'str' but is '{type(name)}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(schema, dict):
|
||||||
|
e_msg += (
|
||||||
|
f"Schema data must be from type 'dict' but is '{type(schema)}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
if e_msg:
|
||||||
|
raise ItemsDBException(e_msg, ItemsDBError.PARAMETER_TYPE))
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.schema = schema
|
||||||
|
|
||||||
|
|
||||||
|
class ItemSchema(BaseSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class LinkSchema(BaseSchema):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DBError",
|
||||||
|
"DBException",
|
||||||
|
"DBSqlite3",
|
||||||
|
"version",
|
||||||
|
"Item",
|
||||||
|
"Link",
|
||||||
|
"ItemSchema",
|
||||||
|
"LinkSchema",
|
||||||
|
]
|
120
src/modules/itemsdb/database.py
Normal file
120
src/modules/itemsdb/database.py
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
from enum import StrEnum, auto, verify, UNIQUE
|
||||||
|
|
||||||
|
from .version import __version__
|
||||||
|
from .log import info, debug, error
|
||||||
|
|
||||||
|
|
||||||
|
@verify(UNIQUE)
|
||||||
|
class DBError(StrEnum):
|
||||||
|
CONNECTION = auto()
|
||||||
|
CREATION = auto()
|
||||||
|
CURSOR = auto()
|
||||||
|
FILE_EXIST = auto()
|
||||||
|
NOTIMPLEMENTED = auto()
|
||||||
|
PARAMETER_NEEDED = auto()
|
||||||
|
PARAMETER_TYPE = auto()
|
||||||
|
TYPE_NOTIMPLEMENTED = auto()
|
||||||
|
TYPE_UNKNOWN = auto()
|
||||||
|
UNKNOWN = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class DBException(Exception):
|
||||||
|
def __init__(self, msg="", error_code=None, db=None, con=None, cur=None):
|
||||||
|
self.msg = msg
|
||||||
|
self.db = db
|
||||||
|
self.con = con
|
||||||
|
self.cur = cur
|
||||||
|
self.error_code = error_code
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
msg = f"msg : {self.msg}\n" if self.msg else ""
|
||||||
|
msg += f"error: {self.error_code}\n" if self.error_code else ""
|
||||||
|
msg += f"db : {str(self.db)}\n" if self.db else ""
|
||||||
|
msg += f"con : {str(self.con)}\n" if self.con else ""
|
||||||
|
msg += f"cur : {str(self.cur)}\n" if self.cur else ""
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
class DBBase:
|
||||||
|
db_types = [
|
||||||
|
"sqlite3",
|
||||||
|
"postgresql",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, db_type, parameter=None):
|
||||||
|
if db_type not in self.db_types:
|
||||||
|
raise DBException(
|
||||||
|
msg=(
|
||||||
|
f"Database type '{db_type}' unknown. Use one of: "
|
||||||
|
f"{', '.join(self.db_types)}"
|
||||||
|
),
|
||||||
|
error_code=DBError("type_unknown"),
|
||||||
|
)
|
||||||
|
elif db_type == "postgresql":
|
||||||
|
raise DBException(
|
||||||
|
msg=f"Database type '{db_type}' not implemented yet",
|
||||||
|
error_code=DBError("type_notimplemented"),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.type = db_type
|
||||||
|
self.parameter = parameter
|
||||||
|
|
||||||
|
def __throw_db_exception__(
|
||||||
|
self,
|
||||||
|
msg="Not implemented by database driver",
|
||||||
|
error_code=DBError.NOTIMPLEMENTED,
|
||||||
|
):
|
||||||
|
raise DBException(
|
||||||
|
msg=msg,
|
||||||
|
error_code=error_code,
|
||||||
|
)
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def createDatabase(self):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def createTables(self):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def createItem(self):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def createLink(self):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def createItemSchema(self):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def createLinkSchema(self):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def getItem(self, item):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def insertItem(self, item):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def updateItem(self, item):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def insertLink(self, link_type, fromItem, toItem):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def updateLink(self, link_type, fromItem, toItem):
|
||||||
|
self.__throw_db_exception__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Not implemented by database driver: {id(self)}"
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DBError",
|
||||||
|
"DBException",
|
||||||
|
"DBBase",
|
||||||
|
]
|
64
src/modules/itemsdb/log.py
Normal file
64
src/modules/itemsdb/log.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
from .version import __version__
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from os.path import basename
|
||||||
|
from inspect import (
|
||||||
|
getouterframes,
|
||||||
|
currentframe,
|
||||||
|
isfunction,
|
||||||
|
ismethod,
|
||||||
|
isclass,
|
||||||
|
ismodule,
|
||||||
|
)
|
||||||
|
|
||||||
|
from pprint import pformat as pf
|
||||||
|
|
||||||
|
|
||||||
|
def __print__(msg, file=sys.stderr):
|
||||||
|
fn, ln = __getCallerData__()
|
||||||
|
debug_prefix = f"{fn}:{ln}: "
|
||||||
|
print(f"{debug_prefix}{msg}", file=file)
|
||||||
|
|
||||||
|
|
||||||
|
def info(msg, file=sys.stderr):
|
||||||
|
__print__(f"INFO: {msg}", file=file)
|
||||||
|
|
||||||
|
|
||||||
|
def warn(msg, file=sys.stderr):
|
||||||
|
__print__(f"WARNING: {msg}", file=file)
|
||||||
|
|
||||||
|
|
||||||
|
def error(msg, file=sys.stderr):
|
||||||
|
__print__(f"ERROR: {msg}", file=file)
|
||||||
|
|
||||||
|
|
||||||
|
def debug(msg, file=sys.stderr):
|
||||||
|
__print__(f"DEBUG: {msg}", file=file)
|
||||||
|
|
||||||
|
|
||||||
|
def __getCallerData__(level=4):
|
||||||
|
# return:
|
||||||
|
#
|
||||||
|
# FrameInfo(frame, filename, lineno, function, code_context, index)
|
||||||
|
# https://docs.python.org/3/library/inspect.html#inspect.FrameInfo
|
||||||
|
#
|
||||||
|
outer_frames = getouterframes(currentframe())
|
||||||
|
len_outer_frames = len(outer_frames)
|
||||||
|
if len_outer_frames < level:
|
||||||
|
outer_frame = outer_frames[-1:]
|
||||||
|
else:
|
||||||
|
outer_frame = outer_frames[level - 1]
|
||||||
|
|
||||||
|
(
|
||||||
|
frame,
|
||||||
|
filename,
|
||||||
|
lineno,
|
||||||
|
function,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
) = outer_frame
|
||||||
|
|
||||||
|
return basename(filename), lineno
|
225
src/modules/itemsdb/sqlite3/__init__.py
Normal file
225
src/modules/itemsdb/sqlite3/__init__.py
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
from ..version import __version__
|
||||||
|
from ..log import info, debug, error, pf
|
||||||
|
from ..database import DBError, DBException, DBBase
|
||||||
|
|
||||||
|
|
||||||
|
from playhouse.sqlite_ext import SqliteExtDatabase
|
||||||
|
|
||||||
|
from .models import *
|
||||||
|
# from .functions import *
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"DBSqlite3",
|
||||||
|
"DBError",
|
||||||
|
"DBException",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class DBSqlite3(DBBase):
|
||||||
|
type = "sqlite3"
|
||||||
|
|
||||||
|
def __init__(self, parameter=None):
|
||||||
|
debug(f"type: '{self.type}', parameter={pf(parameter)}")
|
||||||
|
super().__init__(self.type, parameter)
|
||||||
|
if not parameter:
|
||||||
|
self.pragmas = self.__pragma_parameter__({})
|
||||||
|
elif not isinstance(parameter, dict):
|
||||||
|
e_msg = (
|
||||||
|
"Database parameters mut be from type 'dict' but is "
|
||||||
|
f"'{type(parameter)}'"
|
||||||
|
)
|
||||||
|
raise DBException(e_msg, DBError("parameter_type"))
|
||||||
|
else:
|
||||||
|
for pk, pv in parameter.items():
|
||||||
|
self.__setattr__(pk, pv)
|
||||||
|
|
||||||
|
self.pragmas = self.__pragma_parameter__(
|
||||||
|
parameter.get("pragmas", {})
|
||||||
|
)
|
||||||
|
|
||||||
|
def __pragma_parameter__(self, pragmas):
|
||||||
|
# See:http://docs.peewee-orm.com/en/latest/peewee/sqlite_ext.html#getting-started
|
||||||
|
db_pragmas = dict(
|
||||||
|
cache_size=-1024 * 64, journal_mode="wal", foreign_keys=1
|
||||||
|
)
|
||||||
|
|
||||||
|
for pk, pv in pragmas.items():
|
||||||
|
db_pragmas[pk] = pv
|
||||||
|
|
||||||
|
return [(k, v) for k, v in db_pragmas.items()]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
msg = f"Database type: {self.type}\n"
|
||||||
|
msg += (
|
||||||
|
f" parameter : {pf(self.parameter)}\n"
|
||||||
|
if self.parameter
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
msg += (
|
||||||
|
f" pragmas : {pf(self.pragmas)}\n"
|
||||||
|
if self.pragmas
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def __try_database_init(self):
|
||||||
|
filename = self.parameter["filename"]
|
||||||
|
try:
|
||||||
|
self.db = SqliteExtDatabase(filename, pragmas=self.pragmas)
|
||||||
|
return True, f"Database '{filename}' initialzed"
|
||||||
|
except Exception as e:
|
||||||
|
raise DBException(e, DBError.CREATION)
|
||||||
|
|
||||||
|
def __try_connect__(self):
|
||||||
|
if self.db.is_closed():
|
||||||
|
try:
|
||||||
|
self.db.connect()
|
||||||
|
debug("Open")
|
||||||
|
except Exception as e:
|
||||||
|
raise DBException(e, DBError.CONNECTION)
|
||||||
|
|
||||||
|
def __isInitialized(self):
|
||||||
|
try:
|
||||||
|
_ = self.db
|
||||||
|
return True
|
||||||
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __get_prefix__(self):
|
||||||
|
if "prefix" not in self.parameter:
|
||||||
|
return ""
|
||||||
|
return self.parameter["prefix"]
|
||||||
|
|
||||||
|
def create(self, filename=None):
|
||||||
|
if filename:
|
||||||
|
debug(f"Create database file '{filename}'")
|
||||||
|
self.parameter.update(filename=filename)
|
||||||
|
elif not self.parameter["filename"]:
|
||||||
|
e_msg = f"Sqlite3 database filename not given"
|
||||||
|
debug(e_msg)
|
||||||
|
raise DBException(e_msg, DBError.PARAMETER_NEEDED)
|
||||||
|
|
||||||
|
debug(f"{self.parameter['filename']=}")
|
||||||
|
debug(f"{os.path.exists(self.parameter['filename'])=}")
|
||||||
|
if os.path.exists(self.parameter["filename"]):
|
||||||
|
e_msg = (
|
||||||
|
"Sqlite3 database file already exist: "
|
||||||
|
f"'{self.parameter['filename']}'"
|
||||||
|
)
|
||||||
|
debug(e_msg)
|
||||||
|
return False, e_msg
|
||||||
|
|
||||||
|
debug("If not exist, create database directory")
|
||||||
|
os.makedirs(os.path.dirname(self.parameter["filename"]), exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.db = SqliteExtDatabase(
|
||||||
|
self.parameter["filename"], pragmas=self.pragmas
|
||||||
|
)
|
||||||
|
# Create the sqlite3 database file by open/connect and close it.
|
||||||
|
self.db.connect()
|
||||||
|
self.connection = self.db.connection()
|
||||||
|
except Exception as e:
|
||||||
|
raise DBException(e, DBError.CREATION)
|
||||||
|
|
||||||
|
return True, f"Sqlite3 database '{self.parameter['filename']}' created"
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
db_filename = self.parameter["filename"]
|
||||||
|
debug(f"Try to open database '{db_filename}'")
|
||||||
|
if not self.__isInitialized():
|
||||||
|
debug("Database not initialized")
|
||||||
|
self.__try_database_init()
|
||||||
|
|
||||||
|
debug("Try to connect to database")
|
||||||
|
self.db.connect(reuse_if_open=True)
|
||||||
|
self.connection = self.db.connection()
|
||||||
|
return True, f"Sqlite3 database '{db_filename}' open"
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.db.is_closed():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.db.close()
|
||||||
|
self.connection = None
|
||||||
|
return True
|
||||||
|
except Exception as db_e:
|
||||||
|
return False, str(db_e)
|
||||||
|
|
||||||
|
def createTables(self, models=itemsdb_models):
|
||||||
|
self.__try_connect__()
|
||||||
|
debug(f"Create {len(models)} tables in database")
|
||||||
|
with self.db:
|
||||||
|
try:
|
||||||
|
prefix = self.__get_prefix__()
|
||||||
|
if prefix:
|
||||||
|
debug(f"Use database table prefix: '{prefix}'")
|
||||||
|
for model in models:
|
||||||
|
table_name = f"{prefix}{model.__name__}"
|
||||||
|
debug(
|
||||||
|
f"Set table name for '{model.__name__}' to "
|
||||||
|
f"'{table_name}'"
|
||||||
|
)
|
||||||
|
model._meta.table_name = table_name
|
||||||
|
|
||||||
|
debug(
|
||||||
|
f"Assign the model '{model.__name__}' to the "
|
||||||
|
"database."
|
||||||
|
)
|
||||||
|
model._meta.database = self.db
|
||||||
|
|
||||||
|
debug("Try to create database tables")
|
||||||
|
tables_not_exist = [
|
||||||
|
model for model in models if not self.tableExist(model)
|
||||||
|
]
|
||||||
|
if tables_not_exist:
|
||||||
|
table_names_not_exist = ", ".join(
|
||||||
|
[model._meta.table_name for model in tables_not_exist]
|
||||||
|
)
|
||||||
|
debug("Create database tables: " f"{table_names_not_exist}")
|
||||||
|
self.db.create_tables(tables_not_exist)
|
||||||
|
else:
|
||||||
|
debug("Database tables already exists")
|
||||||
|
self.models = models
|
||||||
|
return True, "Sqlite3 database tables created"
|
||||||
|
except Exception as ct_e:
|
||||||
|
raise DBException(ct_e, DBError.CREATION)
|
||||||
|
|
||||||
|
def tableExist(self, model):
|
||||||
|
sql_query = (
|
||||||
|
"select tbl_name from sqlite_schema where type = 'table' "
|
||||||
|
"and tbl_name = ?"
|
||||||
|
)
|
||||||
|
table_name = model._meta.table_name
|
||||||
|
query_result = model.raw(sql_query, table_name)
|
||||||
|
query_result_len = len(query_result)
|
||||||
|
|
||||||
|
if query_result_len > 0:
|
||||||
|
debug(f"Database table exist: '{table_name}'")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
debug(f"Database table does not exist: '{table_name}'")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getItem(self, item):
|
||||||
|
return item
|
||||||
|
|
||||||
|
def insertItem(self, item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def updateItem(self, item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def insertLink(self, link_type, fromItem, toItem):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def updateLink(self, link_type, fromItem, toItem):
|
||||||
|
pass
|
232
src/modules/itemsdb/sqlite3/functions.py
Normal file
232
src/modules/itemsdb/sqlite3/functions.py
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from ..version import __version__
|
||||||
|
from ..log import (
|
||||||
|
info,
|
||||||
|
warn,
|
||||||
|
error,
|
||||||
|
debug,
|
||||||
|
)
|
||||||
|
from .models import (
|
||||||
|
item_types,
|
||||||
|
items,
|
||||||
|
link_types,
|
||||||
|
links,
|
||||||
|
itemsdb_models,
|
||||||
|
)
|
||||||
|
|
||||||
|
from peewee import (
|
||||||
|
ModelBase,
|
||||||
|
SqliteDatabase,
|
||||||
|
IntegrityError,
|
||||||
|
)
|
||||||
|
|
||||||
|
DATABASE_TYPE = "sqlite3"
|
||||||
|
|
||||||
|
|
||||||
|
def __check_model_type__(model):
|
||||||
|
if not isinstance(model, ModelBase):
|
||||||
|
e_msg = (
|
||||||
|
f"Wrong parameter type: '{type(model)}'. This should be derived "
|
||||||
|
"from peewee.Model"
|
||||||
|
)
|
||||||
|
error(e_msg)
|
||||||
|
raise Exception(e_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def getTablename(model):
|
||||||
|
__check_model_type__(model)
|
||||||
|
return model._meta.table_name
|
||||||
|
|
||||||
|
|
||||||
|
def setTablename(model, table_name):
|
||||||
|
__check_model_type__(model)
|
||||||
|
debug(f"Set table name for '{model.__name__}' to '{table_name}'")
|
||||||
|
try:
|
||||||
|
model._meta.table_name = table_name
|
||||||
|
return True, table_name, ""
|
||||||
|
except Exception as e:
|
||||||
|
e_msg = (
|
||||||
|
f"Fail to set table name '{table_name}' for model "
|
||||||
|
f"'{model.__name__}': {e}"
|
||||||
|
)
|
||||||
|
error(e_msg)
|
||||||
|
return False, model._meta.table_name, e_msg
|
||||||
|
|
||||||
|
|
||||||
|
def createDatabase(*args, **kwargs):
|
||||||
|
debug(f"Create database from type '{DATABASE_TYPE}'")
|
||||||
|
if (len(args) == 0 and "filename" not in kwargs) or (
|
||||||
|
len(args) > 0 and not args[0]
|
||||||
|
):
|
||||||
|
e_msg = (
|
||||||
|
"You need to set the 'filename' parameter to create the sqlite3 "
|
||||||
|
"database"
|
||||||
|
)
|
||||||
|
error(e_msg)
|
||||||
|
return False, None, e_msg
|
||||||
|
|
||||||
|
if len(args) > 0 and args[0]:
|
||||||
|
debug("Use first parameter as filename")
|
||||||
|
filename = args[0]
|
||||||
|
else:
|
||||||
|
debug("Use keyword parameter 'filename' as filename")
|
||||||
|
filename = kwargs.get("filename")
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
e_msg = "'filename' parameter MUST NOT be empty"
|
||||||
|
error(e_msg)
|
||||||
|
return False, None, e_msg
|
||||||
|
|
||||||
|
if os.path.exists(filename):
|
||||||
|
debug(f"Sqlite3 database file '{filename}' already exist")
|
||||||
|
if not ("force" in kwargs and kwargs["force"] is True):
|
||||||
|
e_msg = (
|
||||||
|
f"Sqlite3 database file '{filename}' already exist and you "
|
||||||
|
"have not set 'force=True'. The database file will not be "
|
||||||
|
"recreated."
|
||||||
|
)
|
||||||
|
return False, None, e_msg
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||||
|
|
||||||
|
pragmas = kwargs.get("pragmas", None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if pragmas:
|
||||||
|
if isinstance(pragmas, dict):
|
||||||
|
db = SqliteDatabase(filename, pragmas=pragmas)
|
||||||
|
success = True
|
||||||
|
else:
|
||||||
|
db = None
|
||||||
|
msg = f"Parameter 'pragmas' must be from type 'dict' but is: {pragmas}"
|
||||||
|
debug(msg)
|
||||||
|
success = False
|
||||||
|
else:
|
||||||
|
db = SqliteDatabase(filename)
|
||||||
|
success = True
|
||||||
|
|
||||||
|
if success:
|
||||||
|
db.connect()
|
||||||
|
db.close()
|
||||||
|
pragma_msg = f"{' with pragmas: {pragmas}' if pragmas else ''}"
|
||||||
|
msg = f"Sqlite3 database '{filename}'{pragma_msg} created"
|
||||||
|
debug(msg)
|
||||||
|
except Exception as c_e:
|
||||||
|
msg = f"Sqlite3 database('{filename}') creation failed: {c_e}"
|
||||||
|
debug(msg)
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success, db, msg
|
||||||
|
|
||||||
|
|
||||||
|
def createTables(db, table_models, prefix=""):
|
||||||
|
with db:
|
||||||
|
e_msg = ""
|
||||||
|
if prefix:
|
||||||
|
prefix_result = [
|
||||||
|
setTablename(m, f"{prefix}_{getTablename(m)}")
|
||||||
|
for m in table_models
|
||||||
|
if not getTablename(m).startswith(f"{prefix}_")
|
||||||
|
]
|
||||||
|
|
||||||
|
if False in [r[0] for r in prefix_result]:
|
||||||
|
prefix_errors = [
|
||||||
|
f"{r[1]}: {r[2]}" for r in prefix_result if r[0] is False
|
||||||
|
]
|
||||||
|
newline = "\n"
|
||||||
|
newline_tab = "\n\t"
|
||||||
|
|
||||||
|
e_msg = (
|
||||||
|
f"Fail to set table names:${newline_tab}"
|
||||||
|
f"{newline_tab.join(prefix_errors)}{newline}"
|
||||||
|
)
|
||||||
|
error(e_msg)
|
||||||
|
return False, db, e_msg
|
||||||
|
e_msg = "Setting table names successful"
|
||||||
|
|
||||||
|
debug(
|
||||||
|
"Database table names has created: "
|
||||||
|
f"{', '.join([m._meta.table_name for m in table_models])}"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for m in table_models:
|
||||||
|
debug(f"Set active database for model '{m._meta.name}'")
|
||||||
|
m._meta.database = db
|
||||||
|
|
||||||
|
db.create_tables(table_models)
|
||||||
|
e_msg = "Creating tables successful "
|
||||||
|
except Exception as ct_e:
|
||||||
|
e_msg = f"Fail to create database tables: {ct_e}"
|
||||||
|
debug(e_msg)
|
||||||
|
return False, db, e_msg
|
||||||
|
|
||||||
|
return True, db, e_msg
|
||||||
|
|
||||||
|
|
||||||
|
def createItemType(db, name, schema):
|
||||||
|
if not isinstance(db, SqliteDatabase):
|
||||||
|
msg = (
|
||||||
|
"Parameter 'db' must be from type 'SqliteDatabase' but is "
|
||||||
|
f"'{type(db)}'"
|
||||||
|
)
|
||||||
|
return False, None, msg
|
||||||
|
elif not isinstance(name, str):
|
||||||
|
msg = f"Parameter 'name' must be from type 'str' but is '{type(name)}'"
|
||||||
|
return False, None, msg
|
||||||
|
elif not isinstance(schema, dict):
|
||||||
|
msg = (
|
||||||
|
"Parameter 'schema' must be from type 'dict' but is "
|
||||||
|
f"'{type(schema)}'"
|
||||||
|
)
|
||||||
|
return False, None, msg
|
||||||
|
|
||||||
|
item_type = None
|
||||||
|
with db:
|
||||||
|
try:
|
||||||
|
item_type = item_types.create(name=name, schema=schema)
|
||||||
|
msg = f"Item type '{name}' created"
|
||||||
|
success = True
|
||||||
|
except IntegrityError as i_e:
|
||||||
|
msg = f"Item type '{name}' already exist: {i_e}"
|
||||||
|
debug(msg)
|
||||||
|
success = False
|
||||||
|
except Exception as e:
|
||||||
|
msg = f"Fail to create item type '{name}': {e}"
|
||||||
|
debug(msg)
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success, item_type, msg
|
||||||
|
|
||||||
|
|
||||||
|
def removeItemType(dn, name):
|
||||||
|
if not isinstance(db, SqliteDatabase):
|
||||||
|
msg = (
|
||||||
|
"Parameter 'db' must be from type 'SqliteDatabase' but is "
|
||||||
|
f"'{type(db)}'"
|
||||||
|
)
|
||||||
|
return False, None, msg
|
||||||
|
elif not isinstance(name, str):
|
||||||
|
msg = f"Parameter 'name' must be from type 'str' but is '{type(name)}'"
|
||||||
|
return False, None, msg
|
||||||
|
|
||||||
|
with db:
|
||||||
|
try:
|
||||||
|
item_type = item_types.get(item_types.name == name)
|
||||||
|
item_type.delete_instance()
|
||||||
|
success = True
|
||||||
|
except item_types.DoesNotExist:
|
||||||
|
msg = f"Item type '{name}' does not exist"
|
||||||
|
debug(msg)
|
||||||
|
success = False
|
||||||
|
except Exception as e:
|
||||||
|
msg = f"Fail to remove item type '{name}': {e}"
|
||||||
|
debug(msg)
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success, name, msg
|
82
src/modules/itemsdb/sqlite3/models.py
Normal file
82
src/modules/itemsdb/sqlite3/models.py
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
|
||||||
|
from ..version import __version__
|
||||||
|
from ..log import info, warn, error, debug
|
||||||
|
|
||||||
|
|
||||||
|
from peewee import (
|
||||||
|
Model,
|
||||||
|
TextField,
|
||||||
|
BinaryUUIDField,
|
||||||
|
ForeignKeyField,
|
||||||
|
IntegerField,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(Model):
|
||||||
|
class Meta:
|
||||||
|
database = None
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModelTypes(BaseModel):
|
||||||
|
name = TextField(unique=True, null=False)
|
||||||
|
schema = TextField()
|
||||||
|
|
||||||
|
|
||||||
|
class Version(BaseModel):
|
||||||
|
class Meta:
|
||||||
|
table_name = "itemsdb"
|
||||||
|
|
||||||
|
version = IntegerField(primary_key=True)
|
||||||
|
state = TextField(null=False)
|
||||||
|
|
||||||
|
|
||||||
|
class link_types(BaseModelTypes):
|
||||||
|
class Meta:
|
||||||
|
table_name = "link_types"
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class item_types(BaseModelTypes):
|
||||||
|
class Meta:
|
||||||
|
table_name = "item_types"
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class items(BaseModel):
|
||||||
|
class Meta:
|
||||||
|
table_name = "items"
|
||||||
|
|
||||||
|
id = BinaryUUIDField(primary_key=True)
|
||||||
|
version = BinaryUUIDField(null=False)
|
||||||
|
type = ForeignKeyField(item_types)
|
||||||
|
data = TextField(null=False, default={})
|
||||||
|
|
||||||
|
|
||||||
|
class links(BaseModel):
|
||||||
|
class Meta:
|
||||||
|
table_name = "links"
|
||||||
|
|
||||||
|
id = BinaryUUIDField(primary_key=True)
|
||||||
|
version = BinaryUUIDField(null=False)
|
||||||
|
type = ForeignKeyField(link_types)
|
||||||
|
from_item = ForeignKeyField(items)
|
||||||
|
to_item = ForeignKeyField(items)
|
||||||
|
|
||||||
|
|
||||||
|
itemsdb_models = [link_types, links, item_types, items]
|
||||||
|
|
||||||
|
__all = [
|
||||||
|
"Version",
|
||||||
|
"link_types",
|
||||||
|
"item_types",
|
||||||
|
"links",
|
||||||
|
"items",
|
||||||
|
"itemsdb_models",
|
||||||
|
]
|
4
src/modules/itemsdb/version.py
Normal file
4
src/modules/itemsdb/version.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.0.1"
|
39
test/create-database.py
Normal file
39
test/create-database.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
from itemsdb.sqlite3 import *
|
||||||
|
from itemsdb.log import *
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
dbdir = os.getenv("ITEMSDB_SQLITE3_DATABASEDIR", ".")
|
||||||
|
dbname = os.getenv("ITEMSDB_SQLITE3_FILENAME", "itemsdb.db")
|
||||||
|
dbprefix = os.getenv("ITEMSDB_PREFIX", "")
|
||||||
|
dbfilename = os.path.join(dbdir, f"{dbprefix}{dbname}")
|
||||||
|
|
||||||
|
parameter = dict(filename=dbfilename, prefix=dbprefix)
|
||||||
|
|
||||||
|
db = DBSqlite3(parameter=parameter)
|
||||||
|
|
||||||
|
debug(f"Database: '{type(db)}'")
|
||||||
|
try:
|
||||||
|
db_created, msg = db.create()
|
||||||
|
debug(f"Database after create(): '{type(db)}'")
|
||||||
|
if db_created:
|
||||||
|
print(f"Database '{dbfilename}' created")
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
open, open_msg = db.open()
|
||||||
|
print(open_msg)
|
||||||
|
|
||||||
|
debug(f"Database after open(): '{type(db)}'")
|
||||||
|
tables_created, msg = db.createTables()
|
||||||
|
if tables_created:
|
||||||
|
print(f"{msg}: {', '.join([model.__name__ for model in db.models])}")
|
||||||
|
else:
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
|
||||||
|
except DBException as db_e:
|
||||||
|
print(db_e)
|
61
test/init_sqlite3.py
Executable file
61
test/init_sqlite3.py
Executable file
|
@ -0,0 +1,61 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
print(os.path.abspath(os.curdir))
|
||||||
|
print(__file__)
|
||||||
|
|
||||||
|
module_path = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(__file__), "../src/modules")
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Add python module path: '{module_path}'")
|
||||||
|
sys.path.insert(0, module_path)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from itemsdb.log import info, warn, error, debug
|
||||||
|
except Exception as i_e:
|
||||||
|
e_msg = f"Fail to import itemsdb.log: {i_e}"
|
||||||
|
print(e_msg, file=sys.stderr)
|
||||||
|
raise Exception(e_msg)
|
||||||
|
|
||||||
|
from itemsdb import *
|
||||||
|
|
||||||
|
|
||||||
|
database_filename = os.getenv("ITEMSDB_DATABASE_NAME", "itemsdb_test.db")
|
||||||
|
database_path_module = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(__file__), "../build", database_filename)
|
||||||
|
)
|
||||||
|
database_path = os.getenv("ITEMSDB_DATABASE_PATH", database_path_module)
|
||||||
|
database_table_prefix = os.getenv("ITEMSDB_DATABASE_TABLE_PREFIX", "test")
|
||||||
|
|
||||||
|
database_pragmas = dict(
|
||||||
|
journal_mode="wal", foreign_keys=1, ignore_check_constraints=0
|
||||||
|
)
|
||||||
|
|
||||||
|
success, db, e_msg = createDatabase(
|
||||||
|
database_path, force=True, pragmas=database_pragmas
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
error(e_msg)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
success, db, e_msg = createTables(
|
||||||
|
db, itemsdb_models, prefix=database_table_prefix
|
||||||
|
)
|
||||||
|
if not success:
|
||||||
|
error(e_msg)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
item_name = "server"
|
||||||
|
item_schema = dict(name=item_name, properties=[1, 2, 3, 4, 5])
|
||||||
|
success, item_type, e_msg = createItemType(db, item_name, item_schema)
|
||||||
|
if not success:
|
||||||
|
error(e_msg)
|
||||||
|
sys.exit(3)
|
Loading…
Reference in a new issue