from __future__ import annotations
import contextlib
from typing import Any, cast, Dict, List, Type, Tuple, Set, TYPE_CHECKING
import importlib
import types
from com.sun.star.lang import XServiceInfo
from com.sun.star.lang import XTypeProvider
from ooodev.events.args.event_args import EventArgs
from ooodev.io.log.named_logger import NamedLogger
from ooodev.events.args.generic_args import GenericArgs
# from ooodev.adapter.component_base import ComponentBase
from ooodev.loader import lo as mLo
from ooodev.utils.builder.build_import_arg import BuildImportArg
from ooodev.utils.builder.build_event_arg import BuildEventArg
from ooodev.utils.builder.check_kind import CheckKind
from ooodev.utils.builder.init_kind import InitKind
from ooodev.events.partial.events_partial import EventsPartial
from ooodev.utils import gen_util as gUtil
from ooodev.utils.partial.lo_inst_props_partial import LoInstPropsPartial
if TYPE_CHECKING:
from ooodev.utils.type_var import EventCallback
from ooodev.loader.inst.lo_inst import LoInst
[docs]class DefaultBuilder(LoInstPropsPartial, EventsPartial):
[docs] def __init__(self, component: Any, lo_inst: LoInst | None = None):
# ComponentBase.__init__(self, component)
EventsPartial.__init__(self)
if lo_inst is None:
lo_inst = self._get_default_lo()
LoInstPropsPartial.__init__(self, lo_inst)
self._component = component
# _bases_partial could be classes such as property classes
self._class_props: Dict[str, Any] = {}
self._bases_partial: Dict[Type[Any], BuildImportArg] = {}
# _bases_interfaces could be interfaces such as ComponentPartial
self._bases_interfaces: Dict[Type[Any], BuildImportArg] = {}
self._bases_event_interfaces: Dict[types.ModuleType, BuildEventArg] = {}
# _build_args is a dictionary of BuildImportArg, using dictionary to avoid duplicates and to keep order
# because BuildImportArg, and BuildEventArg do not use the entire object as the key, we are also storing the value,
# this way the values can be changed without changing the key. EG: a BuildImportArg optional value is changed.
self._build_args: Dict[BuildImportArg, BuildImportArg] = {}
self._event_args: Dict[BuildEventArg, BuildEventArg] = {}
self._omit: Set[str] = set()
# add suffixes that are excluded from having _partial appended to the class name
self._partial_excludes = {"_listener", "_events"}
self._service_info = mLo.Lo.qi(XServiceInfo, self._component)
self._type_names = None
if self._service_info is None:
self._implementation_name = ""
else:
self._implementation_name = self._service_info.getImplementationName()
if self._implementation_name:
self._logger = NamedLogger(name=f"{self.__class__.__name__} - {self._implementation_name}")
else:
self._logger = NamedLogger(
name=f"{self.__class__.__name__} - ID: {gUtil.Util.generate_random_string(6).upper()}"
)
def _get_default_lo(self) -> LoInst:
from ooodev.loader.lo import Lo
return Lo.current_lo
def _get_type_names_list(self) -> List[str]:
result: List[str] = []
provider = mLo.Lo.qi(XTypeProvider, self._component, True)
component_types = cast(Tuple[Any], provider.getTypes())
for t in component_types:
result.append(t.typeName)
return result
def _get_type_names(self) -> Set[str]:
if self._type_names is None:
component_types = self._get_type_names_list()
result: Set[str] = set()
for type_name in component_types:
result.add(type_name)
self._type_names = result
return self._type_names
[docs] def auto_interface(self) -> None:
"""
Automatically add interfaces to the builder based on the component types.
"""
unique_type_names: Set[str] = set()
lst = self._get_type_names_list()
for type_name in lst:
if type_name in unique_type_names:
continue
if self.has_omit(type_name):
continue
unique_type_names.add(type_name)
self.auto_add_interface(type_name)
def _get_import(self, name: str) -> types.ModuleType:
return importlib.import_module(name)
def _get_mod_class_names(self, name: str) -> tuple[str, str]:
return tuple(name.rsplit(".", 1)) # type: ignore
def _get_class(self, arg: BuildImportArg) -> Any:
mod_name, class_name = self._get_mod_class_names(arg.ooodev_name)
return getattr(self._get_import(mod_name), class_name)
def _supports_interface(self, *name: str) -> bool:
type_names = self._get_type_names()
for n in name:
if n in type_names:
return True
return False
def _supports_only_interface(self, *name: str) -> bool:
"""
Check if the component supports only the first interface in the list.
Returns:
bool: True if the component supports only the first interface in the list; otherwise, False.
"""
count = len(name)
if count == 0:
return False
type_names = self._get_type_names()
if count == 1:
return name[0] in type_names
if name[0] not in type_names:
return False
for n in name[1:]:
if n in type_names:
return False
return True
def _supports_all_interface(self, *name: str) -> bool:
type_names = self._get_type_names()
for n in name:
if n not in type_names:
return False
return True
def _supports_service(self, *name: str) -> bool:
if self._service_info is None:
return False
result = False
for srv in name:
result = self._service_info.supportsService(srv)
if result:
break
return result
def _supports_all_service(self, *name: str) -> bool:
if self._service_info is None:
return False
result = True
for srv in name:
result = self._service_info.supportsService(srv)
if not result:
break
return result
def _supports_only_service(self, *name: str) -> bool:
"""
Check if the component supports only the first services in the list.
Returns:
bool: True if the component supports only the first services in the list; otherwise, False.
"""
if self._service_info is None:
return False
count = len(name)
if count == 0:
return False
if count == 1:
return self._service_info.supportsService(name[0])
result = self._service_info.supportsService(name[0])
if not result:
return False
for srv in name[1:]:
if self._service_info.supportsService(srv):
return False
return True
def _get_optional_class(self, arg: BuildImportArg) -> Any:
if not arg.uno_name:
return None
if self._passes_check(arg):
try:
return self._get_class(arg)
except ImportError:
if self._logger.is_error:
self._logger.error(f"Import Error: {arg.ooodev_name}")
return None
return None
def _get_optional_event(self, arg: BuildEventArg) -> types.ModuleType | None:
if self._passes_event_check(arg):
try:
return self._get_import(arg.module_name)
except ImportError:
if self._logger.is_error:
self._logger.error(f"Import Error: {arg.module_name}")
return None
return None
def _passes_check(self, arg: BuildImportArg) -> bool:
if not arg.uno_name:
return True
if arg.check_kind == CheckKind.SERVICE:
return self._supports_service(*arg.uno_name)
elif arg.check_kind == CheckKind.SERVICE_ALL:
return self._supports_all_service(*arg.uno_name)
elif arg.check_kind == CheckKind.INTERFACE:
return self._supports_interface(*arg.uno_name)
elif arg.check_kind == CheckKind.INTERFACE_ALL:
return self._supports_all_interface(*arg.uno_name)
elif arg.check_kind == CheckKind.INTERFACE_ONLY:
return self._supports_only_interface(*arg.uno_name)
elif arg.check_kind == CheckKind.SERVICE_ONLY:
return self._supports_only_service(*arg.uno_name)
return True
def _passes_event_check(self, arg: BuildEventArg) -> bool:
if not arg.module_name:
return False
if not arg.class_name:
return False
if not arg.callback_name:
return False
if arg.check_kind == CheckKind.SERVICE:
return self._supports_service(*arg.uno_name)
elif arg.check_kind == CheckKind.SERVICE_ALL:
return self._supports_all_service(*arg.uno_name)
elif arg.check_kind == CheckKind.INTERFACE:
return self._supports_interface(*arg.uno_name)
elif arg.check_kind == CheckKind.INTERFACE_ALL:
return self._supports_all_interface(*arg.uno_name)
elif arg.check_kind == CheckKind.INTERFACE_ONLY:
return self._supports_only_interface(*arg.uno_name)
elif arg.check_kind == CheckKind.SERVICE_ONLY:
return self._supports_only_service(*arg.uno_name)
return True
def _add_base(self, base: Type[Any], arg: BuildImportArg) -> None:
if arg.init_kind == InitKind.COMPONENT:
if self._passes_check(arg):
self._bases_partial[base] = arg
else:
if self._passes_check(arg):
self._bases_interfaces[base] = arg
def _add_event_base(self, mod_type: types.ModuleType, arg: BuildEventArg) -> None:
self._bases_event_interfaces[mod_type] = arg
def _clear_imports(self) -> None:
self._bases_partial.clear()
self._bases_interfaces.clear()
self._bases_event_interfaces.clear()
def _process_imports(self) -> None:
self._clear_imports()
for arg in self._build_args.values():
if not arg.ooodev_name:
continue
if arg.ooodev_name in self._omit:
continue
if arg.optional:
clz = self._get_optional_class(arg)
if clz:
self._add_base(clz, arg)
else:
try:
clz = self._get_class(arg)
self._add_base(clz, arg)
if self._logger.is_debug:
self._logger.debug(f"Added: {arg.ooodev_name}")
except ImportError:
self._logger.error(f"Import Error: {arg.ooodev_name}")
continue
for arg in self._event_args.values():
if not arg.module_name:
continue
if not arg.class_name:
continue
if not arg.callback_name:
continue
if arg.optional:
mod = self._get_optional_event(arg)
if mod:
self._add_event_base(mod, arg)
else:
try:
mod = self._get_import(arg.module_name)
self._add_event_base(mod, arg)
except ImportError:
self._logger.error(f"Import Error: {arg.module_name}")
continue
def _init_class(self, instance: Any, clz: Type[Any], kind: InitKind) -> None:
"""
Initialize a class.
Args:
instance (Any): The instance to initialize.
clz (Type[Any]): The partial class to initialize.
kind (InitKind): The kind of initialization.
Note:
If the class init kind is ``InitKind.CALLBACK`` then an event is triggered.
The event name is ``class_init`` and the event data is a dictionary with keys ``class``, ``kind``, ``instance``.
After the event is triggered, if the event data has keys ``args`` and ``kwargs`` then they will be passed to the class constructor.
"""
if kind == InitKind.COMPONENT:
clz.__init__(instance, self._component) # type: ignore
elif kind == InitKind.COMPONENT_INTERFACE:
clz.__init__(instance, self._component, None) # type: ignore
elif kind == InitKind.LO_INST:
clz.__init__(instance, self.lo_inst) # type: ignore
elif kind == InitKind.CALLBACK:
eargs = EventArgs(source=self)
eargs.event_data = {"class": clz, "kind": kind, "instance": instance}
self.trigger_event("class_init", eargs)
args = eargs.event_data.get("args", ())
kwargs = eargs.event_data.get("kwargs", {})
clz.__init__(instance, *args, **kwargs)
else:
clz.__init__(instance)
def _init_event_class(self, instance: Any, mod: types.ModuleType, arg: BuildEventArg) -> None:
# see subscribe_class_event_init() for more information
# after instance is created, we need to callback to the module on_lazy_cb()
clz = getattr(mod, arg.class_name)
eargs = EventArgs(source=self)
triggers = {"src_comp": self._component, "src_instance": instance}
event_data = {"class": clz, "instance": instance}
eargs.event_data = {"triggers": triggers, "data": event_data}
self.trigger_event("class_event_init", eargs)
generic_triggers = eargs.event_data.get("triggers", {})
if generic_triggers:
trigger_args = GenericArgs(**generic_triggers)
else:
trigger_args = None
cb = getattr(mod, arg.callback_name)
# obj = clz.__new__(clz) # type: ignore
# obj.__init__(trigger_args=trigger_args, cb=cb)
clz.__init__(instance, trigger_args=trigger_args, cb=cb) # type: ignore
def _create_class(self, clz: Type[Any], kind: InitKind) -> Any:
"""
Creates a class.
Args:
clz (Type[Any]): The class to create.
kind (InitKind): The kind of initialization.
Returns:
Any: Class instance
Note:
If the class init kind is ``InitKind.CALLBACK`` then an event is triggered.
The event name is ``class_create`` and the event data is a dictionary with keys ``class``, ``kind``.
After the event is triggered, if the event data has keys ``args`` and ``kwargs`` then they will be passed to the class constructor.
"""
if kind == InitKind.COMPONENT:
inst = clz(self._component) # type: ignore
elif kind == InitKind.COMPONENT_INTERFACE:
inst = clz(self._component, None) # type: ignore
elif kind == InitKind.LO_INST:
inst = clz(self.lo_inst) # type: ignore
elif kind == InitKind.CALLBACK:
eargs = EventArgs(source=self)
eargs.event_data = {"class": clz, "kind": kind}
self.trigger_event("class_create", eargs)
args = eargs.event_data.get("args", ())
kwargs = eargs.event_data.get("kwargs", {})
clz(*args, **kwargs)
else:
inst = clz()
return inst
[docs] def init_classes(self, instance: Any) -> None:
"""Initialize the classes including the events."""
for clz, kind in self._bases_partial.items():
self._init_class(instance, clz, kind.init_kind)
for clz, kind in self._bases_interfaces.items():
self._init_class(instance, clz, kind.init_kind)
for mod, arg in self._bases_event_interfaces.items():
self._init_event_class(instance, mod, arg)
def _generate_class(self, base_class: Type[Any], name: str, **kwargs) -> Type[Any]:
try:
bases = (
[base_class]
+ list(self._bases_partial.keys())
+ list(self._bases_interfaces.keys())
+ list([getattr(mod, arg.class_name) for mod, arg in self._bases_event_interfaces.items()])
)
if len(bases) == 1 and not kwargs:
return base_class
else:
return type(name, tuple(bases), kwargs)
except Exception:
self._logger.error(f"Error generating class: {name}", exc_info=True)
raise
def _convert_to_ooodev(self, name: str) -> str:
if not name:
raise ValueError("Name cannot be empty.")
if name.startswith("ooodev."):
return name
# sample input: com.sun.star.beans.XExactName
suffix = name.replace("com.sun.star.", "") # beans.XExactName
ns, class_name = suffix.rsplit(".", 1) # beans, XExactName
if class_name.startswith("X"):
class_name = class_name[1:] # ExactName
odev_ns = f"ooodev.adapter.{ns.lower()}.{gUtil.Util.camel_to_snake(class_name)}"
if not odev_ns.endswith(tuple(self._partial_excludes)):
odev_ns += "_partial"
odev_class = f"{class_name}Partial"
else:
odev_class = class_name
return f"{odev_ns}.{odev_class}"
[docs] def has_type(self, t: Type[Any]) -> bool:
"""
Gets if the builder has a type in the partial or interface bases.
This check does not check for events.
"""
if t in self._bases_partial:
return True
if t in self._bases_interfaces:
return True
return False
[docs] def pop_type(self, t: Type[Any]) -> None:
"""
Removes a type from the partial or interface bases.
This will not remove events.
"""
if t in self._bases_partial:
del self._bases_partial[t]
if t in self._bases_interfaces:
del self._bases_interfaces[t]
[docs] def add_build_arg(self, *args: BuildImportArg) -> None:
"""
Add one or more import builder to the instance.
Args:
args (BuildImportArg): One or more BuildImportArg instance.
"""
for arg in args:
self._build_args[arg] = arg
[docs] def insert_build_arg(self, idx: int, *args: BuildImportArg) -> None:
"""
Insert one or more import builder to the instance.
Args:
idx (int): The index to insert the import.
args (BuildImportArg): One or more BuildImportArg instance.
"""
if not args:
return
items = list(self._build_args.items())
if len(args) == 1:
reversed_args = list(args)
else:
list_args = list(args)
reversed_args = list_args[::-1]
for arg in reversed_args:
items.insert(idx, (arg, arg))
self._build_args = dict(items)
[docs] def add_event_arg(self, *args: BuildEventArg) -> None:
"""
Add one or more import event to the instance.
Args:
args (BuildEventArg): One or more BuildImportArg instance.
"""
for arg in args:
if arg in self._event_args:
_ = self._event_args.pop(arg)
self._event_args[arg] = arg
[docs] def insert_event_arg(self, idx: int, *args: BuildEventArg) -> None:
"""
Insert one or more import event to the instance.
Args:
idx (int): The index to insert the import.
args (BuildEventArg): One or more BuildImportArg instance.
"""
if not args:
return
items = list(self._event_args.items())
if len(args) == 1:
reversed_args = list(args)
else:
list_args = list(args)
reversed_args = list_args[::-1]
for arg in reversed_args:
items.insert(idx, (arg, arg))
self._event_args = dict(items)
[docs] def get_builders(self) -> List[BuildImportArg]:
"""Get the list of BuildImportArg."""
return list(self._build_args.values())
[docs] def get_events(self) -> List[BuildEventArg]:
"""Get the list of BuildImportArg."""
return list(self._event_args.values())
[docs] def merge(
self, instance: DefaultBuilder, make_optional: bool | None = None, check_kind: CheckKind | int | None = None
) -> None:
"""
Add the builders from another instance.
Args:
instance (DefaultBuilder): The instance to add the builders from.
make_optional (bool, None, optional): Specifies if the import is optional. A value of None means do not change. Defaults to ``None``.
check_kind (CheckKind, int, None, optional): Check Kind. A value of None means do not change. Defaults to ``None``.
"""
def get_check_kind(arg: BuildImportArg | BuildEventArg) -> CheckKind | int:
nonlocal check_kind
if check_kind is not None:
return check_kind
return arg.check_kind
def get_make_optional(arg: BuildImportArg | BuildEventArg) -> bool:
nonlocal make_optional
if make_optional is not None:
return make_optional
return arg.optional
for arg in instance.get_builders():
self.add_import(
name=arg.ooodev_name,
uno_name=arg.uno_name,
optional=get_make_optional(arg),
init_kind=arg.init_kind,
check_kind=get_check_kind(arg),
)
for arg in instance.get_events():
self.add_event(
module_name=arg.module_name,
class_name=arg.class_name,
callback_name=arg.callback_name,
uno_name=arg.uno_name,
optional=get_make_optional(arg),
check_kind=get_check_kind(arg),
)
self.omits.update(instance.omits)
[docs] def add_ooodev_builder(
self, mod_name: str, *, func_name: str = "get_builder", make_optional: bool = False
) -> None:
"""
Add a builder from an ooodev name.
Args:
name (str): Ooodev name such as ``ooodev.adapter.container.index_access_partial.IndexAccessPartial``.
optional (bool, optional): Specifies if the import is optional. Defaults to ``False``.
make_optional (bool, optional): Specifies if the import is optional. Defaults to ``False``.
"""
if not mod_name:
return
try:
mod = self._get_import(mod_name)
except ImportError:
return
if not hasattr(mod, func_name):
return
func = getattr(mod, func_name)
result = func(self._component)
self.merge(result, make_optional)
[docs] def remove_import(self, name: str) -> None:
"""
Remove an import from the builder.
Args:
name (str): Ooodev name such as ``ooodev.adapter.container.index_access_partial.IndexAccessPartial``.
"""
for arg in self._build_args.keys():
if arg.ooodev_name == name:
del self._build_args[arg]
break
[docs] def remove_event(self, module_name: str) -> None:
"""
Remove an event from the builder.
Args:
module_name (str): Ooodev name such as ``ooodev.adapter.util.refresh_events.RefreshEvents``.
"""
for arg in self._event_args.keys():
if arg.module_name == module_name:
del self._event_args[arg]
break
[docs] def auto_add_interface(
self, uno_name: str, optional: bool = True, check_kind: CheckKind | int = CheckKind.INTERFACE
) -> None:
"""
Add an import from a UNO name.
Args:
uno_name (str): UNO Name. such as ``com.sun.star.container.XIndexAccess``.
optional (bool, optional): Specifies if the import is optional. Defaults to ``True``.
check_kind (CheckKind, int, optional): Check Kind. Defaults to ``CheckKind.INTERFACE``.
"""
# when adding a UNO Type it needs to be added to the type names if it does not exist.
# This way when the builder checks for _supports_interface int it will know if the component supports the interface.
if uno_name.startswith("com.sun.star."):
types = self._get_type_names()
if uno_name not in types:
types.add(uno_name)
name = self._convert_to_ooodev(uno_name)
self.add_import(name=name, uno_name=uno_name, optional=optional, check_kind=check_kind)
[docs] def insert_import(
self,
idx: int,
name: str,
*,
uno_name: str | Tuple[str] = "",
optional: bool = False,
init_kind: InitKind | int = InitKind.COMPONENT_INTERFACE,
check_kind: CheckKind | int = CheckKind.NONE,
) -> BuildImportArg:
"""
Insert an import into the builder.
Args:
idx (int): The index to insert the import.
name (str): Ooodev name such as ``ooodev.adapter.container.index_access_partial.IndexAccessPartial``.
uno_name (str, optional): UNO Name. such as ``com.sun.star.container.XIndexAccess``.
optional (bool, optional): Specifies if the import is optional. Defaults to ``False``.
init_kind (InitKind, int, optional): Init Option. Defaults to ``InitKind.COMPONENT_INTERFACE``.
check_kind (CheckKind, int, optional): Check Kind. Defaults to ``CheckKind.NONE``.
Returns:
BuildImportArg: BuildImportArg instance.
"""
arg = self.add_import(
name=name, uno_name=uno_name, optional=optional, init_kind=init_kind, check_kind=check_kind
)
_ = self._build_args.pop(arg)
self.insert_build_arg(idx, arg)
return arg
[docs] def has_import(self, name: str) -> bool:
"""
Check if the builder has an import.
Args:
name (str): Ooodev or UNO name such as ``ooodev.adapter.container.index_access_partial.IndexAccessPartial`` or ``com.sun.star.awt.XWindow``.
"""
ooo_dev_name = self._convert_to_ooodev(name)
return any(arg.ooodev_name == ooo_dev_name for arg in self._build_args.keys())
[docs] def has_omit(self, name: str) -> bool:
"""
Check if the builder has an omit.
Args:
name (str): Ooodev or UNO name such as ``ooodev.adapter.container.index_access_partial.IndexAccessPartial`` or ``com.sun.star.awt.XWindow``.
"""
ooo_dev_name = self._convert_to_ooodev(name)
return ooo_dev_name in self._omit
[docs] def add_import(
self,
name: str,
*,
uno_name: str | Tuple[str] = "",
optional: bool = False,
init_kind: InitKind | int = InitKind.COMPONENT_INTERFACE,
check_kind: CheckKind | int = CheckKind.NONE,
) -> BuildImportArg:
"""
Add an import to the builder.
Args:
name (str): Ooodev name such as ``ooodev.adapter.container.index_access_partial.IndexAccessPartial``.
uno_name (str, optional): UNO Name. such as ``com.sun.star.container.XIndexAccess``.
optional (bool, optional): Specifies if the import is optional. Defaults to ``False``.
init_kind (InitKind, int, optional): Init Option. Defaults to ``InitKind.COMPONENT_INTERFACE``.
check_kind (CheckKind, int, optional): Check Kind. Defaults to ``CheckKind.NONE``.
Returns:
BuildImportArg: BuildImportArg instance.
Note:
``init_kind`` can be an ``InitKind`` or an ``int``:
- NONE = 0
- COMPONENT = 1
- COMPONENT_INTERFACE = 2
``check_kind`` can be a ``CheckKind`` or an ``int``:
- NONE = 0
- SERVICE = 1
- INTERFACE = 2
- SERVICE_ALL = 3
- INTERFACE_ALL = 4
- SERVICE_ONLY = 5
- INTERFACE_ONLY = 6
"""
if isinstance(uno_name, str):
uname = uno_name.strip()
if uname:
uno_name = (uname,)
else:
uno_name = () # type: ignore
kind_init = InitKind(init_kind)
kind_check = CheckKind(check_kind)
bi = BuildImportArg(
ooodev_name=name.strip(),
uno_name=uno_name, # type: ignore
optional=optional,
init_kind=kind_init,
check_kind=kind_check,
)
self.add_build_arg(bi)
return bi
[docs] def insert_event(
self,
idx: int,
module_name: str,
class_name: str,
*,
callback_name: str = "on_lazy_cb",
uno_name: str | Tuple[str, ...] = "",
optional: bool = False,
check_kind: CheckKind | int = CheckKind.INTERFACE,
) -> BuildEventArg:
"""
Insert an event into the builder.
Args:
idx (int): The index to insert the import.
module_name (str): Ooodev name of the module such as ``ooodev.adapter.util.refresh_events``.
class_name (str):Ooodev class name such as ``RefreshEvents``.
callback_name (str): Callback name such as ``on_lazy_cb``.
uno_name (str, optional): UNO Name. such as ``com.sun.star.container.XIndexAccess``.
optional (bool, optional): Specifies if the import is optional. Defaults to ``False``.
check_kind (CheckKind, int, optional): Check Kind. Defaults to ``CheckKind.INTERFACE``.
Returns:
BuildImportArg: _description_
"""
arg = self.add_event(
module_name,
class_name,
callback_name=callback_name,
uno_name=uno_name,
optional=optional,
check_kind=check_kind,
)
_ = self._event_args.pop(arg)
self.insert_event_arg(idx, arg)
return arg
[docs] def add_event(
self,
module_name: str,
class_name: str,
*,
callback_name: str = "on_lazy_cb",
uno_name: str | Tuple[str, ...] = "",
optional: bool = False,
check_kind: CheckKind | int = CheckKind.INTERFACE,
) -> BuildEventArg:
"""
Add an event to the builder.
Args:
module_name (str): Ooodev name of the module such as ``ooodev.adapter.util.refresh_events``.
class_name (str):Ooodev class name such as ``RefreshEvents``.
callback_name (str): Callback name such as ``on_lazy_cb``.
uno_name (str, optional): UNO Name. such as ``com.sun.star.container.XIndexAccess``.
optional (bool, optional): Specifies if the import is optional. Defaults to ``False``.
check_kind (CheckKind, int, optional): Check Kind. Defaults to ``CheckKind.INTERFACE``.
Returns:
BuildImportArg: _description_
Note:
``check_kind`` can be a ``CheckKind`` or an ``int``:
- NONE = 0
- SERVICE = 1
- INTERFACE = 2
- SERVICE_ALL = 3
- INTERFACE_ALL = 4
- SERVICE_ONLY = 5
- INTERFACE_ONLY = 6
"""
if isinstance(uno_name, str):
uname = uno_name.strip()
if uname:
uno_name = (uname,)
else:
uno_name = () # type: ignore
kind_check = CheckKind(check_kind)
bi = BuildEventArg(
module_name=module_name.strip(),
class_name=class_name.strip(),
callback_name=callback_name.strip(),
uno_name=uno_name, # type: ignore
optional=optional,
check_kind=kind_check,
)
self.add_event_arg(bi)
return bi
[docs] def get_class_type(self, name: str, base_class: Type[Any], set_mod_name: bool = True) -> Type[Any]:
"""
Build the import.
Args:
name (str): Class Name. This can just be a class name or a full import name including the class.
When a full import name is passed then the last part is used as the class name and parts before are used as the module name.
init_kind (InitKind, int, optional): Init Option. Defaults to ``InitKind.COMPONENT``.
base_class (Type[Any], optional): Base Class. Defaults to ``BuilderBase``.
set_mod_name (bool, optional): Set the module name. Defaults to ``True``.
Returns:
Any: Class instance
Note:
``init_kind`` can be an ``InitKind`` or an ``int``:
- NONE = 0
- COMPONENT = 1
- COMPONENT_INTERFACE = 2
- CALLBACK = 3
- LO_INST = 4
"""
self._process_imports()
if "." in name:
mod, class_name = name.rsplit(".", 1)
else:
mod = ""
class_name = name
clz = self._generate_class(base_class, class_name)
if set_mod_name and mod:
with contextlib.suppress(Exception):
clz.__module__ = mod
return clz
[docs] def build_class(self, name: str, base_class: Type[Any], init_kind: InitKind | int = InitKind.COMPONENT) -> Any:
"""
Build the import.
Args:
name (str): Class Name. This can just be a class name or a full import name including the class.
When a full import name is passed then the last part is used as the class name and parts before are used as the module name.
init_kind (InitKind, int, optional): Init Option. Defaults to ``InitKind.COMPONENT``.
base_class (Type[Any], optional): Base Class. Defaults to ``BuilderBase``.
Returns:
Any: Class instance
Note:
``init_kind`` can be an ``InitKind`` or an ``int``:
- NONE = 0
- COMPONENT = 1
- COMPONENT_INTERFACE = 2
- CALLBACK = 3
- LO_INST = 4
"""
clz = self.get_class_type(name=name, base_class=base_class)
self.init_class_properties(clz)
inst = self._create_class(clz, InitKind(init_kind))
self.init_classes(inst)
return inst
[docs] def set_omit(self, *names: str) -> None:
"""
Set the names to omit.
This is useful with the base class already implements the class.
Args:
names (str): The CASE Sensitive names to omit.
Note:
Names can be the name of the ``ooodev`` full import name or the UNO name.
Valid names such as ``ooodev.adapter.container.name_access_partial.NameAccessPartial``
or ``com.sun.star.container.XNameAccess``.
"""
for name in names:
self._omit.add(self._convert_to_ooodev(name))
[docs] def get_import_names(self) -> List[str]:
"""Get the list of import names that have been added to the current instance."""
return [arg.ooodev_name for arg in self._build_args.keys()]
# region Class Properties
[docs] def add_class_property(self, name: str, value: Any) -> None:
"""
Set a property.
Args:
name (str): Property name.
value (Any): Property value.
"""
self._class_props[name] = value
[docs] def remove_class_property(self, name: str) -> bool:
"""
Remove a property.
Args:
name (str): Property name.
Returns:
bool: True if the property was removed; otherwise, False.
"""
if name in self._class_props:
del self._class_props[name]
return True
return False
[docs] def get_class_property(self, name: str, default: Any = None) -> Any:
"""
Get a property.
Args:
name (str): Property name.
default (Any, optional): Default value if the property is not found. Defaults to ``None``.
Returns:
Any: Property value.
"""
return self._class_props.get(name, default)
[docs] def init_class_properties(self, clz: Type[Any]) -> None:
"""Initialize the class properties."""
if not self._class_props:
return
for name, value in self._class_props.items():
eargs = EventArgs(self)
eargs.event_data = {"name": name, "value": value}
self.trigger_event("class_property_init", eargs)
if eargs.event_data:
name = eargs.event_data.get("name", name)
value = eargs.event_data.get("value", value)
setattr(clz, name, property(lambda self: value))
# endregion Class Properties
# region Events
[docs] def subscribe_class_properties_init(self, cb: EventCallback) -> None:
"""
Subscribe to the class init properties event.
Args:
cb (EventCallback): Callback function.
Note:
This event is triggered for each class property that is set.
The event data is a dictionary with keys ``name``, ``value``.
After the event is triggered, if the event data ``name`` or ``value`` is set then they will be used.
"""
self.subscribe_event("class_property_init", cb)
[docs] def subscribe_class_init(self, cb: EventCallback) -> None:
"""
Subscribe to the class init event.
Args:
cb (EventCallback): Callback function.
Note:
If the class init kind is ``InitKind.CALLBACK`` callbacks subscribed here will be called.
The event data is a dictionary with keys ``class``, ``kind``.
After the event is triggered, if the event data has keys ``args`` and ``kwargs`` then they will be passed to the class constructor.
"""
self.subscribe_event("class_init", cb)
[docs] def subscribe_class_event_init(self, cb: EventCallback) -> None:
"""
Subscribe to the class event init event.
Args:
cb (EventCallback): Callback function.
Note:
The event data is a dictionary with keys ``triggers``, ``data``.
After the event is triggered, if the event data ``triggers`` has key value pairs then they are passed to a
``GenericArgs`` and passed to the event as ``trigger_args``.
The default ``event_data["triggers"]`` contains key ``src_comp`` (source component)
and ``src_instance`` (current event class instance).
The default ``event_data["data"]`` contains key ``class`` (event class type), ``instance`` (current event class instance).
"""
self.subscribe_event("class_event_init", cb)
[docs] def subscribe_class_create(self, cb: EventCallback) -> None:
"""
Subscribe to the class create event.
Args:
cb (EventCallback): Callback function.
Note:
If the class init kind is ``InitKind.CALLBACK`` then an event is triggered.
The event data is a dictionary with keys ``class``, ``kind``.
After the event is triggered, if the event data has keys ``args`` and ``kwargs`` then they will be passed to the class constructor.
"""
self.subscribe_event("class_create", cb)
# endregion Events
# region Properties
@property
def component(self) -> Any:
return self._component
@property
def omits(self) -> Set[str]:
return self._omit
@property
def lo_inst(self) -> LoInst:
"""Gets/Sets Lo Instance"""
return self._LoInstPropsPartial__lo_inst
@lo_inst.setter
def lo_inst(self, value: LoInst) -> None:
self._LoInstPropsPartial__lo_inst = value
@property
def partial_excludes(self) -> Set[str]:
"""
This is the set of suffixes that are excluded from having ``_partial`` append to the class name.
These name should be in lower case such as ``_listener`` and ``_events``.
"""
return self._partial_excludes
# endregion Properties