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

SG-15210 Python3 port #73

Merged
merged 10 commits into from
Jul 28, 2020
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
44 changes: 44 additions & 0 deletions azure_pipelines.yml
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
23 changes: 11 additions & 12 deletions src/daemonizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/calc_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
22 changes: 15 additions & 7 deletions src/examplePlugins/calc_summaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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":
Expand All @@ -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
Expand Down Expand Up @@ -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"],
Expand All @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions src/examplePlugins/calculate_cut_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]},
Expand All @@ -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__
Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/convert_currency.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/create_note_from_version_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/datestamp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/field_to_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 7 additions & 7 deletions src/examplePlugins/init_entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# See docs folder for detailed usage info.

import os
import six
import shotgun_api3


Expand Down Expand Up @@ -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__
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/link_shot_to_sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 6 additions & 6 deletions src/examplePlugins/update_task_template_entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,15 @@ 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])

# 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":

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instanceof(value, six.text_type)

is the cleaner way to check for this. When we clean up our code in the long future where Python 2 is not supported anymore finding checks for six.text_type will be super easy.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about doing this. There's an inconsistency in the way the argument type validation is done across the different plugins. Most of them use the type.__name__ like this (look at the values under the type key):

args_to_check = {
"source_frames_field": {"sg_type": "number", "type": "str"},
"target_tc_field": {"sg_type": "timecode", "type": "str"},
"fps": {"type": "float"},
}

value_type = type(args[name]).__name__
# 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":
value_type = "str"

And a couple of them use the type itself:

args_to_check = {
"project_ids": {"type": [list], "allow_empty": False},
"entity_types": {"type": [list], "allow_empty": False},
"plugins_field": {
"type": [str],
"allow_empty": False,
"entity": "TaskTemplate",
"sg_type": ["list", "entity_type"],
"required_values": ["N/A", args["plugins_field_value"]],
},
"plugins_field_value": {"type": [str], "allow_empty": False},
"trash_old_tasks": {"type": [bool], "allow_empty": False},
"overwrite_field_values": {"type": [bool], "allow_empty": False},
"exclude_fields": {"type": [list], "allow_empty": True},
}

I decided to do the string compare to keep it consistent with the other ones. I have no problem changing it to the isinstance method. But I did want to point this out to see if you think we should change all the other ones to not use the string matching either.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better is the enemy of good. If this is the pattern used in the repo, let's not rock the boat.

value_type = str

# Make sure the setting value is the correct Python type.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -322,15 +322,15 @@ 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

# Init our data update dict.
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
Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/update_timecode_from_frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/update_timecode_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion src/examplePlugins/update_version_cut_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Loading