Source code for zafiaonline.service_generator.helper
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2025 unelected
#
# This file is part of the zafiaonline project.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
Factory for creating lazily-loaded submodule properties.
This module provides a helper function :func:`make_submodule_property`
that allows dynamic and lazy initialization of client submodules. It is
primarily used inside the `Client` class of the `zafiaonline` package to
instantiate API submodules (e.g., authentication, rooms, matchmaking)
only when they are accessed.
The design follows a combination of the "Lazy Initialization" and
"Factory" patterns. Dependencies between submodules are resolved through
callable factories, ensuring that the correct references are injected at
initialization time.
Example:
from zafiaonline.main import Client
client = Client()
client.auth # lazily instantiates AuthService
client.room # lazily instantiates RoomMethods, injecting auth
Typical usage within Client:
auth = make_submodule_property(
"auth", "user_methods", "AuthService", client=lambda self: self
)
room = make_submodule_property(
"room", "room_methods", "RoomMethods", auth_client=lambda self: self.auth
)
"""
from typing import Any, Callable
[docs]
def make_submodule_property(
attr: str,
module_name: str,
class_name: str,
**init_factories: Callable,
) -> property:
"""
Create a lazily-initialized submodule property.
This function returns a property object that, when accessed,
imports a submodule dynamically, instantiates a target class,
and injects required dependencies. The created instance is
cached by the client to avoid repeated initialization.
Args:
attr (str): Attribute name used as the cache key.
module_name (str): Name of the submodule inside
``zafiaonline.api_client``.
class_name (str): Target class name within the submodule.
**init_factories (Callable): Keyword arguments passed to the
class constructor. Each value can be:
- a callable (e.g., ``lambda self: self.auth``) that will
be evaluated with the client instance.
- a static value passed directly.
Returns:
property: A property descriptor that lazily initializes and
returns the requested submodule instance.
Raises:
AttributeError: If a required dependency cannot be resolved
(e.g., accessing a submodule before its dependency exists).
ImportError: If the requested submodule cannot be imported.
"""
def _getter(self) -> object:
"""
Retrieve or initialize the submodule instance.
This getter is used internally by ``make_submodule_property`` to lazily
construct and cache submodules on first access. It builds the keyword
arguments from the provided init factories, then delegates submodule
loading to :meth:`Client._import_submodule`.
Returns:
object: The cached or newly created submodule instance.
Raises:
RuntimeError: If building kwargs from init factories fails, or if
the submodule class fails to initialize.
ImportError: If the target module or class cannot be imported.
"""
try:
kwargs: dict[str, Any] = {
kwarg: (factory(self) if callable(factory) else factory)
for kwarg, factory in init_factories.items()
}
except Exception as e:
raise RuntimeError(
f"Failed to build kwargs for submodule '{class_name}' "
f"(attr='{attr}')"
) from e
try:
return self._import_submodule(
attr,
module_name,
class_name,
**kwargs
)
except ImportError as e:
raise ImportError(
f"Could not import submodule '{module_name}.{class_name}' "
f"for attribute '{attr}'"
) from e
except Exception as e:
raise RuntimeError(
f"Failed to initialize submodule '{class_name}' "
f"from '{module_name}'"
) from e
return property(_getter)