Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Flow Production Tracking Python API Changelog

Here you can see the full list of changes between each Python API release.

v3.10.2 (2026 Jun 26)
=====================

- Update bundled certifi to version 2026.6.17.

v3.10.1 (2026 Feb 10)
=====================

Expand Down
12 changes: 10 additions & 2 deletions developer/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@

# Updating HTTPLib2

The API comes with a copy of the `httplib2` inside the `shotgun_api3/lib` folder. To update the copy to a more recent version of the API, you can run the `update_httplib2.py` script at the root of this repository like this:
The API comes with a copy of the `httplib2` inside the `shotgun_api3/lib` folder. To update the copy to a more recent version of the API, you can run the `update_httplib2.py` script from the `developer/` directory like this:

python update_httplib2.py vX.Y.Z

where `vX.Y.Z` is a release found on `httplib2`'s [release page](https://github.com/httplib2/httplib2/releases).
where `vX.Y.Z` is a release found on `httplib2`'s [tags page](https://github.com/httplib2/httplib2/tags).

# Updating Certifi

The API comes with a copy of `certifi` inside the `shotgun_api3/lib` folder. To update the copy to a more recent version of the API, you can run the `update_certifi.py` script from the `developer/` directory like this:

python update_certifi.py YYYY.MM.DD

where `YYYY.MM.DD` is a release found on `certifi`'s [release page](https://pypi.org/project/certifi/#history).


# Release process
Expand Down
113 changes: 113 additions & 0 deletions developer/update_certifi.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you move this script to the developer folder, please like in tk-core.
And same for update_httplib2.py if that makes sense.

Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/usr/bin/env python3

"""
Updates the bundled certifi module.

Run as "./update_certifi.py YYYY.MM.DD" to get a specific release from PyPI.
"""

import pathlib
import re
import shutil
import subprocess

Check notice on line 12 in developer/update_certifi.py

View check run for this annotation

ShotGrid Chorus / security/bandit

B404: blacklist

Consider possible security implications associated with the subprocess module. secure coding id: SEC-PY-INV-003.
import sys
import tempfile


class Utilities:
def download_wheel(self, version, dest_dir):
"""Download the certifi wheel from PyPI."""
print(f"Downloading certifi {version}")
subprocess.check_output(
[
"pip",
"download",
f"certifi=={version}",
"--no-deps",
"--index-url",
"https://pypi.org/simple/",
"-d",
str(dest_dir),
]
)

Check notice on line 32 in developer/update_certifi.py

View check run for this annotation

ShotGrid Chorus / security/bandit

B607: start_process_with_partial_path

Starting a process with a partial executable path secure coding id: SEC-PY-INV-003.

Check notice on line 32 in developer/update_certifi.py

View check run for this annotation

ShotGrid Chorus / security/bandit

B603: subprocess_without_shell_equals_true

subprocess call - check for execution of untrusted input. secure coding id: SEC-PY-INV-003.

def unzip_archive(self, file_path, temp_dir):
"""Unzip in a temp dir."""
print(f"Unzipping {file_path.name}")
subprocess.check_output(["unzip", str(file_path), "-d", str(temp_dir)])

Check notice on line 37 in developer/update_certifi.py

View check run for this annotation

ShotGrid Chorus / security/bandit

B607: start_process_with_partial_path

Starting a process with a partial executable path secure coding id: SEC-PY-INV-003.

Check notice on line 37 in developer/update_certifi.py

View check run for this annotation

ShotGrid Chorus / security/bandit

B603: subprocess_without_shell_equals_true

subprocess call - check for execution of untrusted input. secure coding id: SEC-PY-INV-003.

def remove_folder(self, path):
"""Remove a folder recursively."""
print(f"Removing the folder {path}")
shutil.rmtree(path, ignore_errors=True)

def git_remove(self, target):
print(f"Removing {target} in git.")
try:
subprocess.check_output(["git", "rm", "-rf"] + target)

Check notice on line 47 in developer/update_certifi.py

View check run for this annotation

ShotGrid Chorus / security/bandit

B603: subprocess_without_shell_equals_true

subprocess call - check for execution of untrusted input. secure coding id: SEC-PY-INV-003.
except Exception:
pass

Check notice on line 49 in developer/update_certifi.py

View check run for this annotation

ShotGrid Chorus / security/bandit

B110: try_except_pass

Try, Except, Pass detected. secure coding id: SEC-PY-ERR-001.

def copy_folder(self, source, target):
"""Copy a folder recursively."""
shutil.copytree(source, target)

def update_requirements(self, req_file, version):
"""Update the certifi version pin in requirements.txt."""
content = req_file.read_text()
content = re.sub(r"certifi==[\d.]+", f"certifi=={version}", content)
req_file.write_text(content)


def main(temp_path, repo_root, version):
certifi_dir = repo_root / "shotgun_api3" / "lib" / "certifi"
req_file = repo_root / "shotgun_api3" / "lib" / "requirements.txt"

utilities = Utilities()

# Download the wheel from PyPI
utilities.download_wheel(version, temp_path)

# Find the downloaded wheel file
wheels = list(temp_path.glob("certifi-*.whl"))
if not wheels:
raise RuntimeError("No certifi wheel found after download")

# Unzip into a temp dir
unzipped = temp_path / "unzipped"
unzipped.mkdir()
utilities.unzip_archive(wheels[0], unzipped)

# Remove old certifi from git and disk
utilities.git_remove([str(certifi_dir)])
utilities.remove_folder(certifi_dir)

# Copy new certifi into place (only the certifi/ package, not .dist-info)
print("Copying new version of certifi")
utilities.copy_folder(str(unzipped / "certifi"), str(certifi_dir))

# Update requirements.txt version pin
print("Updating requirements.txt")
utilities.update_requirements(req_file, version)

# Stage changes
print("Adding to git")
subprocess.check_output(
["git", "add", str(certifi_dir), str(req_file)]
) # nosec B607

Check notice on line 97 in developer/update_certifi.py

View check run for this annotation

ShotGrid Chorus / security/bandit

B603: subprocess_without_shell_equals_true

subprocess call - check for execution of untrusted input. secure coding id: SEC-PY-INV-003.


def find_repo_root():
path = pathlib.Path(__file__).resolve()
for parent in [path, *path.parents]:
if (parent / ".git").exists():
return parent
raise RuntimeError("Could not find repo root (no .git directory found)")


if __name__ == "__main__":
try:
temp_path = pathlib.Path(tempfile.mkdtemp())
main(temp_path, find_repo_root(), sys.argv[1])
finally:
shutil.rmtree(temp_path)
18 changes: 15 additions & 3 deletions update_httplib2.py → developer/update_httplib2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
Updates the httplib2 module.

Run as "./upgrade_httplib2.py vX.Y.Z" to get a specific release from github.
Run as "./update_httplib2.py vX.Y.Z" to get a specific release from github.
"""

import pathlib
Expand Down Expand Up @@ -93,7 +93,11 @@ def main(temp_path, repo_root, version):
# Copies a new version into place.
print("Copying new version of httplib2")
root_folder = unzipped_folder / f"httplib2-{version[1:]}"
utilities.copy_folder(str(root_folder / "python3" / "httplib2"), httplib2_dir)
# Older releases had python3/httplib2/; newer ones have httplib2/ at root.
python3_path = root_folder / "python3" / "httplib2"
flat_path = root_folder / "httplib2"
src_path = python3_path if python3_path.exists() else flat_path
utilities.copy_folder(str(src_path), httplib2_dir)
Comment on lines +96 to +100

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

When I moved the scripts I tested them out to make sure they are still usable, and found that since v0.30., httplib2 no longer has a python3 folder and just has it at root.

I didn't upgrade httplib2 here to not add too much to this PR.

utilities.remove_folder(f"{httplib2_dir}/test")

# Patches the httplib2 imports so they are relative instead of absolute.
Expand All @@ -106,9 +110,17 @@ def main(temp_path, repo_root, version):
subprocess.check_output(["git", "add", str(httplib2_dir)]) # nosec B607


def find_repo_root():
path = pathlib.Path(__file__).resolve()
for parent in [path, *path.parents]:
if (parent / ".git").exists():
return parent
raise RuntimeError("Could not find repo root (no .git directory found)")


if __name__ == "__main__":
try:
temp_path = pathlib.Path(tempfile.mkdtemp())
main(temp_path, pathlib.Path(__file__).parent, sys.argv[1])
main(temp_path, find_repo_root(), sys.argv[1])
finally:
shutil.rmtree(temp_path)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

setup(
name="shotgun_api3",
version="3.10.0",
version="3.10.2",
description="Flow Production Tracking Python API",
long_description=readme,
author="Autodesk",
Expand Down
2 changes: 1 addition & 1 deletion shotgun_api3/lib/certifi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .core import contents, where

__all__ = ["contents", "where"]
__version__ = "2026.01.04"
__version__ = "2026.06.17"
Loading