Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asset renaming synchronization #102

Merged
merged 12 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/ayon_shotgrid/version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring shotgrid addon version."""
__version__ = "0.4.2-dev.3"
__version__ = "0.4.2-dev.5"
2 changes: 1 addition & 1 deletion package.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "shotgrid"
title = "Shotgrid"
version = "0.4.2-dev.3"
version = "0.4.2-dev.5"
client_dir = "ayon_shotgrid"

services = {
Expand Down
15 changes: 6 additions & 9 deletions service_tools/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,19 @@ def main():
sys.path.insert(0, path)

if service_name == "processor":
from processor import ShotgridProcessor
from processor import service_main

shotgrid_processor = ShotgridProcessor()
sys.exit(shotgrid_processor.start_processing())
service_main()

elif service_name == "leecher":
from leecher import ShotgridListener
from leecher import service_main

shotgrid_listener = ShotgridListener()
sys.exit(shotgrid_listener.start_listening())
service_main()

else:
from transmitter import ShotgridTransmitter
from transmitter import service_main

shotgrid_transmitter = ShotgridTransmitter()
sys.exit(shotgrid_transmitter.start_processing())
service_main()


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions services/leecher/leecher/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .listener import ShotgridListener
from .listener import ShotgridListener, service_main


__all__ = (
"service_main",
"ShotgridListener",
)

8 changes: 2 additions & 6 deletions services/leecher/leecher/__main__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import sys

from .listener import ShotgridListener
from .listener import service_main


if __name__ == "__main__":
shotgrid_listener = ShotgridListener()
sys.exit(shotgrid_listener.start_listening())

service_main()
55 changes: 33 additions & 22 deletions services/leecher/leecher/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import time
import signal
import socket
from pprint import pformat
import traceback
from typing import Any
from pprint import pformat

from utils import get_logger

Expand All @@ -36,7 +37,6 @@ def __init__(self):
self.log.info("Initializing the Shotgrid Listener.")

try:
ayon_api.init_service()
self.settings = ayon_api.get_service_addon_settings()
service_settings = self.settings["service_settings"]

Expand All @@ -47,6 +47,14 @@ def __init__(self):
# get server op related ShotGrid script api properties
shotgrid_secret = ayon_api.get_secret(
service_settings["script_key"])

if isinstance(shotgrid_secret, list):
raise ValueError(
"Shotgrid API Key not found. Make sure to set it in the "
"Addon System settings. "
"`ayon+settings://shotgrid/service_settings/script_key`"
)

self.sg_api_key = shotgrid_secret.get("value")
if not self.sg_api_key:
raise ValueError(
Expand Down Expand Up @@ -129,19 +137,19 @@ def _build_shotgrid_filters(self, sg_projects):

filters.append(["project", "in", sg_projects])

sg_event_types = []

# TODO: Create a complex filter so skip event types "_Change" that
# we don't handle.
for entity_type in self.sg_enabled_entities:
for event_name in SG_EVENT_TYPES:
sg_event_types.append(event_name.format(entity_type))

if sg_event_types:
if sg_event_types := self._get_supported_event_types():
filters.append(["event_type", "in", sg_event_types])

return filters

def _get_supported_event_types(self) -> list[str]:
sg_event_types = []
for entity_type in self.sg_enabled_entities:
sg_event_types.extend(
event_name.format(entity_type) for event_name in SG_EVENT_TYPES
)
return sg_event_types

def _get_last_event_processed(self, sg_filters):
"""Find the Event ID for the last SG processed event.

Expand Down Expand Up @@ -169,14 +177,6 @@ def _get_last_event_processed(self, sg_filters):

return last_event_id

def _get_supported_event_types(self) -> list[str]:
sg_event_types = []
for entity_type in self.sg_enabled_entities:
sg_event_types.extend(
event_name.format(entity_type) for event_name in SG_EVENT_TYPES
)
return sg_event_types

def start_listening(self):
"""Main loop querying the Shotgrid database for new events

Expand Down Expand Up @@ -259,10 +259,14 @@ def start_listening(self):

self.send_shotgrid_event_to_ayon(event, sg_projects_by_id)

except Exception as err:
self.log.error(err, exc_info=True)
except Exception:
self.log.error(traceback.format_exc())

self.log.debug(
f"Leecher waiting {self.shotgrid_polling_frequency} seconds..."
)
time.sleep(self.shotgrid_polling_frequency)
continue

def _is_api_user_event(self, event: dict[str, Any]) -> bool:
"""Check if the event was caused by an API user.
Expand Down Expand Up @@ -297,7 +301,8 @@ def send_shotgrid_event_to_ayon(
# fix non serializable datetime
payload["created_at"] = payload["created_at"].isoformat()

if payload.get("meta", {}).get("entity_type", "Undefined") == "Project":
payload_meta = payload.get("meta", {})
if payload_meta.get("entity_type", "Undefined") == "Project":
project_name = payload.get("entity", {}).get("name", "Undefined")
project_id = payload.get("entity", {}).get("id", "Undefined")
else:
Expand Down Expand Up @@ -325,3 +330,9 @@ def send_shotgrid_event_to_ayon(
)

self.log.info("Dispatched Ayon event with payload:", payload)


def service_main():
ayon_api.init_service()
shotgrid_listener = ShotgridListener()
sys.exit(shotgrid_listener.start_listening())
2 changes: 1 addition & 1 deletion services/leecher/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "shotgrid-leecher"
version = "0.4.2-dev.3"
version = "0.4.2-dev.5"
description = "Shotgrid Integration for Ayon"
authors = ["Oscar Domingo <[email protected]>"]

Expand Down
7 changes: 5 additions & 2 deletions services/processor/processor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from .processor import ShotgridProcessor
from .processor import (
ShotgridProcessor,
service_main,
)


__all__ = (
"ShotgridProcessor",
"service_main",
)

7 changes: 3 additions & 4 deletions services/processor/processor/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import sys
from .processor import ShotgridProcessor
from .processor import service_main


if __name__ == "__main__":
shotgrid_processor = ShotgridProcessor()
sys.exit(shotgrid_processor.start_processing())
service_main()
12 changes: 5 additions & 7 deletions services/processor/processor/handlers/shotgrid_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,25 @@

def process_event(
sg_processor,
**kwargs,
event,
):
"""React to Shotgrid Events.

Events with the action `shotgrid-event` will trigger this
function, where we attempt to replicate a change coming form Shotgrid, like
creating a new Shot, renaming a Task, etc.
"""
sg_payload = kwargs.get("sg_payload", {})
sg_payload = event.get("sg_payload", {})
if not sg_payload:
raise ValueError("The Event payload is empty!")

if not sg_payload.get("meta", {}):
raise ValueError("The Event payload is missing the action to perform!")

hub = AyonShotgridHub(
kwargs.get("project_name"),
kwargs.get("project_code"),
sg_processor.sg_url,
sg_processor.sg_api_key,
sg_processor.sg_script_name,
sg_processor.get_sg_connection(),
event.get("project_name"),
event.get("project_code"),
sg_project_code_field=sg_processor.sg_project_code_field,
custom_attribs_map=sg_processor.custom_attribs_map,
custom_attribs_types=sg_processor.custom_attribs_types,
Expand Down
13 changes: 5 additions & 8 deletions services/processor/processor/handlers/sync_projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

def process_event(
sg_processor,
**kwargs,
event,
):
"""Synchronize a project between AYON and Shotgrid.

Expand All @@ -18,11 +18,9 @@ def process_event(
Shotgrid or AYON, and replicate it's structure in the other platform.
"""
hub = AyonShotgridHub(
kwargs.get("project_name"),
kwargs.get("project_code"),
sg_processor.sg_url,
sg_processor.sg_api_key,
sg_processor.sg_script_name,
sg_processor.get_sg_connection(),
event.get("project_name"),
event.get("project_code"),
sg_project_code_field=sg_processor.sg_project_code_field,
custom_attribs_map=sg_processor.custom_attribs_map,
custom_attribs_types=sg_processor.custom_attribs_types,
Expand All @@ -32,6 +30,5 @@ def process_event(
# This will ensure that the project exists in both platforms.
hub.create_project()
sync_source = (
"ayon" if kwargs.get("action") == "sync-from-ayon" else "shotgrid"
)
"ayon" if event.get("action") == "sync-from-ayon" else "shotgrid")
hub.synchronize_projects(source=sync_source)
51 changes: 49 additions & 2 deletions services/processor/processor/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@
related events.
"""
import os
import sys
from pprint import pformat
import time
import types
import socket
import importlib.machinery
import traceback

import ayon_api
import shotgun_api3

from utils import get_logger


class ShotgridProcessor:

_sg: shotgun_api3.Shotgun = None
log = get_logger(__file__)

def __init__(self):
Expand Down Expand Up @@ -54,6 +57,14 @@ def __init__(self):
# get server op related ShotGrid script api properties
shotgrid_secret = ayon_api.get_secret(
service_settings["script_key"])

if isinstance(shotgrid_secret, list):
raise ValueError(
"Shotgrid API Key not found. Make sure to set it in the "
"Addon System settings. "
"`ayon+settings://shotgrid/service_settings/script_key`"
)

self.sg_api_key = shotgrid_secret.get("value")
if not self.sg_api_key:
raise ValueError(
Expand Down Expand Up @@ -136,6 +147,33 @@ def _get_handlers(self):

return handlers_dict

def get_sg_connection(self):
"""Ensure we can talk to AYON and Shotgrid.

Start connections to the APIs and catch any possible error, we abort if
this steps fails for any reason.
"""

if self._sg is None:
try:
self._sg = shotgun_api3.Shotgun(
self.sg_url,
script_name=self.sg_script_name,
api_key=self.sg_api_key
)
except Exception as e:
self.log.error("Unable to create Shotgrid Session.")
raise e

try:
self._sg.connect()

except Exception as e:
self.log.error("Unable to connect to Shotgrid.")
raise e

return self._sg

def start_processing(self):
"""Enroll AYON events of topic `shotgrid.event`

Expand Down Expand Up @@ -192,9 +230,11 @@ def start_processing(self):
),
status="finished"
)
self.log.debug(
f"processing event {pformat(payload)}")
handler.process_event(
self,
**payload,
payload,
)

except Exception:
Expand Down Expand Up @@ -231,3 +271,10 @@ def start_processing(self):

except Exception:
self.log.error(traceback.format_exc())


def service_main():
ayon_api.init_service()

shotgrid_processor = ShotgridProcessor()
sys.exit(shotgrid_processor.start_processing())
2 changes: 1 addition & 1 deletion services/processor/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "shotgrid-processor"
version = "0.4.2-dev.3"
version = "0.4.2-dev.5"
description = "Shotgrid Integration for Ayon"
authors = ["Oscar Domingo <[email protected]>"]

Expand Down
Loading