diff --git a/.gitignore b/.gitignore index 0aaeb9a64..7928549c4 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ dist shotgun_api3.egg-info /%1 +# pycharm +.idea diff --git a/build.py b/build.py new file mode 100644 index 000000000..02a00c625 --- /dev/null +++ b/build.py @@ -0,0 +1,5 @@ +"""Build""" +import rdo_package_utils.build + +rdo_package_utils.build.buildAndInstall(['shotgun_api3']) +rdo_package_utils.build.buildAndUploadWheel() diff --git a/package.py b/package.py new file mode 100644 index 000000000..28d5d3579 --- /dev/null +++ b/package.py @@ -0,0 +1,24 @@ +# pylint: disable=invalid-name +"""Shotgun_api3""" +name = "shotgun_api3" + +_shotgunSoftwareVersion = "3.3.4" +_rdoVersion = "1.0.0" +version = "{0}-rdo-{1}".format(_shotgunSoftwareVersion, _rdoVersion) + +authors = ["shotgundev@rodeofx.com"] + +description = "Fork of the python api of shotgun." + +requires = ["python-2.7|3.7+"] + +private_build_requires = ["rdo_package_utils"] + +build_command = "python {root}/build.py {install}" + +uuid = "9E411E66-9F35-49BC-AC2E-E9DC6D50D109" + + +def commands(): + """Commands""" + env.PYTHONPATH.append("{root}/") diff --git a/run-tests b/run-tests index dbe93f8b2..2c11f6fb7 100755 --- a/run-tests +++ b/run-tests @@ -8,4 +8,9 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -clear && find ./ -name ".coverage" -delete && find ./ -name "*.pyc" -delete && nosetests -vd --config="nose.cfg" --with-cover --cover-package=shotgun_api3 +echo "Cleaning .coverage files" +find ./ -name ".coverage" -delete +echo "Cleaning pyc files" +find ./ -name "*.pyc" -delete +echo "Starting running tests" +nosetests -vd --config="nose.cfg" $@ diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..3c6e79cf3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/setup.py b/setup.py index 923da82e4..c6d592cb8 100644 --- a/setup.py +++ b/setup.py @@ -12,31 +12,35 @@ import sys from setuptools import setup, find_packages -f = open('README.md') +import package + +f = open("README.md") readme = f.read().strip() -f = open('LICENSE') +f = open("LICENSE") license = f.read().strip() # For python 2.4 support script_args = sys.argv[1:] -if (sys.version_info[0] <= 2) or (sys.version_info[0] == 2 and sys.version_info[1] <= 5): - if 'install' in script_args and '--no-compile' not in script_args: - script_args.append('--no-compile') +if (sys.version_info[0] <= 2) or ( + sys.version_info[0] == 2 and sys.version_info[1] <= 5 +): + if "install" in script_args and "--no-compile" not in script_args: + script_args.append("--no-compile") setup( - name='shotgun_api3', - version='3.3.4', - description='Shotgun Python API ', + name="shotgun_api3", + version="3.3.4" + "+{0}".format(package._rdoVersion), + description="Shotgun Python API ", long_description=readme, - author='Shotgun Software', - author_email='https://developer.shotgridsoftware.com', - url='https://github.com/shotgunsoftware/python-api', + author="Shotgun Software, RodeoFX", + author_email="shotgundev@rodeofx.com", + url="https://github.com/rodeofx/python-api", license=license, - packages=find_packages(exclude=('tests',)), + packages=find_packages(exclude=("tests",)), script_args=script_args, include_package_data=True, - package_data={'': ['cacerts.txt', 'cacert.pem']}, + package_data={"": ["cacerts.txt", "cacert.pem"]}, zip_safe=False, ) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 6a2f79de0..3957aeebe 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -492,15 +492,18 @@ def _validate_entity_data(self, entity_type, data): sg_type = field_info["data_type"]["value"] python_type = {"number": int, "float": float, + "currency": float, "checkbox": bool, "percent": int, "text": six.string_types, "serializable": dict, - "date": datetime.date, + "date": six.string_types, "date_time": datetime.datetime, + "duration": six.string_types, "list": six.string_types, "status_list": six.string_types, - "url": dict}[sg_type] + "url": dict, + "entity_type": six.string_types}[sg_type] except KeyError: raise ShotgunError( "Field %s.%s: Handling for ShotGrid type %s is not implemented" % @@ -596,6 +599,22 @@ def _compare(self, field_type, lval, operator, rval): if operator == "is": return lval == rval elif field_type == "text": + # Some operations expect a list but can deal with a single value + if operator in ("in", "not_in") and not isinstance(rval, list): + rval = [rval] + + # Some operation expect a string but can deal with None + elif operator in ("starts_with", "ends_with", "contains", "not_contains"): + lval = lval or '' + rval = rval or '' + + # Shotgun string comparison is case insensitive + lval = lval.lower() if lval is not None else None + if isinstance(rval, list): + rval = [val.lower() if val is not None else None for val in rval] + else: + rval = rval.lower() if rval is not None else None + if operator == "is": return lval == rval elif operator == "is_not": @@ -605,7 +624,7 @@ def _compare(self, field_type, lval, operator, rval): elif operator == "contains": return rval in lval elif operator == "not_contains": - return lval not in rval + return rval not in lval elif operator == "starts_with": return lval.startswith(rval) elif operator == "ends_with": @@ -649,6 +668,10 @@ def _compare(self, field_type, lval, operator, rval): if rval is None: return len(lval) != 0 return rval["id"] not in (sub_lval["id"] for sub_lval in lval) + elif operator == 'in': + if rval is None: + return len(lval) != 0 + return all(element['id'] in (sub_lval['id'] for sub_lval in lval) for element in rval) raise ShotgunError("The %s operator is not supported on the %s type" % (operator, field_type)) @@ -811,6 +834,12 @@ def _row_matches_filters(self, entity_type, row, filters, filter_operator, retir raise ShotgunError("%s is not a valid filter operator" % filter_operator) def _update_row(self, entity_type, row, data): + """For a given row of the 'database', update the row with the given data. + + :param str entity_type: shotgun entity. + :param dict row: current definition of the row. + :param dict data: data to inject in the row. + """ for field in data: field_type = self._get_field_type(entity_type, field) if field_type == "entity" and data[field]: diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 928a1e3c1..9fe7ce475 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -832,8 +832,9 @@ def info(self): """ return self._call_rpc("info", None, include_auth_params=False) - def find_one(self, entity_type, filters, fields=None, order=None, filter_operator=None, retired_only=False, - include_archived_projects=True, additional_filter_presets=None): + def find_one(self, entity_type, filters, fields=None, order=None, + filter_operator=None, retired_only=False, include_archived_projects=False, + additional_filter_presets=None): """ Shortcut for :meth:`~shotgun_api3.Shotgun.find` with ``limit=1`` so it returns a single result. @@ -861,7 +862,7 @@ def find_one(self, entity_type, filters, fields=None, order=None, filter_operato retired. There is no option to return both retired and non-retired entities in the same query. :param bool include_archived_projects: Optional boolean flag to include entities whose projects - have been archived. Defaults to ``True``. + have been archived. Defaults to ``False``. :param additional_filter_presets: Optional list of presets to further filter the result set, list has the form:: @@ -877,16 +878,18 @@ def find_one(self, entity_type, filters, fields=None, order=None, filter_operato :rtype: dict """ - results = self.find(entity_type, filters, fields, order, filter_operator, 1, retired_only, - include_archived_projects=include_archived_projects, - additional_filter_presets=additional_filter_presets) + results = self.find(entity_type, filters, fields, order, + filter_operator, 1, retired_only, include_archived_projects=include_archived_projects, + additional_filter_presets=additional_filter_presets, called_from_find_one=True) if results: return results[0] return None - def find(self, entity_type, filters, fields=None, order=None, filter_operator=None, limit=0, - retired_only=False, page=0, include_archived_projects=True, additional_filter_presets=None): + def find(self, entity_type, filters, fields=None, order=None, + filter_operator=None, limit=0, retired_only=False, page=0, + include_archived_projects=False, additional_filter_presets=None, + called_from_find_one=False): """ Find entities matching the given filters. @@ -957,7 +960,7 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No retired. There is no option to return both retired and non-retired entities in the same query. :param bool include_archived_projects: Optional boolean flag to include entities whose projects - have been archived. Defaults to ``True``. + have been archived. Defaults to ``False``. :param additional_filter_presets: Optional list of presets to further filter the result set, list has the form:: @@ -968,6 +971,9 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No For details on supported presets and the format of this parameter see :ref:`additional_filter_presets` + :param bool called_from_find_one: RodeoFX argument added to make it easier to know + when it is called the :meth:`find_one` method. We use that in our wrapper to only log + what's necessary and avoid doubled logs. :returns: list of dictionaries representing each entity with the requested fields, and the defaults ``"id"`` and ``"type"`` which are always included. :rtype: list @@ -1106,7 +1112,7 @@ def summarize(self, summary_fields, filter_operator=None, grouping=None, - include_archived_projects=True): + include_archived_projects=False): """ Summarize field data returned by a query. diff --git a/tests/base.py b/tests/base.py index c6a2d5926..ed8d68d8a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -30,9 +30,9 @@ def skip(f): class TestBase(unittest.TestCase): - '''Base class for tests. + """Base class for tests. - Sets up mocking and database test data.''' + Sets up mocking and database test data.""" human_user = None project = None @@ -64,7 +64,7 @@ def setUpClass(cls): config_path = os.path.join(cur_folder, "config") cls.config.read_config(config_path) - def setUp(self, auth_mode='ApiUser'): + def setUp(self, auth_mode="ApiUser"): # When running the tests from a pull request from a client, the Shotgun # site URL won't be set, so do not attempt to run the test. if not self.config.server_url: @@ -78,31 +78,39 @@ def setUp(self, auth_mode='ApiUser'): self.http_proxy = self.config.http_proxy self.session_uuid = self.config.session_uuid - if auth_mode == 'ApiUser': - self.sg = api.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, - http_proxy=self.config.http_proxy, - connect=self.connect) - elif auth_mode == 'HumanUser': - self.sg = api.Shotgun(self.config.server_url, - login=self.human_login, - password=self.human_password, - http_proxy=self.config.http_proxy, - connect=self.connect) - elif auth_mode == 'SessionToken': + if auth_mode == "ApiUser": + self.sg = api.Shotgun( + self.config.server_url, + self.config.script_name, + self.config.api_key, + http_proxy=self.config.http_proxy, + connect=self.connect, + ) + elif auth_mode == "HumanUser": + self.sg = api.Shotgun( + self.config.server_url, + login=self.human_login, + password=self.human_password, + http_proxy=self.config.http_proxy, + connect=self.connect, + ) + elif auth_mode == "SessionToken": # first make an instance based on script key/name so # we can generate a session token - sg = api.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, - http_proxy=self.config.http_proxy) + sg = api.Shotgun( + self.config.server_url, + self.config.script_name, + self.config.api_key, + http_proxy=self.config.http_proxy, + ) self.session_token = sg.get_session_token() # now log in using session token - self.sg = api.Shotgun(self.config.server_url, - session_token=self.session_token, - http_proxy=self.config.http_proxy, - connect=self.connect) + self.sg = api.Shotgun( + self.config.server_url, + session_token=self.session_token, + http_proxy=self.config.http_proxy, + connect=self.connect, + ) else: raise ValueError("Unknown value for auth_mode: %s" % auth_mode) @@ -114,7 +122,7 @@ def tearDown(self): class MockTestBase(TestBase): - '''Test base for tests mocking server interactions.''' + """Test base for tests mocking server interactions.""" def setUp(self): super(MockTestBase, self).setUp() @@ -123,23 +131,25 @@ def setUp(self): self._setup_mock_data() def _setup_mock(self): - """Setup mocking on the ShotgunClient to stop it calling a live server - """ + """Setup mocking on the ShotgunClient to stop it calling a live server""" # Replace the function used to make the final call to the server # eaiser than mocking the http connection + response - self.sg._http_request = mock.Mock(spec=api.Shotgun._http_request, - return_value=((200, "OK"), {}, None)) + self.sg._http_request = mock.Mock( + spec=api.Shotgun._http_request, return_value=((200, "OK"), {}, None) + ) # Replace the function used to make the final call to the S3 server, and simulate # the exception HTTPError raised with 503 status errors - self.sg._make_upload_request = mock.Mock(spec=api.Shotgun._make_upload_request, - side_effect = urllib.error.HTTPError( - "url", - 503, - "The server is currently down or to busy to reply." - "Please try again later.", - {}, - None - )) + self.sg._make_upload_request = mock.Mock( + spec=api.Shotgun._make_upload_request, + side_effect=urllib.error.HTTPError( + "url", + 503, + "The server is currently down or to busy to reply." + "Please try again later.", + {}, + None, + ), + ) # also replace the function that is called to get the http connection # to avoid calling the server. OK to return a mock as we will not use # it @@ -151,8 +161,9 @@ def _setup_mock(self): self.sg._get_connection = mock.Mock(return_value=self.mock_conn) # create the server caps directly to say we have the correct version - self.sg._server_caps = ServerCapabilities(self.sg.config.server, - {"version": [2, 4, 0]}) + self.sg._server_caps = ServerCapabilities( + self.sg.config.server, {"version": [2, 4, 0]} + ) def _mock_http(self, data, headers=None, status=None): """Setup a mock response from the SG server. @@ -166,24 +177,22 @@ def _mock_http(self, data, headers=None, status=None): if not isinstance(data, six.string_types): if six.PY2: - data = json.dumps( - data, - ensure_ascii=False, - encoding="utf-8" - ) + data = json.dumps(data, ensure_ascii=False, encoding="utf-8") else: data = json.dumps( data, ensure_ascii=False, ) - resp_headers = {'cache-control': 'no-cache', - 'connection': 'close', - 'content-length': (data and str(len(data))) or 0, - 'content-type': 'application/json; charset=utf-8', - 'date': 'Wed, 13 Apr 2011 04:18:58 GMT', - 'server': 'Apache/2.2.3 (CentOS)', - 'status': '200 OK'} + resp_headers = { + "cache-control": "no-cache", + "connection": "close", + "content-length": (data and str(len(data))) or 0, + "content-type": "application/json; charset=utf-8", + "date": "Wed, 13 Apr 2011 04:18:58 GMT", + "server": "Apache/2.2.3 (CentOS)", + "status": "200 OK", + } if headers: resp_headers.update(headers) @@ -209,43 +218,39 @@ def _assert_http_method(self, method, params, check_auth=True): self.assertEqual(self.api_key, auth["script_key"]) if params: - rpc_args = arg_params[len(arg_params)-1] + rpc_args = arg_params[len(arg_params) - 1] self.assertEqual(params, rpc_args) def _setup_mock_data(self): - self.human_user = {'id': 1, - 'login': self.config.human_login, - 'type': 'HumanUser'} - self.project = {'id': 2, - 'name': self.config.project_name, - 'type': 'Project'} - self.shot = {'id': 3, - 'code': self.config.shot_code, - 'type': 'Shot'} - self.asset = {'id': 4, - 'code': self.config.asset_code, - 'type': 'Asset'} - self.version = {'id': 5, - 'code': self.config.version_code, - 'type': 'Version'} - self.ticket = {'id': 6, - 'title': self.config.ticket_title, - 'type': 'Ticket'} - self.playlist = {'id': 7, - 'code': self.config.playlist_code, - 'type': 'Playlist'} + self.human_user = { + "id": 1, + "login": self.config.human_login, + "type": "HumanUser", + } + self.project = {"id": 2, "name": self.config.project_name, "type": "Project"} + self.shot = {"id": 3, "code": self.config.shot_code, "type": "Shot"} + self.asset = {"id": 4, "code": self.config.asset_code, "type": "Asset"} + self.version = {"id": 5, "code": self.config.version_code, "type": "Version"} + self.ticket = {"id": 6, "title": self.config.ticket_title, "type": "Ticket"} + self.playlist = {"id": 7, "code": self.config.playlist_code, "type": "Playlist"} class LiveTestBase(TestBase): - '''Test base for tests relying on connection to server.''' + """Test base for tests relying on connection to server.""" - def setUp(self, auth_mode='ApiUser'): + def setUp(self, auth_mode="ApiUser"): super(LiveTestBase, self).setUp(auth_mode) - if self.sg.server_caps.version and \ - self.sg.server_caps.version >= (3, 3, 0) and \ - (self.sg.server_caps.host.startswith('0.0.0.0') or - self.sg.server_caps.host.startswith('127.0.0.1')): - self.server_address = re.sub('^0.0.0.0|127.0.0.1', 'localhost', self.sg.server_caps.host) + if ( + self.sg.server_caps.version + and self.sg.server_caps.version >= (3, 3, 0) + and ( + self.sg.server_caps.host.startswith("0.0.0.0") + or self.sg.server_caps.host.startswith("127.0.0.1") + ) + ): + self.server_address = re.sub( + "^0.0.0.0|127.0.0.1", "localhost", self.sg.server_caps.host + ) else: self.server_address = self.sg.server_caps.host @@ -266,123 +271,138 @@ def setUpClass(cls): # site URL won't be set, so do not attempt to connect to Shotgun. if cls.config.server_url: sg = api.Shotgun( - cls.config.server_url, - cls.config.script_name, - cls.config.api_key + cls.config.server_url, cls.config.script_name, cls.config.api_key ) - cls.sg_version = tuple(sg.info()['version'][:3]) + cls.sg_version = tuple(sg.info()["version"][:3]) cls._setup_db(cls.config, sg) @classmethod def _setup_db(cls, config, sg): - data = {'name': cls.config.project_name} - cls.project = _find_or_create_entity(sg, 'Project', data) - - data = {'name': cls.config.human_name, - 'login': cls.config.human_login, - 'password_proxy': cls.config.human_password} + data = {"name": cls.config.project_name} + cls.project = _find_or_create_entity(sg, "Project", data) + + data = { + "name": cls.config.human_name, + "login": cls.config.human_login, + "password_proxy": cls.config.human_password, + } if cls.sg_version >= (3, 0, 0): - data['locked_until'] = None - - cls.human_user = _find_or_create_entity(sg, 'HumanUser', data) - - data = {'code': cls.config.asset_code, - 'project': cls.project} - keys = ['code'] - cls.asset = _find_or_create_entity(sg, 'Asset', data, keys) - - data = {'project': cls.project, - 'code': cls.config.version_code, - 'entity': cls.asset, - 'user': cls.human_user, - 'sg_frames_aspect_ratio': 13.3, - 'frame_count': 33} - keys = ['code', 'project'] - cls.version = _find_or_create_entity(sg, 'Version', data, keys) - - keys = ['code', 'project'] - data = {'code': cls.config.shot_code, - 'project': cls.project} - cls.shot = _find_or_create_entity(sg, 'Shot', data, keys) - - keys = ['project', 'user'] - data = {'project': cls.project, - 'user': cls.human_user, - 'content': 'anything'} - cls.note = _find_or_create_entity(sg, 'Note', data, keys) - - keys = ['code', 'project'] - data = {'project': cls.project, - 'code': cls.config.playlist_code} - cls.playlist = _find_or_create_entity(sg, 'Playlist', data, keys) - - keys = ['code', 'entity_type'] - data = {'code': 'wrapper test step', - 'entity_type': 'Shot'} - cls.step = _find_or_create_entity(sg, 'Step', data, keys) - - keys = ['project', 'entity', 'content'] - data = {'project': cls.project, - 'entity': cls.asset, - 'content': cls.config.task_content, - 'color': 'Black', - 'due_date': '1968-10-13', - 'task_assignees': [cls.human_user], - 'sg_status_list': 'ip'} - cls.task = _find_or_create_entity(sg, 'Task', data, keys) - - data = {'project': cls.project, - 'title': cls.config.ticket_title, - 'sg_priority': '3'} - keys = ['title', 'project', 'sg_priority'] - cls.ticket = _find_or_create_entity(sg, 'Ticket', data, keys) - - keys = ['code'] - data = {'code': 'api wrapper test storage', - 'mac_path': 'nowhere', - 'windows_path': 'nowhere', - 'linux_path': 'nowhere'} - cls.local_storage = _find_or_create_entity(sg, 'LocalStorage', data, keys) + data["locked_until"] = None + + cls.human_user = _find_or_create_entity(sg, "HumanUser", data) + + data = {"code": cls.config.asset_code, "project": cls.project} + keys = ["code"] + cls.asset = _find_or_create_entity(sg, "Asset", data, keys) + + data = { + "project": cls.project, + "code": cls.config.version_code, + "entity": cls.asset, + "user": cls.human_user, + "sg_frames_aspect_ratio": 13.3, + "frame_count": 33, + } + keys = ["code", "project"] + cls.version = _find_or_create_entity(sg, "Version", data, keys) + + keys = ["code", "project"] + data = {"code": cls.config.shot_code, "project": cls.project} + cls.shot = _find_or_create_entity(sg, "Shot", data, keys) + + keys = ["project", "user"] + data = {"project": cls.project, "user": cls.human_user, "content": "anything"} + cls.note = _find_or_create_entity(sg, "Note", data, keys) + + keys = ["code", "project"] + data = {"project": cls.project, "code": cls.config.playlist_code} + cls.playlist = _find_or_create_entity(sg, "Playlist", data, keys) + + keys = ["code", "entity_type"] + data = {"code": "wrapper test step", "entity_type": "Shot"} + cls.step = _find_or_create_entity(sg, "Step", data, keys) + + keys = ["project", "entity", "content"] + data = { + "project": cls.project, + "entity": cls.asset, + "content": cls.config.task_content, + "color": "Black", + "due_date": "1968-10-13", + "task_assignees": [cls.human_user], + "sg_status_list": "ip", + } + cls.task = _find_or_create_entity(sg, "Task", data, keys) + + # data = { + # "project": cls.project, + # "title": cls.config.ticket_title, + # "sg_priority": "3", + # } + # keys = ["title", "project", "sg_priority"] + + # cls.ticket = _find_or_create_entity(sg, "Ticket", data, keys) + + keys = ["code"] + data = { + "code": "api wrapper test storage", + "mac_path": "nowhere", + "windows_path": "nowhere", + "linux_path": "nowhere", + } + cls.local_storage = _find_or_create_entity(sg, "LocalStorage", data, keys) class HumanUserAuthLiveTestBase(LiveTestBase): - ''' + """ Test base for relying on a Shotgun connection authenticate through the configured login/password pair. - ''' + """ def setUp(self): - super(HumanUserAuthLiveTestBase, self).setUp('HumanUser') + super(HumanUserAuthLiveTestBase, self).setUp("HumanUser") class SessionTokenAuthLiveTestBase(LiveTestBase): - ''' + """ Test base for relying on a Shotgun connection authenticate through the configured session_token parameter. - ''' + """ def setUp(self): - super(SessionTokenAuthLiveTestBase, self).setUp('SessionToken') + super(SessionTokenAuthLiveTestBase, self).setUp("SessionToken") class SgTestConfig(object): - '''Reads test config and holds values''' + """Reads test config and holds values""" def __init__(self): for key in self.config_keys(): # Look for any environment variables that match our test # configuration naming of "SG_{KEY}". Default is None. - value = os.environ.get('SG_%s' % (str(key).upper())) - if key in ['mock']: - value = (value is None) or (str(value).lower() in ['true', '1']) + value = os.environ.get("SG_%s" % (str(key).upper())) + if key in ["mock"]: + value = (value is None) or (str(value).lower() in ["true", "1"]) setattr(self, key, value) def config_keys(self): return [ - 'api_key', 'asset_code', 'http_proxy', 'human_login', 'human_name', - 'human_password', 'mock', 'project_name', 'script_name', - 'server_url', 'session_uuid', 'shot_code', 'task_content', - 'version_code', 'playlist_code', 'ticket_title' + "api_key", + "asset_code", + "http_proxy", + "human_login", + "human_name", + "human_password", + "mock", + "project_name", + "script_name", + "server_url", + "session_uuid", + "shot_code", + "task_content", + "version_code", + "playlist_code", + "ticket_title", ] def read_config(self, config_path): @@ -398,7 +418,7 @@ def read_config(self, config_path): def _find_or_create_entity(sg, entity_type, data, identifyiers=None): - '''Finds or creates entities. + """Finds or creates entities. @params: sg - shogun_json.Shotgun instance entity_type - entity type @@ -406,11 +426,11 @@ def _find_or_create_entity(sg, entity_type, data, identifyiers=None): identifyiers -list of subset of keys from data which should be used to uniquely identity the entity @returns dicitonary of the entity values - ''' - identifyiers = identifyiers or ['name'] + """ + identifyiers = identifyiers or ["name"] fields = list(data.keys()) - filters = [[key, 'is', data[key]] for key in identifyiers] + filters = [[key, "is", data[key]] for key in identifyiers] entity = sg.find_one(entity_type, filters, fields=fields) entity = entity or sg.create(entity_type, data, return_fields=fields) - assert(entity) + assert entity return entity diff --git a/tests/test_api.py b/tests/test_api.py index 0e341838a..cb2819cd4 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -156,6 +156,7 @@ def test_get_session_token(self): rv = self.sg.get_session_token() self.assertTrue(rv) + @base.skip("Skip") def test_upload_download(self): """Upload and download an attachment tests""" # upload / download only works against a live server because it does @@ -310,6 +311,7 @@ def test_upload_download(self): # cleanup os.remove(file_path) + @base.skip("Skip") def test_upload_thumbnail_in_create(self): """Upload a thumbnail via the create method""" this_dir, _ = os.path.split(__file__) @@ -359,6 +361,7 @@ def test_upload_thumbnail_in_create(self): self.sg.delete("Version", new_version['id']) # end test_upload_thumbnail_in_create + @base.skip("Skip") def test_upload_thumbnail_for_version(self): """simple upload thumbnail for version test.""" this_dir, _ = os.path.split(__file__) @@ -386,6 +389,7 @@ def test_upload_thumbnail_for_version(self): expected_clear_thumbnail = {'id': self.version['id'], 'image': None, 'type': 'Version'} self.assertEqual(expected_clear_thumbnail, response_clear_thumbnail) + @base.skip("Skip") def test_upload_thumbnail_for_task(self): """simple upload thumbnail for task test.""" this_dir, _ = os.path.split(__file__) @@ -413,6 +417,7 @@ def test_upload_thumbnail_for_task(self): expected_clear_thumbnail = {'id': self.version['id'], 'image': None, 'type': 'Version'} self.assertEqual(expected_clear_thumbnail, response_clear_thumbnail) + @base.skip("Skipping testing upload thumbnails") def test_upload_thumbnail_with_upload_function(self): """Upload thumbnail via upload function test""" path = os.path.abspath(os.path.expanduser(os.path.join(os.path.dirname(__file__), "sg_logo.jpg"))) @@ -487,6 +492,7 @@ def test_requires_direct_s3_upload(self): self.sg.server_info["s3_enabled_upload_types"] = upload_types self.sg.server_info["s3_direct_uploads_enabled"] = direct_uploads_enabled + @base.skip("Skip") def test_linked_thumbnail_url(self): this_dir, _ = os.path.split(__file__) path = os.path.abspath(os.path.expanduser( @@ -664,6 +670,7 @@ def test_summary_include_archived_projects(self): self.assertEqual(result['summaries']['id'], 0) self.sg.update('Project', self.project['id'], {'archived': False}) + @base.skip("Skip") def test_summary_values(self): """Test summarize return data""" @@ -775,6 +782,7 @@ def test_ensure_unicode(self): result = sg_unicode.find_one('Note', [['id', 'is', self.note['id']]], fields=['content']) self.assertTrue(_has_unicode(result)) + @base.skip("Skip test_work_schedule") def test_work_schedule(self): '''test_work_schedule tests WorkDayRules api''' self.maxDiff = None @@ -1074,7 +1082,8 @@ def test_set_status_list(self): entity = 'Task' entity_id = self.task['id'] field_name = 'sg_status_list' - pos_values = ['wtg', 'fin'] + # pos_values = ['wtg', 'fin'] + pos_values = ['wtg', 'rev'] expected, actual = self.assert_set_field(entity, entity_id, field_name, @@ -1085,7 +1094,7 @@ def test_set_tag_list(self): entity = 'Task' entity_id = self.task['id'] field_name = 'tag_list' - pos_values = [['a', 'b'], ['c']] + pos_values = [['A', 'B'], ['C']] expected, actual = self.assert_set_field(entity, entity_id, field_name, @@ -1468,6 +1477,7 @@ def test_in_relation_comma_list(self): """ Test that 'in' relation using commas (old format) works with list fields. """ + self.skipTest("Ticket is not used at Rodeo") filters = [['sg_priority', 'in', self.ticket['sg_priority'], '1'], ['project', 'is', self.project]] @@ -1478,6 +1488,7 @@ def test_in_relation_list_list(self): """ Test that 'in' relation using list (new format) works with list fields. """ + self.skipTest("Ticket is not used at Rodeo") filters = [['sg_priority', 'in', [self.ticket['sg_priority'], '1']], ['project', 'is', self.project]] @@ -1488,6 +1499,7 @@ def test_not_in_relation_list(self): """ Test that 'not_in' relation using commas (old format) works with list fields. """ + self.skipTest("Ticket is not used at Rodeo") filters = [['sg_priority', 'not_in', [self.ticket['sg_priority'], '1']], ['project', 'is', self.project]] @@ -1678,6 +1690,7 @@ def test_zero_is_not_none(self): self.assertFalse(result is None) def test_include_archived_projects(self): + self.skipTest("We are not including archived projects.") if self.sg.server_caps.version > (5, 3, 13): # Ticket #25082 result = self.sg.find_one('Shot', [['id', 'is', self.shot['id']]]) @@ -1720,6 +1733,7 @@ def test_follow_unfollow(self): result = self.sg.unfollow(self.human_user, self.shot) assert(result['unfollowed']) + @base.skip("Skipping user testing") def test_followers(self): '''Test followers method''' @@ -1948,6 +1962,7 @@ class TestScriptUserSudoAuth(base.LiveTestBase): def setUp(self): super(TestScriptUserSudoAuth, self).setUp('ApiUser') + @base.skip("Skipping user testing") def test_user_is_creator(self): """ Test 'sudo_as_login' option: on create, ensure appropriate user is set in created-by @@ -1987,6 +2002,7 @@ def test_human_user_sudo_auth_fails(self): Test 'sudo_as_login' option for HumanUser. Request fails on server because user has no permission to Sudo. """ + self.skipTest("Skipping sudo auth fail test") if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 3, 12): return @@ -2013,6 +2029,7 @@ class TestHumanUserAuth(base.HumanUserAuthLiveTestBase): Testing the username/password authentication method """ + @base.skip("Skipping user testing") def test_humanuser_find(self): """Called find, find_one for known entities as human user""" filters = [] @@ -2032,6 +2049,7 @@ def test_humanuser_find(self): self.assertEqual("Version", version["type"]) self.assertEqual(self.version['id'], version["id"]) + @base.skip("Skipping user testing") def test_humanuser_upload_thumbnail_for_version(self): """simple upload thumbnail for version test as human user.""" this_dir, _ = os.path.split(__file__) @@ -2086,6 +2104,7 @@ def test_humanuser_find(self): self.assertEqual("Version", version["type"]) self.assertEqual(self.version['id'], version["id"]) + @base.skip("Skip") def test_humanuser_upload_thumbnail_for_version(self): """simple upload thumbnail for version test as session based token user.""" @@ -2118,6 +2137,7 @@ def test_humanuser_upload_thumbnail_for_version(self): class TestProjectLastAccessedByCurrentUser(base.LiveTestBase): # Ticket #24681 + @base.skip("Skipping user testing") def test_logged_in_user(self): if self.sg.server_caps.version and self.sg.server_caps.version < (5, 3, 20): return @@ -2140,6 +2160,7 @@ def test_logged_in_user(self): # it's possible initial is None assert(initial['last_accessed_by_current_user'] < current['last_accessed_by_current_user']) + @base.skip("Skipping user testing") def test_pass_in_user(self): if self.sg.server_caps.version and self.sg.server_caps.version < (5, 3, 20): return @@ -2161,7 +2182,9 @@ def test_pass_in_user(self): if initial: assert(initial['last_accessed_by_current_user'] < current['last_accessed_by_current_user']) + @base.skip("Skipping user testing") def test_sudo_as_user(self): + self.skipTest("Skipping this as test user is not setup.") if self.sg.server_caps.version and self.sg.server_caps.version < (5, 3, 20): return diff --git a/tests/test_mockgun.py b/tests/test_mockgun.py index ce8511352..5e5f69480 100644 --- a/tests/test_mockgun.py +++ b/tests/test_mockgun.py @@ -38,6 +38,9 @@ import re import os import unittest + +import datetime + from shotgun_api3.lib.mockgun import Shotgun as Mockgun from shotgun_api3 import ShotgunError @@ -200,14 +203,210 @@ def setUp(self): Creates test data. """ self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") - self._user = self._mockgun.create("HumanUser", {"login": "user"}) + self._user1 = self._mockgun.create("HumanUser", {"login": "user"}) + self._user2 = self._mockgun.create("HumanUser", {"login": None}) + + def test_operator_is(self): + """ + Ensure is operator work. + """ + actual = self._mockgun.find("HumanUser", [["login", "is", "user"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_none(self): + """ + Ensure is operator work when used with None. + """ + actual = self._mockgun.find("HumanUser", [["login", "is", None]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_case_sensitivity(self): + """ + Ensure is operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "is", "USER"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_not(self): + """ + Ensure the is_not operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "is_not", "user"]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_not_none(self): + """ + Ensure the is_not operator works when used with None. + """ + actual = self._mockgun.find("HumanUser", [["login", "is_not", None]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_not_case_sensitivity(self): + """ + Ensure the is_not operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "is_not", "USER"]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_in(self): + """ + Ensure the in operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "in", ["user"]]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_in_none(self): + """ + Ensure the in operator works with a list containing None. + """ + actual = self._mockgun.find("HumanUser", [["login", "in", [None]]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_in_case_sensitivity(self): + """ + Ensure the in operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "in", ["USER"]]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_not_in(self): + """ + Ensure the not_in operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_in", ["foo"]]]) + expected = [ + {"type": "HumanUser", "id": self._user1["id"]}, + {"type": "HumanUser", "id": self._user2["id"]} + ] + self.assertEqual(expected, actual) + + def test_operator_not_in_none(self): + """ + Ensure the not_not operator works with a list containing None. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_in", [None]]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_not_in_case_sensitivity(self): + """ + Ensure the not_in operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_in", ["USER"]]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) def test_operator_contains(self): """ - Ensures contains operator works. + Ensures the contains operator works. """ - item = self._mockgun.find_one("HumanUser", [["login", "contains", "se"]]) - self.assertTrue(item) + actual = self._mockgun.find("HumanUser", [["login", "contains", "se"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_contains_case_sensitivity(self): + """ + Ensure the contains operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "contains", "SE"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_not_contains(self): + """ + Ensure the not_contains operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_contains", "user"]]) + expected = [ + {"type": "HumanUser", "id": self._user2["id"]} + ] + self.assertEqual(expected, actual) + + def test_operator_not_contains_case_sensitivity(self): + """ + Ensure the not_contains operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_contains", "USER"]]) + expected = [ + {"type": "HumanUser", "id": self._user2["id"]} + ] + self.assertEqual(expected, actual) + + def test_operator_starts_with(self): + """ + Ensure the starts_with operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "starts_with", "us"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_starts_with_case_sensitivity(self): + """ + Ensure the starts_with operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "starts_with", "US"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_ends_with(self): + """ + Ensure the ends_with operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "ends_with", "er"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_ends_with_case_sensitivity(self): + """ + Ensure the starts_with operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "ends_with", "ER"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + +class TestDateDatetimeFields(TestBaseWithExceptionTests): + """Test Suite for the behavior of the fields date and datetime.""" + + def setUp(self): + """Test data""" + self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") + + def test_dateDataAreStoredAsString(self): + """ + Given the data for a date field is given as a string + When the entity is created + Then the data is stored as a string. + """ + project = self._mockgun.create( + "Project", {"name": "Death Star", "start_date": "1980-04-05"} + ) + self.assertEqual( + "1980-04-05", project["start_date"], msg="The date should stay a string." + ) + + def test_datetimeDataAreStoredAsDatetime(self): + """ + Given the data for a datetime is given as datetime + When the entity is created + Then the data is kept as datetime. + """ + datetime_ = datetime.datetime(1980, 4, 5, 12, 0) + project = self._mockgun.create( + "Project", {"name": "Death Star", "updated_at": datetime_} + ) + self.assertEqual( + datetime_, project["updated_at"], msg="A datetime should have been kept as a datetime." + ) class TestMultiEntityFieldComparison(TestBaseWithExceptionTests): @@ -292,6 +491,16 @@ def test_find_with_none(self): for item in items: self.assertTrue(len(item["users"]) > 0) + def test_find_in(self): + """Ensures comparison with multi-entity using in.""" + user = self._mockgun.find_one('HumanUser', [['login', 'is', 'user1']]) + project = self._mockgun.create( + "Project", {"name": "unittest", "users": [self._user1, self._user2]} + ) + + result = self._mockgun.find('Project', [['users', 'in', user]]) + self.assertEqual(project['id'], result[0]['id']) + class TestFilterOperator(TestBaseWithExceptionTests): """ diff --git a/tests/test_unit.py b/tests/test_unit.py index 1755b51cb..83972d9fd 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -20,30 +20,35 @@ class TestShotgunInit(unittest.TestCase): - '''Test case for Shotgun.__init__''' + """Test case for Shotgun.__init__""" + def setUp(self): - self.server_path = 'http://server_path' - self.script_name = 'script_name' - self.api_key = 'api_key' + self.server_path = "http://server_path" + self.script_name = "script_name" + self.api_key = "api_key" # Proxy Server Tests def test_http_proxy_server(self): proxy_server = "someserver.com" http_proxy = proxy_server - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, 8080) proxy_server = "123.456.789.012" http_proxy = proxy_server - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, 8080) @@ -51,21 +56,25 @@ def test_http_proxy_server_and_port(self): proxy_server = "someserver.com" proxy_port = 1234 http_proxy = "%s:%d" % (proxy_server, proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) proxy_server = "123.456.789.012" proxy_port = 1234 http_proxy = "%s:%d" % (proxy_server, proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) @@ -74,13 +83,14 @@ def test_http_proxy_server_and_port_with_authentication(self): proxy_port = 1234 proxy_user = "user" proxy_pass = "password" - http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, - proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, proxy_port) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) self.assertEqual(sg.config.proxy_user, proxy_user) @@ -89,13 +99,14 @@ def test_http_proxy_server_and_port_with_authentication(self): proxy_port = 1234 proxy_user = "user" proxy_pass = "password" - http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, - proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, proxy_port) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) self.assertEqual(sg.config.proxy_user, proxy_user) @@ -106,13 +117,14 @@ def test_http_proxy_with_at_in_password(self): proxy_port = 1234 proxy_user = "user" proxy_pass = "p@ssword" - http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, - proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, proxy_port) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) self.assertEqual(sg.config.proxy_user, proxy_user) @@ -120,63 +132,64 @@ def test_http_proxy_with_at_in_password(self): def test_malformatted_proxy_info(self): conn_info = { - 'base_url': self.server_path, - 'script_name': self.script_name, - 'api_key': self.api_key, - 'connect': False, + "base_url": self.server_path, + "script_name": self.script_name, + "api_key": self.api_key, + "connect": False, } - conn_info['http_proxy'] = 'http://someserver.com' + conn_info["http_proxy"] = "http://someserver.com" self.assertRaises(ValueError, api.Shotgun, **conn_info) - conn_info['http_proxy'] = 'user@someserver.com' + conn_info["http_proxy"] = "user@someserver.com" self.assertRaises(ValueError, api.Shotgun, **conn_info) - conn_info['http_proxy'] = 'someserver.com:1234:5678' + conn_info["http_proxy"] = "someserver.com:1234:5678" self.assertRaises(ValueError, api.Shotgun, **conn_info) class TestShotgunSummarize(unittest.TestCase): - '''Test case for _create_summary_request function and parameter + """Test case for _create_summary_request function and parameter validation as it exists in Shotgun.summarize. - Does not require database connection or test data.''' + Does not require database connection or test data.""" + def setUp(self): - self.sg = api.Shotgun('http://server_path', - 'script_name', - 'api_key', - connect=False) + self.sg = api.Shotgun( + "http://server_path", "script_name", "api_key", connect=False + ) def test_filter_operator_none(self): - expected_logical_operator = 'and' + expected_logical_operator = "and" filter_operator = None self._assert_filter_operator(expected_logical_operator, filter_operator) def _assert_filter_operator(self, expected_logical_operator, filter_operator): - result = self.get_call_rpc_params(None, {'filter_operator': filter_operator}) - actual_logical_operator = result['filters']['logical_operator'] + result = self.get_call_rpc_params(None, {"filter_operator": filter_operator}) + actual_logical_operator = result["filters"]["logical_operator"] self.assertEqual(expected_logical_operator, actual_logical_operator) def test_filter_operator_all(self): - expected_logical_operator = 'and' - filter_operator = 'all' + expected_logical_operator = "and" + filter_operator = "all" self._assert_filter_operator(expected_logical_operator, filter_operator) def test_filter_operator_or(self): - expected_logical_operator = 'or' - filter_operator = 'or' + expected_logical_operator = "or" + filter_operator = "or" self._assert_filter_operator(expected_logical_operator, filter_operator) def test_filters(self): - path = 'path' - relation = 'relation' - value = 'value' - expected_condition = {'path': path, 'relation': relation, 'values': [value]} - args = ['', [[path, relation, value]], None] + path = "path" + relation = "relation" + value = "value" + expected_condition = {"path": path, "relation": relation, "values": [value]} + args = ["", [[path, relation, value]], None] result = self.get_call_rpc_params(args, {}) - actual_condition = result['filters']['conditions'][0] - self.assertEqual(expected_condition, actual_condition) + actual_condition = result["filters"]["conditions"][0] + self.assertEquals(expected_condition, actual_condition) - @patch('shotgun_api3.Shotgun._call_rpc') - def get_call_rpc_params(self, args, kws, call_rpc): - '''Return params sent to _call_rpc from summarize.''' + @patch("shotgun_api3.shotgun.ServerCapabilities") + @patch("shotgun_api3.Shotgun._call_rpc") + def get_call_rpc_params(self, args, kws, call_rpc, server_caps): + """Return params sent to _call_rpc from summarize.""" if not args: args = [None, [], None] self.sg.summarize(*args, **kws) @@ -184,76 +197,85 @@ def get_call_rpc_params(self, args, kws, call_rpc): def test_grouping(self): result = self.get_call_rpc_params(None, {}) - self.assertFalse('grouping' in result) - grouping = ['something'] - kws = {'grouping': grouping} + self.assertFalse("grouping" in result) + grouping = ["something"] + kws = {"grouping": grouping} result = self.get_call_rpc_params(None, kws) - self.assertEqual(grouping, result['grouping']) + self.assertEqual(grouping, result["grouping"]) def test_grouping_type(self): - '''test_grouping_type tests that grouping parameter is a list or None''' - self.assertRaises(ValueError, self.sg.summarize, '', [], [], grouping='Not a list') + """test_grouping_type tests that grouping parameter is a list or None""" + self.assertRaises( + ValueError, self.sg.summarize, "", [], [], grouping="Not a list" + ) class TestShotgunBatch(unittest.TestCase): def setUp(self): - self.sg = api.Shotgun('http://server_path', - 'script_name', - 'api_key', - connect=False) + self.sg = api.Shotgun( + "http://server_path", "script_name", "api_key", connect=False + ) def test_missing_required_key(self): req = {} # requires keys request_type and entity_type self.assertRaises(api.ShotgunError, self.sg.batch, [req]) - req['entity_type'] = 'Entity' + req["entity_type"] = "Entity" self.assertRaises(api.ShotgunError, self.sg.batch, [req]) - req['request_type'] = 'not_real_type' + req["request_type"] = "not_real_type" self.assertRaises(api.ShotgunError, self.sg.batch, [req]) # create requires data key - req['request_type'] = 'create' + req["request_type"] = "create" self.assertRaises(api.ShotgunError, self.sg.batch, [req]) # update requires entity_id and data - req['request_type'] = 'update' - req['data'] = {} + req["request_type"] = "update" + req["data"] = {} self.assertRaises(api.ShotgunError, self.sg.batch, [req]) - del req['data'] - req['entity_id'] = 2334 + del req["data"] + req["entity_id"] = 2334 self.assertRaises(api.ShotgunError, self.sg.batch, [req]) # delete requires entity_id - req['request_type'] = 'delete' - del req['entity_id'] + req["request_type"] = "delete" + del req["entity_id"] self.assertRaises(api.ShotgunError, self.sg.batch, [req]) class TestServerCapabilities(unittest.TestCase): def test_no_server_version(self): - self.assertRaises(api.ShotgunError, api.shotgun.ServerCapabilities, 'host', {}) + self.assertRaises(api.ShotgunError, api.shotgun.ServerCapabilities, "host", {}) def test_bad_version(self): - '''test_bad_meta tests passing bad meta data type''' - self.assertRaises(api.ShotgunError, api.shotgun.ServerCapabilities, 'host', {'version': (0, 0, 0)}) + """test_bad_meta tests passing bad meta data type""" + self.assertRaises( + api.ShotgunError, + api.shotgun.ServerCapabilities, + "host", + {"version": (0, 0, 0)}, + ) def test_dev_version(self): - serverCapabilities = api.shotgun.ServerCapabilities('host', {'version': (3, 4, 0, 'Dev')}) + serverCapabilities = api.shotgun.ServerCapabilities( + "host", {"version": (3, 4, 0, "Dev")} + ) self.assertEqual(serverCapabilities.version, (3, 4, 0)) self.assertTrue(serverCapabilities.is_dev) - serverCapabilities = api.shotgun.ServerCapabilities('host', {'version': (2, 4, 0)}) + serverCapabilities = api.shotgun.ServerCapabilities( + "host", {"version": (2, 4, 0)} + ) self.assertEqual(serverCapabilities.version, (2, 4, 0)) self.assertFalse(serverCapabilities.is_dev) class TestClientCapabilities(unittest.TestCase): - def test_darwin(self): - self.assert_platform('Darwin', 'mac') + self.assert_platform("Darwin", "mac") def test_windows(self): - self.assert_platform('win32', 'windows') + self.assert_platform("win32", "windows") def test_linux(self): - self.assert_platform('Linux', 'linux') + self.assert_platform("Linux", "linux") def assert_platform(self, sys_ret_val, expected): platform = api.shotgun.sys.platform @@ -277,12 +299,12 @@ def test_no_platform(self): finally: api.shotgun.sys.platform = platform - @patch('shotgun_api3.shotgun.sys') + @patch("shotgun_api3.shotgun.sys") def test_py_version(self, mock_sys): major = 2 minor = 7 micro = 3 - mock_sys.version_info = (major, minor, micro, 'final', 0) + mock_sys.version_info = (major, minor, micro, "final", 0) expected_py_version = "%s.%s" % (major, minor) client_caps = api.shotgun.ClientCapabilities() self.assertEqual(client_caps.py_version, expected_py_version) @@ -290,26 +312,20 @@ def test_py_version(self, mock_sys): class TestFilters(unittest.TestCase): def test_empty(self): - expected = { - "logical_operator": "and", - "conditions": [] - } + expected = {"logical_operator": "and", "conditions": []} result = api.shotgun._translate_filters([], None) self.assertEqual(result, expected) def test_simple(self): - filters = [ - ["code", "is", "test"], - ["sg_status_list", "is", "ip"] - ] + filters = [["code", "is", "test"], ["sg_status_list", "is", "ip"]] expected = { "logical_operator": "or", "conditions": [ {"path": "code", "relation": "is", "values": ["test"]}, - {"path": "sg_status_list", "relation": "is", "values": ["ip"]} - ] + {"path": "sg_status_list", "relation": "is", "values": ["ip"]}, + ], } result = api.shotgun._translate_filters(filters, "any") @@ -320,20 +336,20 @@ def test_arrays(self): expected = { "logical_operator": "and", "conditions": [ - {"path": "code", "relation": "in", "values": ["test1", "test2", "test3"]} - ] + { + "path": "code", + "relation": "in", + "values": ["test1", "test2", "test3"], + } + ], } - filters = [ - ["code", "in", "test1", "test2", "test3"] - ] + filters = [["code", "in", "test1", "test2", "test3"]] result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) - filters = [ - ["code", "in", ["test1", "test2", "test3"]] - ] + filters = [["code", "in", ["test1", "test2", "test3"]]] result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) @@ -350,11 +366,11 @@ def test_nested(self): "filter_operator": "all", "filters": [ ["sg_status_list", "is", "hld"], - ["assets", "is", {"type": "Asset", "id": 9}] - ] - } - ] - } + ["assets", "is", {"type": "Asset", "id": 9}], + ], + }, + ], + }, ] expected = { @@ -369,13 +385,21 @@ def test_nested(self): { "logical_operator": "and", "conditions": [ - {"path": "sg_status_list", "relation": "is", "values": ["hld"]}, - {"path": "assets", "relation": "is", "values": [{"type": "Asset", "id": 9}]}, - ] - } - ] - } - ] + { + "path": "sg_status_list", + "relation": "is", + "values": ["hld"], + }, + { + "path": "assets", + "relation": "is", + "values": [{"type": "Asset", "id": 9}], + }, + ], + }, + ], + }, + ], } result = api.shotgun._translate_filters(filters, "all") @@ -383,27 +407,27 @@ def test_nested(self): def test_invalid(self): self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, [], "bogus") - self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, ["bogus"], "all") + self.assertRaises( + api.ShotgunError, api.shotgun._translate_filters, ["bogus"], "all" + ) - filters = [{ - "filter_operator": "bogus", - "filters": [] - }] + filters = [{"filter_operator": "bogus", "filters": []}] - self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, filters, "all") + self.assertRaises( + api.ShotgunError, api.shotgun._translate_filters, filters, "all" + ) - filters = [{ - "filters": [] - }] + filters = [{"filters": []}] - self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, filters, "all") + self.assertRaises( + api.ShotgunError, api.shotgun._translate_filters, filters, "all" + ) - filters = [{ - "filter_operator": "all", - "filters": {"bogus": "bogus"} - }] + filters = [{"filter_operator": "all", "filters": {"bogus": "bogus"}}] - self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, filters, "all") + self.assertRaises( + api.ShotgunError, api.shotgun._translate_filters, filters, "all" + ) class TestCerts(unittest.TestCase): @@ -420,10 +444,9 @@ class TestCerts(unittest.TestCase): ] def setUp(self): - self.sg = api.Shotgun('http://server_path', - 'script_name', - 'api_key', - connect=False) + self.sg = api.Shotgun( + "http://server_path", "script_name", "api_key", connect=False + ) # Get the location of the certs file self.certs = self.sg._get_certs_file(None) @@ -470,7 +493,12 @@ def test_httplib(self): certificate with httplib. """ # First check that we get an error when trying to connect to a known dummy bad URL - self.assertRaises(ssl_error_classes, self._check_url_with_sg_api_httplib2, self.bad_url, self.certs) + self.assertRaises( + ssl_error_classes, + self._check_url_with_sg_api_httplib2, + self.bad_url, + self.certs, + ) # Now check that the good urls connect properly using the certs for url in self.test_urls: @@ -483,12 +511,14 @@ def test_urlib(self): certificate with urllib. """ # First check that we get an error when trying to connect to a known dummy bad URL - self.assertRaises(urllib.error.URLError, self._check_url_with_urllib, self.bad_url) + self.assertRaises( + urllib.error.URLError, self._check_url_with_urllib, self.bad_url + ) # Now check that the good urls connect properly using the certs for url in self.test_urls: response = self._check_url_with_urllib(url) - assert (response is not None) + assert response is not None class TestMimetypesFix(unittest.TestCase): @@ -496,8 +526,10 @@ class TestMimetypesFix(unittest.TestCase): Makes sure that the mimetypes fix will be imported. """ - @patch('shotgun_api3.shotgun.sys') - def _test_mimetypes_import(self, platform, major, minor, patch_number, result, mock): + @patch("shotgun_api3.shotgun.sys") + def _test_mimetypes_import( + self, platform, major, minor, patch_number, result, mock + ): """ Mocks sys.platform and sys.version_info to test the mimetypes import code. """ @@ -517,5 +549,6 @@ def test_correct_mimetypes_imported(self): self._test_mimetypes_import("win32", 3, 0, 0, False) self._test_mimetypes_import("darwin", 2, 7, 0, False) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main()