From 4cc990605500afd5ed964f76baf5b54d96101d14 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Sun, 2 Feb 2025 14:02:21 +0100 Subject: [PATCH] db.query: Add Query, QueryResult, Queries Signed-off-by: Jan Lindemann --- tools/python/jwutils/db/query/Makefile | 4 + tools/python/jwutils/db/query/Queries.py | 93 ++++++++++++++++++++ tools/python/jwutils/db/query/Query.py | 64 ++++++++++++++ tools/python/jwutils/db/query/QueryResult.py | 56 ++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 tools/python/jwutils/db/query/Makefile create mode 100644 tools/python/jwutils/db/query/Queries.py create mode 100644 tools/python/jwutils/db/query/Query.py create mode 100644 tools/python/jwutils/db/query/QueryResult.py diff --git a/tools/python/jwutils/db/query/Makefile b/tools/python/jwutils/db/query/Makefile new file mode 100644 index 0000000..781b0c8 --- /dev/null +++ b/tools/python/jwutils/db/query/Makefile @@ -0,0 +1,4 @@ +TOPDIR = ../../../../.. + +include $(TOPDIR)/make/proj.mk +include $(JWBDIR)/make/py-mod.mk diff --git a/tools/python/jwutils/db/query/Queries.py b/tools/python/jwutils/db/query/Queries.py new file mode 100644 index 0000000..8f662c0 --- /dev/null +++ b/tools/python/jwutils/db/query/Queries.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +from typing import Any + +import abc + +from jwutils.log import * +from jwutils.misc import load_classes +from jwutils.Cmds import Cmds +from jwutils.db.DataBase import DataBase +from jwutils.db.query.Query import Query as QueryBase +from jwutils.db.query.QueryResult import QueryResult +from jwutils.db.schema.Schema import Schema + +class Queries(abc.ABC): # export + + class Query(QueryBase): + + def __init__(self: Any, query: QueryBase, name: str, location: str, func: Any): + self.__query = query + self.__func = func + self.__location = location + self.__name = name + + # -- implement API + + def _run(self, *args, **kwargs) -> QueryResult: + return self.__func(*args, **kwargs) + + def _register(self): + raise Exception('Can\'t call register on this object') + + def _column_names(self) -> list[str]: + return self.__query.column_names + + @property + def name(self): + return self.__name + + def __init__(self, db: DataBase) -> None: + assert(isinstance(db, DataBase)) + self.__db = db + self.__queries: dict[str, Any] = dict() + + def __getitem__(self, name) -> Query: + try: + return self.__queries[name] + except Exception as e: + slog(ERR, f'Failed to retrieve query {name} ({e})') + slog_m(ERR, '\n'.join(self.__queries.keys())) + raise + + @property + def db(self) -> DataBase: + return self.__db + + @property + def app(self) -> Cmds: + return self.__db.app + + def load(self, modules: list[str], cls=QueryBase): + for path in modules: + slog(INFO, f'Loading modules from {path}') + for c in load_classes(path, cls): + c(self).register() + + @property + def schema(self) -> Schema: + return self.__db.schema + + def add(self, query: QueryBase, query_name: str, location: str, func: Any): + slog(INFO, f'Adding query "{query_name}" on location "{location}"') + assert(isinstance(query_name, str)) + assert(isinstance(location, str)) + #ret = self.Query(query, func) + ret = self.Query(query, query_name, location, func) + #setattr(ret, 'name', name) + self.__queries[query_name] = ret + self._add(ret) + + # To be overriden in case the derived class wants to keep track + def _add(self, query): + pass + +# def register(self): +# return self._register() +# +# def query(self, *args, **kwargs): +# return self.app.query(*args, **kwargs) +# +# def pages(self, *args, **kwargs): +# return self.app.pages(*args, **kwargs) +# diff --git a/tools/python/jwutils/db/query/Query.py b/tools/python/jwutils/db/query/Query.py new file mode 100644 index 0000000..0342400 --- /dev/null +++ b/tools/python/jwutils/db/query/Query.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +from typing import Any + +import abc + +from jwutils.log import * +from jwutils.misc import load_classes +from jwutils.Cmds import Cmds +from jwutils.db.DataBase import DataBase +from jwutils.db.query.QueryResult import QueryResult +#from jwutils.db.query.Queries import Queries + +class Query(abc.ABC): # export + + def __init__(self, parent: Any) -> None: + self.__parent = parent + + # -- pure virtuals + + @abc.abstractmethod + def _run(self, *args, **kwargs) -> QueryResult: + raise Exception('Called pure virtual _run()') + + @abc.abstractmethod + def _register(self): + raise Exception('Called pure virtual _register()') + + @abc.abstractmethod + def _column_names(self) -> list[str]: + raise Exception('Called pure virtual _column()') + + # -- used by Queries class + + def register(self): + return self._register() + + # -- to be used + + def _add(self, query_name: str, location: str, func: Any): + return self.__parent.add(self, query_name, location, func) + + def run(self, *args, **kwargs) -> QueryResult: + return self._run(*args, **kwargs) + + @property + def parent(self): + return self.__parent + + @property + def db(self) -> DataBase: + return self.__parent.db + + @property + def schema(self): + return self.__parent.db.schema + + @property + def app(self) -> Cmds: + return self.__parent.app + + @property + def column_names(self) -> list[str]: + return self._column_names() diff --git a/tools/python/jwutils/db/query/QueryResult.py b/tools/python/jwutils/db/query/QueryResult.py new file mode 100644 index 0000000..8da354b --- /dev/null +++ b/tools/python/jwutils/db/query/QueryResult.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +from typing import Any, Union + +import abc +from enum import Enum, auto + +from jwutils.log import * +from jwutils.Cmds import Cmds +from jwutils.db.DataBase import DataBase + +class ResType(Enum): # export + Statement = auto() + Scalars = auto() + One = auto() + First = auto() + Pages = auto() + +class QueryResult(abc.ABC): # export + + def __init__(self, query: Any) -> None: + self.__query = query + + @property + def app(self) -> Cmds: + return self.__query.app + + @property + def db(self) -> DataBase: + return self.__query.db + + @property + def schema(self): + return self.__query.db.schema + + def rows(self) -> list[Any]: + return self._cast(ResType.Scalars) + + def pages(self, per_page=20) -> Any: + return self._cast(ResType.Pages, per_page=per_page) + + def one(self) -> Any: + return self._cast(ResType.One) + + def first(self) -> Any: + return self._cast(ResType.First) + + @property + def statement(self) -> Any: + return self._cast(ResType.Statement) + + # -- pure virtuals + + @abc.abstractmethod + def _cast(self, res_type: ResType, **kwargs) -> Union[Any|list[Any]]: + pass