본문 바로가기
Flow Production Tracking/Python api

Handling Action Menu Item Calls

by 르면가게 2024. 12. 25.

주요 기능

  • GET vs POST:
    • ActionMenuItem이 HTTP/HTTPS로 데이터를 전송하는 경우 POST 방식 사용.
    • GET 방식은 사용자 지정 프로토콜(custom protocol)에서 사용되며, 브라우저에서 URL 길이 제한에 유의해야 합니다.
  • 클래스 개요:
    • ShotgunAction 클래스는 URL을 프로토콜, 액션, 파라미터로 분리하여 변수에 저장.
    • GET 요청의 파라미터를 Python 딕셔너리로 파싱.

주요 코드 설명

# ---------------------------------------------------------------------------------------------
# Imports
# ---------------------------------------------------------------------------------------------
import sys, os
import six
import logging as logger

# ---------------------------------------------------------------------------------------------
# Variables
# ---------------------------------------------------------------------------------------------
# location to write logfile for this script
# logging is a bit of overkill for this class, but can still be useful.
logfile = os.path.dirname(sys.argv[0]) + "/shotgun_action.log"

# ----------------------------------------------
# Generic ShotgunAction Exception Class
# ----------------------------------------------
class ShotgunActionException(Exception):
    pass

# ----------------------------------------------
# ShotgunAction Class to manage ActionMenuItem call
# ----------------------------------------------
class ShotgunAction:
    def __init__(self, url):
        self.logger = self._init_log(logfile)
        self.url = url
        self.protocol, self.action, self.params = self._parse_url()

        # entity type that the page was displaying
        self.entity_type = self.params["entity_type"]

        # Project info (if the ActionMenuItem was launched from a page not belonging
        # to a Project (Global Page, My Page, etc.), this will be blank
        if "project_id" in self.params:
            self.project = {
                "id": int(self.params["project_id"]),
                "name": self.params["project_name"],
            }
        else:
            self.project = None

        # Internal column names currently displayed on the page
        self.columns = self.params["cols"]

        # Human readable names of the columns currently displayed on the page
        self.column_display_names = self.params["column_display_names"]

        # All ids of the entities returned by the query (not just those visible on the page)
        self.ids = []
        if len(self.params["ids"]) > 0:
            ids = self.params["ids"].split(",")
            self.ids = [int(id) for id in ids]

        # All ids of the entities returned by the query in filter format ready
        # to use in a find() query
        self.ids_filter = self._convert_ids_to_filter(self.ids)

        # ids of entities that were currently selected
        self.selected_ids = []
        if len(self.params["selected_ids"]) > 0:
            sids = self.params["selected_ids"].split(",")
            self.selected_ids = [int(id) for id in sids]

        # All selected ids of the entities returned by the query in filter format ready
        # to use in a find() query
        self.selected_ids_filter = self._convert_ids_to_filter(self.selected_ids)

        # sort values for the page
        # (we don't allow no sort anymore, but not sure if there's legacy here)
        if "sort_column" in self.params:
            self.sort = {
                "column": self.params["sort_column"],
                "direction": self.params["sort_direction"],
            }
        else:
            self.sort = None

        # title of the page
        self.title = self.params["title"]

        # user info who launched the ActionMenuItem
        self.user = {"id": self.params["user_id"], "login": self.params["user_login"]}

        # session_uuid
        self.session_uuid = self.params["session_uuid"]

    # ----------------------------------------------
    # Set up logging
    # ----------------------------------------------
    def _init_log(self, filename="shotgun_action.log"):
        try:
            logger.basicConfig(
                level=logger.DEBUG,
                format="%(asctime)s %(levelname)-8s %(message)s",
                datefmt="%Y-%b-%d %H:%M:%S",
                filename=filename,
                filemode="w+",
            )
        except IOError as e:
            raise ShotgunActionException("Unable to open logfile for writing: %s" % e)
        logger.info("ShotgunAction logging started.")
        return logger

        # ----------------------------------------------

    # Parse ActionMenuItem call into protocol, action and params
    # ----------------------------------------------
    def _parse_url(self):
        logger.info("Parsing full url received: %s" % self.url)

        # get the protocol used
        protocol, path = self.url.split(":", 1)
        logger.info("protocol: %s" % protocol)

        # extract the action
        action, params = path.split("?", 1)
        action = action.strip("/")
        logger.info("action: %s" % action)

        # extract the parameters
        # 'column_display_names' and 'cols' occurs once for each column displayed so we store it as a list
        params = params.split("&")
        p = {"column_display_names": [], "cols": []}
        for arg in params:
            key, value = map(six.moves.urllib.parse.unquote, arg.split("=", 1))
            if key == "column_display_names" or key == "cols":
                p[key].append(value)
            else:
                p[key] = value
        params = p
        logger.info("params: %s" % params)
        return (protocol, action, params)

    # ----------------------------------------------
    # Convert IDs to filter format to us in find() queries
    # ----------------------------------------------
    def _convert_ids_to_filter(self, ids):
        filter = []
        for id in ids:
            filter.append(["id", "is", id])
        logger.debug("parsed ids into: %s" % filter)
        return filter

# ----------------------------------------------
# Main Block
# ----------------------------------------------
if __name__ == "__main__":
    try:
        sa = ShotgunAction(sys.argv[1])
        logger.info("ShotgunAction: Firing... %s" % (sys.argv[1]))
    except IndexError as e:
        raise ShotgunActionException("Missing GET arguments")
    logger.info("ShotgunAction process finished.")

URL 파싱

  • URL 구조:
    • 프로토콜: myCoolProtocol
    • 액션: doSomethingCool
    • 파라미터: user_id=123, user_login=miled, title=All Versions
  • myCoolProtocol://doSomethingCool?user_id=123&user_login=miled&title=All%20Versions
  • 파싱된 결과:
    • protocol: myCoolProtocol
    • action: doSomethingCool
    • params: { 'user_id': '123', 'user_login': 'miled', 'title': 'All Versions' }

파라미터 접근

  • 사용자 로그인:
sa.params['user_login'] # 'miled'
  • 사용자 ID:
sa.params['user_id'] # 123

ShotgunAction 클래스 주요 속성

초기화:

  • URL 파싱 및 변수 설정:
sa = ShotgunAction(url)

속성 설명:

  • protocol: URL의 프로토콜 (예: myCoolProtocol).
  • action: 수행할 액션 이름.
  • params: URL에서 파싱한 파라미터 딕셔너리.
  • project: 프로젝트 ID 및 이름.
  • columns: 현재 페이지의 내부 컬럼 이름.
  • selected_ids: 선택된 엔터티의 ID 목록.
  • sort: 정렬 기준 (열과 방향).
  • user: 호출한 사용자 정보.

로깅(logging)

  • 로그 파일 경로 설정:
logfile = os.path.dirname(sys.argv[0]) + "/shotgun_action.log"
  • 로깅 예제:
logger.info("ShotgunAction: Firing...")

필터 변환

  • ID 목록을 API의 find() 쿼리에 사용할 수 있도록 변환:
def _convert_ids_to_filter(self, ids):
    filter = []
    for id in ids:
        filter.append(["id", "is", id])
    return filter

사용 예제

메인 블록:

  • URL을 인자로 받아 처리:
if __name__ == "__main__":
    sa = ShotgunAction(sys.argv[1])  # sys.argv[1]에 URL 전달

액션 메뉴 아이템을 호출 했을 때 주는 url을 이용하는 방법을 알려주는 것 같음 실제로 사용해야 이해가 더 잘 될 것 같다.

 

https://developers.shotgridsoftware.com/python-api/cookbook/examples/ami_handler.html

 

Handling Action Menu Item Calls — python-api v3.7.0 documentation

This is an example ActionMenu Python class to handle the GET request sent from an ActionMenuItem. It doesn’t manage dispatching custom protocols but rather takes the arguments from any GET data and parses them into the easily accessible and correctly typ

developers.shotgridsoftware.com

 

'Flow Production Tracking > Python api' 카테고리의 다른 글

Python API Best Practices  (0) 2024.12.29
Python API Overview  (0) 2024.12.29
API Usage Tips  (1) 2024.12.25
API Reference(2)  (0) 2024.12.25
API Reference(1)  (1) 2024.12.25