Skip to content

Commit 59b8f05

Browse files
authored
SG-38306 Python2 Removal - Part 7 - various (#404)
1 parent c3888df commit 59b8f05

File tree

6 files changed

+31
-107
lines changed

6 files changed

+31
-107
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Integration and unit tests are provided.
3737
- (Note: Running `pip install -r tests/ci_requirements.txt` will install this package)
3838
- A `tests/config` file (you can copy an example from `tests/example_config`).
3939
- Tests can be run individually like this: `nosetests --config="nose.cfg" tests/test_client.py`
40-
- Make sure to not forget the `--config="nose.cfg"` option. This option tells nose to use our config file. This will exclude python 2- and 3-specific files in the `/lib` directory, preventing a failure from being reported by nose for compilation due to incompatible syntax in those files.
40+
- Make sure to not forget the `--config="nose.cfg"` option. This option tells nose to use our config file.
4141
- `test_client` and `tests_unit` use mock server interaction and do not require a Flow Production Tracking instance to be available (no modifications to `tests/config` are necessary).
4242
- `test_api` and `test_api_long` *do* require a Flow Production Tracking instance, with a script key available for the tests. The server and script user values must be supplied in the `tests/config` file. The tests will add test data to your server based on information in your config. This data will be manipulated by the tests, and should not be used for other purposes.
4343
- To run all of the tests, use the shell script `run-tests`.

azure-pipelines-templates/run-tests.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ parameters:
3333

3434
jobs:
3535
# The job will be named after the OS and Azure will suffix the strategy to make it unique
36-
# so we'll have a job name "Windows Python 2.7" for example. What's a strategy? Strategies are the
37-
# name of the keys under the strategy.matrix scope. So for each OS we'll have "<OS> Python 2.7" and
38-
# "<OS> Python 3.7".
36+
# so we'll have a job name "Windows Python 3.9" for example. What's a strategy? Strategies are the
37+
# name of the keys under the strategy.matrix scope. So for each OS we'll have "<OS> Python 3.9" and
38+
# "<OS> Python 3.10".
3939
- job: ${{ parameters.name }}
4040
pool:
4141
vmImage: ${{ parameters.vm_image }}
@@ -68,8 +68,8 @@ jobs:
6868
versionSpec: '$(python.version)'
6969
addToPath: True
7070

71-
# Install all dependencies needed for running the tests. This command is good for
72-
# Python 2 and 3, but also for all OSes
71+
# Install all dependencies needed for running the tests. This command is good
72+
# for all OSes
7373
- script: |
7474
python -m pip install --upgrade pip setuptools wheel
7575
python -m pip install -r tests/ci_requirements.txt

shotgun_api3/shotgun.py

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import json
3636
import http.client # Used for secure file upload
3737
import http.cookiejar # used for attachment upload
38-
import io # used for attachment upload
38+
import io
3939
import logging
4040
import mimetypes
4141
import os
@@ -49,14 +49,14 @@
4949
import urllib.parse
5050
import urllib.request
5151
import uuid # used for attachment upload
52+
import xml.etree.ElementTree
5253

5354
# Import Error and ResponseError (even though they're unused in this file) since they need
5455
# to be exposed as part of the API.
5556
from xmlrpc.client import Error, ProtocolError, ResponseError # noqa
5657

5758
# Python 2/3 compatibility
5859
from .lib import six
59-
from .lib import sgsix
6060
from .lib import sgutils
6161
from .lib.httplib2 import Http, ProxyInfo, socks
6262
from .lib.sgtimezone import SgTimezone
@@ -329,7 +329,7 @@ class ClientCapabilities(object):
329329
``windows``, or ``None`` (if the current platform couldn't be determined).
330330
:ivar str local_path_field: The PTR field used for local file paths. This is calculated using
331331
the value of ``platform``. Ex. ``local_path_mac``.
332-
:ivar str py_version: Simple version of Python executable as a string. Eg. ``2.7``.
332+
:ivar str py_version: Simple version of Python executable as a string. Eg. ``3.9``.
333333
:ivar str ssl_version: Version of OpenSSL installed. Eg. ``OpenSSL 1.0.2g 1 Mar 2016``. This
334334
info is only available in Python 2.7+ if the ssl module was imported successfully.
335335
Defaults to ``unknown``
@@ -567,18 +567,6 @@ def __init__(
567567
:class:`~shotgun_api3.MissingTwoFactorAuthenticationFault` will be raised if the
568568
``auth_token`` is invalid.
569569
.. todo: Add this info to the Authentication section of the docs
570-
571-
.. note:: A note about proxy connections: If you are using Python <= v2.6.2, HTTPS
572-
connections through a proxy server will not work due to a bug in the :mod:`urllib2`
573-
library (see http://bugs.python.org/issue1424152). This will affect upload and
574-
download-related methods in the Shotgun API (eg. :meth:`~shotgun_api3.Shotgun.upload`,
575-
:meth:`~shotgun_api3.Shotgun.upload_thumbnail`,
576-
:meth:`~shotgun_api3.Shotgun.upload_filmstrip_thumbnail`,
577-
:meth:`~shotgun_api3.Shotgun.download_attachment`. Normal CRUD methods for passing JSON
578-
data should still work fine. If you cannot upgrade your Python installation, you can see
579-
the patch merged into Python v2.6.3 (http://hg.python.org/cpython/rev/0f57b30a152f/) and
580-
try and hack it into your installation but YMMV. For older versions of Python there
581-
are other patches that were proposed in the bug report that may help you as well.
582570
"""
583571

584572
# verify authentication arguments
@@ -617,13 +605,7 @@ def __init__(
617605
if script_name is not None or api_key is not None:
618606
raise ValueError("cannot provide an auth_code with script_name/api_key")
619607

620-
# Can't use 'all' with python 2.4
621-
if (
622-
len(
623-
[x for x in [session_token, script_name, api_key, login, password] if x]
624-
)
625-
== 0
626-
):
608+
if not any([session_token, script_name, api_key, login, password]):
627609
if connect:
628610
raise ValueError(
629611
"must provide login/password, session_token or script_name/api_key"
@@ -2879,8 +2861,7 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No
28792861
This parameter exists only for backwards compatibility for scripts specifying
28802862
the parameter with keywords.
28812863
:returns: If ``file_path`` is provided, returns the path to the file on disk. If
2882-
``file_path`` is ``None``, returns the actual data of the file, as str in Python 2 or
2883-
bytes in Python 3.
2864+
``file_path`` is ``None``, returns the actual data of the file, as bytes.
28842865
:rtype: str | bytes
28852866
"""
28862867
# backwards compatibility when passed via keyword argument
@@ -2941,12 +2922,13 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No
29412922
]
29422923

29432924
if body:
2944-
xml = "".join(body)
2945-
# Once python 2.4 support is not needed we can think about using
2946-
# elementtree. The doc is pretty small so this shouldn't be an issue.
2947-
match = re.search("<Message>(.*)</Message>", xml)
2948-
if match:
2949-
err += " - %s" % (match.group(1))
2925+
try:
2926+
root = xml.etree.ElementTree.fromstring("".join(body))
2927+
message_elem = root.find(".//Message")
2928+
if message_elem is not None and message_elem.text:
2929+
err = f"{err} - {message_elem.text}"
2930+
except xml.etree.ElementTree.ParseError:
2931+
err = f"{err}\n{''.join(body)}\n"
29502932
elif e.code == 409 or e.code == 410:
29512933
# we may be dealing with a file that is pending/failed a malware scan, e.g:
29522934
# 409: This file is undergoing a malware scan, please try again in a few minutes
@@ -4693,17 +4675,12 @@ class FormPostHandler(urllib.request.BaseHandler):
46934675
handler_order = urllib.request.HTTPHandler.handler_order - 10 # needs to run first
46944676

46954677
def http_request(self, request):
4696-
# get_data was removed in 3.4. since we're testing against 3.6 and
4697-
# 3.7, this should be sufficient.
4698-
if six.PY3:
4699-
data = request.data
4700-
else:
4701-
data = request.get_data()
4678+
data = request.data
47024679
if data is not None and not isinstance(data, str):
47034680
files = []
47044681
params = []
47054682
for key, value in data.items():
4706-
if isinstance(value, sgsix.file_types):
4683+
if isinstance(value, io.IOBase):
47074684
files.append((key, value))
47084685
else:
47094686
params.append((key, value))
@@ -4714,12 +4691,8 @@ def http_request(self, request):
47144691
boundary, data = self.encode(params, files)
47154692
content_type = "multipart/form-data; boundary=%s" % boundary
47164693
request.add_unredirected_header("Content-Type", content_type)
4717-
# add_data was removed in 3.4. since we're testing against 3.6 and
4718-
# 3.7, this should be sufficient.
4719-
if six.PY3:
4720-
request.data = data
4721-
else:
4722-
request.add_data(data)
4694+
request.data = data
4695+
47234696
return request
47244697

47254698
def encode(self, params, files, boundary=None, buffer=None):

tests/base.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,10 @@ def _mock_http(self, data, headers=None, status=None):
176176
return
177177

178178
if not isinstance(data, str):
179-
if six.PY2:
180-
data = json.dumps(data, ensure_ascii=False, encoding="utf-8")
181-
else:
182-
data = json.dumps(
183-
data,
184-
ensure_ascii=False,
185-
)
179+
data = json.dumps(
180+
data,
181+
ensure_ascii=False,
182+
)
186183

187184
resp_headers = {
188185
"cache-control": "no-cache",

tests/test_api.py

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@
3232
from shotgun_api3.lib import six
3333
from shotgun_api3.lib.httplib2 import Http
3434

35-
# To mock the correct exception when testion on Python 2 and 3, use the
36-
# ShotgunSSLError variable from sgsix that contains the appropriate exception
37-
# class for the current Python version.
38-
from shotgun_api3.lib.sgsix import ShotgunSSLError
39-
4035
import shotgun_api3
4136

4237
from . import base
@@ -272,44 +267,6 @@ def test_upload_download(self):
272267
"sg_uploaded_movie",
273268
tag_list="monkeys, everywhere, send, help",
274269
)
275-
if six.PY2:
276-
# In Python2, make sure that non-utf-8 encoded paths raise when they
277-
# can't be converted to utf-8. For Python3, we'll skip these tests
278-
# since string encoding is handled differently.
279-
280-
# We need to touch the file we're going to test with first. We can't
281-
# bundle a file with this filename in the repo due to some pip install
282-
# problems on Windows. Note that the path below is utf-8 encoding of
283-
# what we'll eventually encode as shift-jis.
284-
file_path_s = os.path.join(this_dir, "./\xe3\x81\x94.shift-jis")
285-
file_path_u = file_path_s.decode("utf-8")
286-
287-
with open(
288-
file_path_u if sys.platform.startswith("win") else file_path_s, "w"
289-
) as fh:
290-
fh.write("This is just a test file with some random data in it.")
291-
292-
self.assertRaises(
293-
shotgun_api3.ShotgunError,
294-
self.sg.upload,
295-
"Version",
296-
self.version["id"],
297-
file_path_u.encode("shift-jis"),
298-
"sg_uploaded_movie",
299-
tag_list="monkeys, everywhere, send, help",
300-
)
301-
302-
# But it should work in all cases if a unicode string is used.
303-
self.sg.upload(
304-
"Version",
305-
self.version["id"],
306-
file_path_u,
307-
"sg_uploaded_movie",
308-
tag_list="monkeys, everywhere, send, help",
309-
)
310-
311-
# cleanup
312-
os.remove(file_path_u)
313270

314271
# cleanup
315272
os.remove(file_path)
@@ -2265,7 +2222,7 @@ def my_side_effect2(*args, **kwargs):
22652222
@unittest.mock.patch("shotgun_api3.shotgun.Http.request")
22662223
def test_sha2_error(self, mock_request):
22672224
# Simulate the exception raised with SHA-2 errors
2268-
mock_request.side_effect = ShotgunSSLError(
2225+
mock_request.side_effect = ssl.SSLError(
22692226
"[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 "
22702227
"encoding routines:ASN1_item_verify: unknown message digest "
22712228
"algorithm"
@@ -2292,7 +2249,7 @@ def test_sha2_error(self, mock_request):
22922249

22932250
try:
22942251
self.sg.info()
2295-
except ShotgunSSLError:
2252+
except ssl.SSLError:
22962253
# ensure the api has reset the values in the correct fallback behavior
22972254
self.assertTrue(self.sg.config.no_ssl_validation)
22982255
self.assertTrue(shotgun_api3.shotgun.NO_SSL_VALIDATION)
@@ -2305,7 +2262,7 @@ def test_sha2_error(self, mock_request):
23052262
@unittest.mock.patch("shotgun_api3.shotgun.Http.request")
23062263
def test_sha2_error_with_strict(self, mock_request):
23072264
# Simulate the exception raised with SHA-2 errors
2308-
mock_request.side_effect = ShotgunSSLError(
2265+
mock_request.side_effect = ssl.SSLError(
23092266
"[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 "
23102267
"encoding routines:ASN1_item_verify: unknown message digest "
23112268
"algorithm"
@@ -2322,7 +2279,7 @@ def test_sha2_error_with_strict(self, mock_request):
23222279

23232280
try:
23242281
self.sg.info()
2325-
except ShotgunSSLError:
2282+
except ssl.SSLError:
23262283
# ensure the api has NOT reset the values in the fallback behavior because we have
23272284
# set the env variable to force validation
23282285
self.assertFalse(self.sg.config.no_ssl_validation)

tests/test_client.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -651,10 +651,7 @@ def _assert_decode_resonse(self, ensure_ascii, data):
651651
connect=False,
652652
)
653653

654-
if six.PY3:
655-
j = json.dumps(d, ensure_ascii=ensure_ascii)
656-
else:
657-
j = json.dumps(d, ensure_ascii=ensure_ascii, encoding="utf-8")
654+
j = json.dumps(d, ensure_ascii=ensure_ascii)
658655
self.assertEqual(d, sg._decode_response(headers, j))
659656

660657
headers["content-type"] = "text/javascript"

0 commit comments

Comments
 (0)