Skip to content

Commit

Permalink
Refactor ayon_server modules to use the new AddonLibrary and `Rez…
Browse files Browse the repository at this point in the history
…Repo`

Modified all modules where the interface with `AddonLibrary` changed or
where it was now necessary to use the `RezRepo`.
  • Loading branch information
Minkiu committed Jan 29, 2024
1 parent c5222f5 commit 72e0e6a
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 311 deletions.
4 changes: 3 additions & 1 deletion ayon_server/addons/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
__all__ = ["AddonLibrary", "BaseServerAddon", "SSOOption"]
__all__ = ["AddonLibrary", "BaseServerAddon", "SSOOption", "RezRepo"]


from ayon_server.addons.addon import BaseServerAddon
from ayon_server.addons.library import AddonLibrary
from ayon_server.addons.models import SSOOption
from ayon_server.addons.rezrepo import RezRepo

26 changes: 17 additions & 9 deletions ayon_server/addons/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,46 @@
from ayon_server.settings import BaseSettingsModel, apply_overrides

if TYPE_CHECKING:
from ayon_server.addons.definition import ServerAddonDefinition
from rez.packages import Package


class BaseServerAddon:
name: str
version: str
title: str | None = None
app_host_name: str | None = None
definition: "ServerAddonDefinition"
rez_package: "Package"
endpoints: list[dict[str, Any]]
settings_model: Type[BaseSettingsModel] | None = None
site_settings_model: Type[BaseSettingsModel] | None = None
frontend_scopes: dict[str, Any] = {}
services: dict[str, Any] = {}
system: bool = False # Hide settings for non-admins and make the addon mandatory

def __init__(self, definition: "ServerAddonDefinition", addon_dir: str):
def __init__(self, rez_package: "Package", addon_dir: str):
assert self.name and self.version
self.definition = definition
self.rez_package = rez_package
self.addon_dir = addon_dir
self.endpoints = []
self.restart_requested = False
logging.info(f"Initializing addon {self.name} v{self.version} in {addon_dir}")
self.initialize()

def __repr__(self) -> str:
return f"<Addon name='{self.definition.name}' version='{self.version}'>"
return f"<Addon name='{self.rez_package.name}' version='{self.version}'>"

@property
def definition(self) -> "Package":
return self.rez_package

@property
def friendly_name(self) -> str:
"""Return the friendly name of the addon."""
return f"{self.definition.friendly_name} {self.version}"
try:
friendly_name = self.rez_package.friendly_name
except AttributeError:
friendly_name = self.name.capitalize()
return f"{friendly_name} {self.version}"

async def is_production(self) -> bool:
"""Return True if the addon is in production bundle."""
Expand Down Expand Up @@ -224,7 +232,7 @@ async def get_studio_overrides(self, variant: str = "production") -> dict[str, A
WHERE addon_name = $1 AND addon_version = $2 AND variant = $3
"""

res = await Postgres.fetch(query, self.definition.name, self.version, variant)
res = await Postgres.fetch(query, self.rez_package.name, self.version, variant)
if res:
return dict(res[0]["data"])
return {}
Expand All @@ -243,7 +251,7 @@ async def get_project_overrides(

try:
res = await Postgres.fetch(
query, self.definition.name, self.version, variant
query, self.rez_package.name, self.version, variant
)
except Postgres.UndefinedTableError:
raise NotFoundException(f"Project {project_name} does not exists") from None
Expand All @@ -265,7 +273,7 @@ async def get_project_site_overrides(
WHERE addon_name = $1 AND addon_version = $2
AND user_name = $3 AND site_id = $4
""",
self.definition.name,
self.rez_package.name,
self.version,
user_name,
site_id,
Expand Down
218 changes: 107 additions & 111 deletions ayon_server/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
from nxtools import log_to_file, log_traceback, logging, slugify

from ayon_server.access.access_groups import AccessGroups
from ayon_server.addons import get_enabled_addons
from ayon_server.addons.rezrepo import RezRepo
from ayon_server.addons import AddonLibrary, RezRepo
from ayon_server.api.messaging import Messaging
from ayon_server.api.metadata import app_meta, tags_meta
from ayon_server.api.responses import ErrorResponse
Expand Down Expand Up @@ -303,64 +302,57 @@ def init_api(target_app: fastapi.FastAPI, plugin_dir: str = "api") -> None:
route.operation_id = route.name


def init_addon_endpoints(target_app: fastapi.FastAPI) -> None:
# library = AddonLibrary.getinstance()
for addon in get_enabled_addons():
print(addon) # get_addon(addon)
# for addon_name, addon_definition in library.items():
# for version in addon_definition.versions:
# addon = addon_definition.versions[version]
# for endpoint in addon.endpoints:
# path = endpoint["path"].lstrip("/")
# first_element = path.split("/")[0]
# # TODO: site settings? other routes?
# if first_element in ["settings", "schema", "overrides"]:
# logging.error(f"Unable to assing path to endpoint: {path}")
# continue

# path = f"/api/addons/{addon_name}/{version}/{path}"
# target_app.add_api_route(
# path,
# endpoint["handler"],
# methods=[endpoint["method"]],
# name=endpoint["name"],
# tags=[f"{addon_definition.friendly_name} {version}"],
# operation_id=slugify(
# f"{addon_name}_{version}_{endpoint['name']}",
# separator="_",
# ),
# )


def init_addon_static(target_app: fastapi.FastAPI) -> None:
def init_addon_endpoints(target_app: fastapi.FastAPI, addons: list) -> None:
for addon_name, addon in addons.items():
for endpoint in addon.endpoints:
path = endpoint["path"].lstrip("/")
first_element = path.split("/")[0]
# TODO: site settings? other routes?
if first_element in ["settings", "schema", "overrides"]:
logging.error(f"Unable to assing path to endpoint: {path}")
continue

path = f"/api/addons/{addon_name}/{addon.version}/{path}"
target_app.add_api_route(
path,
endpoint["handler"],
methods=[endpoint["method"]],
name=endpoint["name"],
tags=[f"{addon.title} {addon>addon.version}"],
operation_id=slugify(
f"{addon.name}_{addon.version}_{endpoint['name']}",
separator="_",
),
)


def init_addon_static(target_app: fastapi.FastAPI, addons: list) -> None:
"""Serve static files for addon frontends."""
# for addon_name, addon_definition in AddonLibrary.items():
# for version in addon_definition.versions:
# addon = addon_definition.versions[version]
# static_dirs = []
# if (fedir := addon.get_frontend_dir()) is not None:
# static_dirs.append("frontend")
# target_app.mount(
# f"/addons/{addon_name}/{version}/frontend/",
# StaticFiles(directory=fedir, html=True),
# )
# if (resdir := addon.get_public_dir()) is not None:
# static_dirs.append("public")
# target_app.mount(
# f"/addons/{addon_name}/{version}/public/",
# StaticFiles(directory=resdir),
# )
# if (resdir := addon.get_private_dir()) is not None:
# static_dirs.append("private")
# target_app.mount(
# f"/addons/{addon_name}/{version}/private/",
# AuthStaticFiles(directory=resdir),
# )

# if static_dirs:
# logging.debug(
# f"Initialized static dirs for {addon_name}:{version}: {', '.join(static_dirs)}"
# )
for addon_name, addon in addons.items():
static_dirs = []
if (fedir := addon.get_frontend_dir()) is not None:
static_dirs.append("frontend")
target_app.mount(
f"/addons/{addon.name}/{addon.version}/frontend/",
StaticFiles(directory=fedir, html=True),
)
if (resdir := addon.get_public_dir()) is not None:
static_dirs.append("public")
target_app.mount(
f"/addons/{addon.name}/{addon.version}/public/",
StaticFiles(directory=resdir),
)
if (resdir := addon.get_private_dir()) is not None:
static_dirs.append("private")
target_app.mount(
f"/addons/{addon.name}/{addon.version}/private/",
AuthStaticFiles(directory=resdir),
)

if static_dirs:
logging.debug(
f"Initialized static dirs for {addon.name}:{addon.version}: {', '.join(static_dirs)}"
)


def init_frontend(target_app: fastapi.FastAPI, frontend_dir: str) -> None:
Expand Down Expand Up @@ -422,69 +414,72 @@ async def startup_event() -> None:
messaging.start()

# Initialize addons

start_event = await dispatch_event("server.started", finished=False)

library = AddonLibrary.getinstance()
addon_records = list(AddonLibrary.items())
if library.restart_requested:
logging.warning("Restart requested, skipping addon setup")
rezrepo = RezRepo.get_instance()

# In case we installed the `ayon_server`
if rezrepo.restart_requested:
await dispatch_event(
"server.restart_requested",
description="Server restart requested during addon initialization",
)
return
# addon_library = .get_instance()
initialized_addons, broken_addons = await AddonsLibrary.initialize_enabled_addons()

restart_requested = False
bad_addons = {}
for addon_name, addon in rezrepo.packages:
for version in addon.versions.values():
try:
if inspect.iscoroutinefunction(version.pre_setup):
# Since setup may, but does not have to be async, we need to
# silence mypy here.
await version.pre_setup() # type: ignore
else:
version.pre_setup()
if (not restart_requested) and version.restart_requested:
logging.warning(
f"Restart requested during addon {addon_name} pre-setup."
)
restart_requested = True
except Exception as e:
log_traceback(f"Error during {addon_name} {version.version} pre-setup")
reason = {
"error": str(e),
"traceback": traceback.format_exc(),
}
bad_addons[(addon_name, version.version)] = reason

for addon_name, addon in addon_records:
for version in addon.versions.values():
try:
if inspect.iscoroutinefunction(version.setup):
await version.setup()
else:
version.setup()
if (not restart_requested) and version.restart_requested:
logging.warning(
f"Restart requested during addon {addon_name} setup."
)
restart_requested = True
except Exception as e:
log_traceback(f"Error during {addon_name} {version.version} setup")
reason = {
"error": str(e),
"traceback": traceback.format_exc(),
}
bad_addons[(addon_name, version.version)] = reason

async def _run_addon_method(addon, method_name):
method_object = getattr(addon, method_name, False)

try:
if inspect.iscoroutinefunction(method_object):
# Since setup may, but does not have to be async, we need to
# silence mypy here.
await method_object() # type: ignore
else:
method_object()
except Exception as e:
log_traceback(f"Error during {addon} pre-setup")
reason = {
"error": str(e),
"traceback": traceback.format_exc(),
}
return (False, reason)

return (True, None)

# Perform Addon's `pre_setup` method
for addon_name, addon_class in initialized_addons.items():
ran, result = await _run_addon_method(addon_class, "pre_setup")

if not ran:
bad_addons[(addon_class.name, addon_class.version)] = result

if (not restart_requested) and addon_class.restart_requested:
logging.warning(
f"Restart requested during addon {addon_name}-{addon_class.version} setup."
)
restart_requested = True

# Perform Addon's `pre_setup` method
for addon_name, addon_class in initialized_addons.items():
ran, result = await _run_addon_method(addon_class, "setup")

if not ran:
bad_addons[(addon_class.name, addon_class.version)] = result

if (not restart_requested) and addon_class.restart_requested:
logging.warning(
f"Restart requested during addon {addon_name}-{addon_class.version} setup."
)
restart_requested = True

for _addon_name, _addon_version in bad_addons:
logging.error(
f"Addon {_addon_name} {_addon_version} failed to initialize. Unloading."
f"Addon {_addon_name} {_addon_version} failed to initialize."
)
reason = bad_addons[(_addon_name, _addon_version)]
library.unload_addon(_addon_name, _addon_version, reason=reason)

if restart_requested:
await dispatch_event(
Expand All @@ -493,10 +488,10 @@ async def startup_event() -> None:
)
else:
# Initialize endpoints for active addons
init_addon_endpoints(app)
init_addon_endpoints(app, initialized_addons)

# Addon static dirs must stay exactly here
init_addon_static(app)
init_addon_static(app, initialized_addons)

# Frontend must be initialized last (since it is mounted to /)
init_frontend(app, ayonconfig.frontend_dir)
Expand All @@ -519,3 +514,4 @@ async def shutdown_event() -> None:
await messaging.shutdown()
await Postgres.shutdown()
logging.info("Server stopped", handlers=None)

6 changes: 3 additions & 3 deletions ayon_server/installer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from ayon_server.background.background_worker import BackgroundWorker
from ayon_server.events import update_event
from ayon_server.installer.addons import install_addon_from_url, unpack_addon
from ayon_server.addons import AddonLibrary
from ayon_server.installer.dependency_packages import download_dependency_package
from ayon_server.installer.installers import download_installer
from ayon_server.lib.postgres import Postgres
Expand Down Expand Up @@ -48,15 +48,15 @@ async def process_event(self, event_id: str):
logging.info(f"Background installer: processing {topic} event: {event_id}")

if topic == "addon.install":
await unpack_addon(
await AddonLibrary.install_addon(
event_id,
summary["zip_path"],
summary["addon_name"],
summary["addon_version"],
)

elif topic == "addon.install_from_url":
await install_addon_from_url(event_id, summary["url"])
await AddonLibrary.install_addon_from_url(event_id, summary["url"])

elif topic == "dependency_package.install_from_url":
await download_dependency_package(event_id, summary["url"])
Expand Down
Loading

0 comments on commit 72e0e6a

Please sign in to comment.