diff --git a/README.md b/README.md index 3624ac8f..87257da2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Python 2.6 2.7 3.7](https://img.shields.io/badge/python-2.6%20%7C%202.7%20%7C%203.7-blue.svg)](https://www.python.org/) [![Build Status](https://dev.azure.com/shotgun-ecosystem/Toolkit/_apis/build/status/shotgunEvents?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Toolkit/_build/latest?definitionId=89&branchName=master) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Linting](https://img.shields.io/badge/PEP8%20by-Hound%20CI-a873d1.svg)](https://houndci.com) diff --git a/azure_pipelines.yml b/azure_pipelines.yml new file mode 100644 index 00000000..2d82aac5 --- /dev/null +++ b/azure_pipelines.yml @@ -0,0 +1,44 @@ +# Copyright (c) 2020 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +# Imports the shared Azure CI tools from the master branch of shotgunsoftware/tk-ci-tools +resources: + repositories: + - repository: templates + type: github + name: shotgunsoftware/tk-ci-tools + ref: refs/heads/master + endpoint: shotgunsoftware + +# We want builds to trigger for 3 reasons: +# - The master branch sees new commits +# - Each PR should get rebuilt when commits are added to it. +# - When we tag something +trigger: + branches: + include: + - master + tags: + include: + - v* +pr: + branches: + include: + - "*" + +# This pulls in a variable group from Azure. Variables can be encrypted or not. +variables: +- group: deploy-secrets + +# Launch into the build pipeline. +jobs: +- template: build-pipeline.yml@templates + parameters: + skip_tests: true diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..19770fb4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +# Copyright 2020 Autodesk, Inc. All rights reserved. +# +# Use of this software is subject to the terms of the Autodesk license agreement +# provided at the time of installation or download, or which otherwise accompanies +# this software in either electronic or hard copy form. +# +# List of Python packages needed by this tool + +# Used for Python2/3 compatibility +six diff --git a/src/daemonizer.py b/src/daemonizer.py index 62396230..fb189603 100644 --- a/src/daemonizer.py +++ b/src/daemonizer.py @@ -69,19 +69,20 @@ def _daemonize(self): # redirect standard file descriptors sys.stdout.flush() sys.stderr.flush() - si = file(self._stdin, "r") - so = file(self._stdout, "a+") - se = file(self._stderr, "a+", 0) + si = open(self._stdin, "r") + so = open(self._stdout, "a+") + se = open(self._stderr, "a+", 0) os.dup2(si.fileno(), sys.stdin.fileno()) os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) # write pidfile and subsys file pid = str(os.getpid()) - file(self._pidfile, "w+").write("%s\n" % pid) + with open(self._pidfile, "w+") as f: + f.write("%s\n" % pid) if os.path.exists("/var/lock/subsys"): - fh = open(os.path.join("/var/lock/subsys", self._serviceName), "w") - fh.close() + with open(os.path.join("/var/lock/subsys", self._serviceName), "w") as f: + pass def _delpid(self): if os.path.exists(self._pidfile): @@ -99,9 +100,8 @@ def start(self, daemonize=True): """ # Check for a pidfile to see if the daemon already runs try: - pf = file(self._pidfile, "r") - pid = int(pf.read().strip()) - pf.close() + with open(self._pidfile, "r") as pf: + pid = int(pf.read().strip()) except IOError: pid = None @@ -130,9 +130,8 @@ def stop(self): """ # Get the pid from the pidfile try: - pf = file(self._pidfile, "r") - pid = int(pf.read().strip()) - pf.close() + with open(self._pidfile, "r") as pf: + pid = int(pf.read().strip()) except IOError: pid = None diff --git a/src/examplePlugins/calc_field.py b/src/examplePlugins/calc_field.py index 045a3c49..5504f623 100644 --- a/src/examplePlugins/calc_field.py +++ b/src/examplePlugins/calc_field.py @@ -170,7 +170,7 @@ def is_valid(sg, logger, args): ) return - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]) diff --git a/src/examplePlugins/calc_summaries.py b/src/examplePlugins/calc_summaries.py index b234d4ee..83190620 100644 --- a/src/examplePlugins/calc_summaries.py +++ b/src/examplePlugins/calc_summaries.py @@ -95,7 +95,7 @@ def is_valid(sg, logger, args): } # Check our args. - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]) @@ -138,7 +138,11 @@ def is_valid(sg, logger, args): # Make sure the field we want to summarize is on our entity. if not check_entity_schema( - sg, summary_item["entity_type"], summary_item["field"], valid_fields, + sg, + logger, + summary_item["entity_type"], + summary_item["field"], + valid_fields, ): return @@ -170,7 +174,9 @@ def is_valid(sg, logger, args): ) # Grab the data type for the linked field. - data_type = link_field_schema[link_field_schema.keys()[0]]["data_type"]["value"] + data_type = link_field_schema[list(link_field_schema.keys())[0]]["data_type"][ + "value" + ] # Bail if we don't have a single-entity field. if data_type != "entity": @@ -185,9 +191,9 @@ def is_valid(sg, logger, args): return # Get the valid entity types for the linked field. - entity_links = link_field_schema[link_field_schema.keys()[0]]["properties"][ - "valid_types" - ]["value"] + entity_links = link_field_schema[list(link_field_schema.keys())[0]][ + "properties" + ]["valid_types"]["value"] # Bail if we have more than one valid type. Things get a bit crazy if we # have to check for fields on more than one entity type that may or may @@ -223,6 +229,7 @@ def is_valid(sg, logger, args): # Can check the linked entity_type field schema for field_to_update. if not check_entity_schema( sg, + logger, summarize_entity_type, args["field_to_update"], ["number", "float", "currency", "text", "percent"], @@ -232,11 +239,12 @@ def is_valid(sg, logger, args): return True -def check_entity_schema(sg, entity_type, field_name, field_type=None): +def check_entity_schema(sg, logger, entity_type, field_name, field_type=None): """ Verifies that field_name of field_type exists in entity_type's schema. :param sg: An authenticated Shotgun Python API instance. + :param logger: Logger instance. :param entity_type: String, a Shotgun entity type. :param field_name: String, the name of a field on entity_type. :param field_type: List of strings, the Shotgun field type field_name should be. diff --git a/src/examplePlugins/calculate_cut_length.py b/src/examplePlugins/calculate_cut_length.py index 8e247a74..dc661fde 100644 --- a/src/examplePlugins/calculate_cut_length.py +++ b/src/examplePlugins/calculate_cut_length.py @@ -68,8 +68,6 @@ def is_valid(sg, logger, args): :returns: True if plugin is valid, None if not. """ - logger.info("hey!") - args_to_check = { "fps": {"type": ["float"]}, "cut_in_field": {"sg_type": ["number"], "type": ["str"]}, @@ -88,7 +86,7 @@ def is_valid(sg, logger, args): ) return - for name, type_targets in args_to_check.iteritems(): + for name, type_targets in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]).__name__ diff --git a/src/examplePlugins/convert_currency.py b/src/examplePlugins/convert_currency.py index 75b518c9..a66e6f1b 100644 --- a/src/examplePlugins/convert_currency.py +++ b/src/examplePlugins/convert_currency.py @@ -88,7 +88,7 @@ def is_valid(sg, logger, args): % (args["entity_type"], e) ) - for name, type_target in args_to_check.iteritems(): + for name, type_target in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]) diff --git a/src/examplePlugins/create_note_from_version_field.py b/src/examplePlugins/create_note_from_version_field.py index 98e1a378..b9568255 100644 --- a/src/examplePlugins/create_note_from_version_field.py +++ b/src/examplePlugins/create_note_from_version_field.py @@ -62,7 +62,7 @@ def is_valid(sg, logger, args): "author_is_artist": {"type": [bool], "allow_empty": False}, } - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Bail if we're missing any required args. try: diff --git a/src/examplePlugins/datestamp.py b/src/examplePlugins/datestamp.py index 4a77d5c4..acd6c749 100644 --- a/src/examplePlugins/datestamp.py +++ b/src/examplePlugins/datestamp.py @@ -155,7 +155,7 @@ def is_valid(sg, logger, args): "set_date_on_entity_creation": {"type": [bool], "allow_empty": False}, } - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]) diff --git a/src/examplePlugins/docs/task_status_update_version_status.md b/src/examplePlugins/docs/task_status_update_version_status.md index 83448c98..049d6c17 100644 --- a/src/examplePlugins/docs/task_status_update_version_status.md +++ b/src/examplePlugins/docs/task_status_update_version_status.md @@ -2,7 +2,7 @@ When a HumanUser updates a Task's Status, find the latest Version in Shotgun whose Task matches the relevant Task, and update that Version's Status to the -value defined defined by the Status' `status_mapping_field`. If the +value defined by the Status' `status_mapping_field`. If the `status_mapping_field` value is blank or not present on the Version, do not update the Version Status field. diff --git a/src/examplePlugins/field_to_field.py b/src/examplePlugins/field_to_field.py index 68520d3b..80b8154f 100644 --- a/src/examplePlugins/field_to_field.py +++ b/src/examplePlugins/field_to_field.py @@ -71,7 +71,7 @@ def is_valid(sg, logger, args): "to_value": {"allow_empty": True}, } - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Bail if we're missing any required args. try: diff --git a/src/examplePlugins/init_entity.py b/src/examplePlugins/init_entity.py index fd434751..105a23f4 100644 --- a/src/examplePlugins/init_entity.py +++ b/src/examplePlugins/init_entity.py @@ -8,6 +8,7 @@ # See docs folder for detailed usage info. import os +import six import shotgun_api3 @@ -76,7 +77,7 @@ def is_valid(sg, logger, args): ) return - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Grab the arg's value type. value_type = type(args[name]).__name__ @@ -123,7 +124,7 @@ def is_valid(sg, logger, args): "float": ["float"], } - for field_name, field_value in args[name].iteritems(): + for field_name, field_value in args[name].items(): field_value_type = type(field_value).__name__ # We assume unicode and str to be equivalent for these checks because @@ -197,7 +198,7 @@ def init_entity(sg, logger, event, args): # Re-query the entity so we don't clobber a value that may have # been populated by a user - fields_to_update = args["initial_data"].keys() + fields_to_update = list(args["initial_data"].keys()) entity = sg.find_one( entity_type, args["filters"] + [["id", "is", entity_id]], fields_to_update, ) @@ -215,10 +216,9 @@ def init_entity(sg, logger, event, args): update_data = {} # Convert anything that's currently unicode to a string. - for key, value in args["initial_data"].iteritems(): - key = str(key) - if isinstance(value, unicode): - value = str(value) + for key, value in args["initial_data"].items(): + key = six.ensure_str(key) + value = six.ensure_str(value) # If the field is already populated, don't clobber it unless # we've been told to. diff --git a/src/examplePlugins/link_shot_to_sequence.py b/src/examplePlugins/link_shot_to_sequence.py index 7c437863..3b328514 100644 --- a/src/examplePlugins/link_shot_to_sequence.py +++ b/src/examplePlugins/link_shot_to_sequence.py @@ -121,7 +121,7 @@ def is_valid(sg, logger, args): "sequence_filters": {"type": [list], "allow_empty": True}, } - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Bail if we're missing any required args. try: diff --git a/src/examplePlugins/update_task_template_entities.py b/src/examplePlugins/update_task_template_entities.py index 85ac7eac..6716d8cf 100644 --- a/src/examplePlugins/update_task_template_entities.py +++ b/src/examplePlugins/update_task_template_entities.py @@ -81,7 +81,7 @@ def is_valid(sg, logger, args): } # Check our args. - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]) @@ -89,7 +89,7 @@ def is_valid(sg, logger, args): # We assume unicode and str to be equivalent for these checks because # args come back from Django as unicode but are first set by the # Registrar as str. - if value_type == unicode: + if value_type.__name__ == "unicode": value_type = str # Make sure the setting value is the correct Python type. @@ -255,7 +255,7 @@ def update_entities(sg, logger, event, args): # Remove any Task fields from the schema that we aren't allowed to edit. task_schema_copy = task_schema.copy() - for field, value in task_schema.iteritems(): + for field, value in task_schema.items(): if value["editable"]["value"] is False: del task_schema_copy[field] task_schema = task_schema_copy @@ -322,7 +322,7 @@ def update_entities(sg, logger, event, args): # Gather non-empty field info in our template_data dict. template_data = {} - for field, value in template_task.iteritems(): + for field, value in template_task.items(): if value and field in task_schema.keys(): template_data[field] = value @@ -330,7 +330,7 @@ def update_entities(sg, logger, event, args): data = {} # Loop over the fields. - for field, value in task.iteritems(): + for field, value in task.items(): # Determine if we're going to write the value. write_value = False @@ -393,7 +393,7 @@ def update_entities(sg, logger, event, args): # Add any non-empty field info to our data dict. data = {} - for field, value in template_task.iteritems(): + for field, value in template_task.items(): if value and field in task_schema.keys(): data[field] = value diff --git a/src/examplePlugins/update_timecode_from_frames.py b/src/examplePlugins/update_timecode_from_frames.py index 0ca8317e..80789f6b 100644 --- a/src/examplePlugins/update_timecode_from_frames.py +++ b/src/examplePlugins/update_timecode_from_frames.py @@ -75,7 +75,7 @@ def is_valid(sg, logger, args): ) return - for name, type_targets in args_to_check.iteritems(): + for name, type_targets in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]).__name__ diff --git a/src/examplePlugins/update_timecode_values.py b/src/examplePlugins/update_timecode_values.py index 137f91e6..4d1a5398 100644 --- a/src/examplePlugins/update_timecode_values.py +++ b/src/examplePlugins/update_timecode_values.py @@ -191,7 +191,7 @@ def is_valid(sg, logger, args): ) return - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]) diff --git a/src/examplePlugins/update_version_cut_values.py b/src/examplePlugins/update_version_cut_values.py index 0a629c66..267602cc 100644 --- a/src/examplePlugins/update_version_cut_values.py +++ b/src/examplePlugins/update_version_cut_values.py @@ -169,7 +169,7 @@ def is_valid(sg, logger, args): "default_tail_out": {"type": [int], "allow_empty": False,}, } - for name, checks in args_to_check.iteritems(): + for name, checks in args_to_check.items(): # Grab the setting's value type. value_type = type(args[name]) diff --git a/src/shotgunEventDaemon.py b/src/shotgunEventDaemon.py index ede37d67..f0e2f1fa 100755 --- a/src/shotgunEventDaemon.py +++ b/src/shotgunEventDaemon.py @@ -28,9 +28,14 @@ __version__ = "0.9" __version_info__ = (0, 9) -import ConfigParser +# Suppress the deprecation warning about imp until we get around to replacing it +import warnings + +with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + import imp + import datetime -import imp import logging import logging.handlers import os @@ -39,14 +44,11 @@ import sys import time import traceback +from six.moves import configparser +import six.moves.cPickle as pickle from distutils.version import StrictVersion -try: - import cPickle as pickle -except ImportError: - import pickle - if sys.platform == "win32": import win32serviceutil import win32service @@ -147,9 +149,9 @@ def _addMailHandlerToLogger( logger.addHandler(mailHandler) -class Config(ConfigParser.ConfigParser): +class Config(configparser.ConfigParser): def __init__(self, path): - ConfigParser.ConfigParser.__init__(self) + configparser.ConfigParser.__init__(self) self.read(path) def getShotgunURL(self): @@ -167,7 +169,7 @@ def getEngineProxyServer(self): if not proxy_server: return None return proxy_server - except ConfigParser.NoOptionError: + except configparser.NoOptionError: return None def getEventIdFile(self): @@ -392,7 +394,7 @@ def _loadEventIdData(self): if eventIdFile and os.path.exists(eventIdFile): try: - fh = open(eventIdFile) + fh = open(eventIdFile, "rb") try: self._eventIdData = pickle.load(fh) @@ -438,7 +440,7 @@ def _loadEventIdData(self): # Backwards compatibility: # Reopen the file to try to read an old-style int - fh = open(eventIdFile) + fh = open(eventIdFile, "rb") line = fh.readline().strip() if line.isdigit(): # The _loadEventIdData got an old-style id file containing a single @@ -609,9 +611,9 @@ def _saveEventIdData(self): for colPath, state in self._eventIdData.items(): if state: try: - fh = open(eventIdFile, "w") - pickle.dump(self._eventIdData, fh) - fh.close() + with open(eventIdFile, "wb") as fh: + # Use protocol 2 so it can also be loaded in Python 2 + pickle.dump(self._eventIdData, fh, protocol=2) except OSError as err: self.log.error( "Can not write event id data to %s.\n\n%s",