diff --git a/tools/python/jwutils/db/schema/Schema.py b/tools/python/jwutils/db/schema/Schema.py index 61382b7..67c0644 100644 --- a/tools/python/jwutils/db/schema/Schema.py +++ b/tools/python/jwutils/db/schema/Schema.py @@ -49,6 +49,10 @@ class Schema(abc.ABC): # export def _access_defining_columns(self): pass + @abc.abstractmethod + def _model_module_search_paths(self) -> list[tuple[str, type]]: + pass + # ------ API to be called def __len__(self): @@ -99,3 +103,7 @@ class Schema(abc.ABC): # export def columns(self, table_name: str) -> Iterable[Column]: return self.__tables[table_name].columns + + @property + def model_module_search_paths(self) -> list[tuple[str, type]]: + return self._model_module_search_paths() diff --git a/tools/python/jwutils/db/schema/Table.py b/tools/python/jwutils/db/schema/Table.py index b3bceb7..d7490d1 100644 --- a/tools/python/jwutils/db/schema/Table.py +++ b/tools/python/jwutils/db/schema/Table.py @@ -4,8 +4,10 @@ from typing import Optional, Iterable, Any # TODO: Need any for many things, as import abc from collections import OrderedDict +from urllib.parse import quote_plus from ...log import * +from ...misc import load_class from .ColumnSet import ColumnSet from .DataType import DataType @@ -32,10 +34,11 @@ class Table(abc.ABC): # export self.__log_columns: Optional[Iterable[str]] = None self.__display_columns: Optional[Iterable[str]] = None self.__column_default: Optional[dict[str, Any]] = None - self.__base_location: Optional[Iterable[str]] = None - self.__location: Optional[Iterable[str]] = None - self.__row_location: Optional[Iterable[str]] = None + self.__base_location_rule: Optional[Iterable[str]] = None + self.__location_rule: Optional[Iterable[str]] = None + self.__row_location_rule: Optional[Iterable[str]] = None self.__foreign_keys_to_parent_table: Optional[OrderedDict[str, Any]] = None + self.__model_class: Optional[Any] = None @property def __columns(self) -> OrderedDict[str, Any]: @@ -101,7 +104,8 @@ class Table(abc.ABC): # export @abc.abstractmethod def _display_columns(self) -> Iterable[str]: - return self._primary_keys() + return None + #return self._primary_keys() @abc.abstractmethod def _nullable_columns(self) -> Iterable[str]: @@ -123,29 +127,31 @@ class Table(abc.ABC): # export slog(WARNING, f'Returning None model name for table {self.name}') return None - def _model_class(self) -> Optional[Any]: - slog(WARNING, f'Returning None model class for table {self.name}') - throw(ERR, "Not implemented") - return None + def _model_module_search_paths(self) -> list[tuple[str, type]]: + return self.schema.model_module_search_paths # Fall back to Schema-global default @abc.abstractmethod def _query_name(self) -> str: - return self.__name + return 'tbl/' + self.__name + + @abc.abstractmethod + def _row_query_name(self) -> str: + return 'row/' + self.__name # -- common URL schema for all data - def _base_location(self) -> Optional[str]: + def _base_location_rule(self) -> Optional[str]: return f'/{self.name}' - def _location(self) -> Optional[str]: + def _location_rule(self) -> Optional[str]: ret = '' for col in self.__schema.access_defining_columns: if col in self.primary_keys: ret += f'/<{col}>' - ret += '/' + self.base_location + ret += self.base_location_rule return ret - def _row_location(self) -> Optional[str]: - ret = self._location() + def _row_location_rule(self) -> Optional[str]: + ret = self._location_rule() if ret is not None: for col in self.primary_keys: if col not in self.__schema.access_defining_columns: @@ -185,26 +191,55 @@ class Table(abc.ABC): # export def model_name(self) -> Optional[str]: return self._model_name() + @property + def model_class(self) -> Any: + if self.__model_class is None: + pattern = r'^' + self.model_name + '$' + for module_path, base_class in self._model_module_search_paths(): + ret = load_class(module_path, base_class, class_name_filter=pattern) + if ret is not None: + self.__model_class = ret + break + else: + throw(ERR, f'No model class found for model {self.model_name}') + return self.__model_class + def query_name(self) -> str: return self._query_name() - @property - def base_location(self): - if self.__base_location is None: - self.__base_location = self._base_location() - return self.__base_location + def row_query_name(self) -> str: + return self._row_query_name() @property - def location(self): - if self.__location is None: - self.__location = self._base_location() - return self.__location + def base_location_rule(self): + if self.__base_location_rule is None: + self.__base_location_rule = self._base_location_rule() + return self.__base_location_rule @property - def row_location(self): - if self.__row_location is None: - self.__row_location = self._row_location() - return self.__row_location + def location_rule(self): + if self.__location_rule is None: + self.__location_rule = self._location_rule() + return self.__location_rule + + def location(self, *args, **kwargs): + ret = self.location_rule + for token, val in kwargs.items(): # FIXME: Poor man's row location assembly + ret = re.sub(f'<{token}>', quote_plus(quote_plus(str(val))), ret) + return ret + + @property + def row_location_rule(self): + if self.__row_location_rule is None: + self.__row_location_rule = self._row_location_rule() + return self.__row_location_rule + + def row_location(self, *args, **kwargs): + ret = self.row_location_rule + for col in self.primary_keys: + if col in kwargs: # FIXME: Poor man's row location assembly + ret = re.sub(f'<{col}>', quote_plus(quote_plus(str(kwargs[col]))), ret) + return ret @property def primary_keys(self) -> Iterable[str]: