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
6 changes: 6 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ Flow Production Tracking Python API Changelog

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

v3.9.1 (2025 Sep 17)
===================

- Add ``export_page`` method to Shotgun class.
- Update documentation.

v3.9.0 (2025 Sep 10)
===================

Expand Down
2 changes: 2 additions & 0 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ The documentation for all of the methods you'll need in your scripts lives in he
Shotgun.work_schedule_read
Shotgun.work_schedule_update
Shotgun.preferences_read
Shotgun.export_page

.. rubric:: Working With Files

Expand Down Expand Up @@ -150,6 +151,7 @@ also some specialized convenience methods for accessing particular types of info
.. automethod:: Shotgun.work_schedule_read
.. automethod:: Shotgun.work_schedule_update
.. automethod:: Shotgun.preferences_read
.. automethod:: Shotgun.export_page

Working With Files
==================
Expand Down
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.9.0",
version="3.9.1",
description="Flow Production Tracking Python API",
long_description=readme,
author="Autodesk",
Expand Down
26 changes: 25 additions & 1 deletion shotgun_api3/shotgun.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@

# ----------------------------------------------------------------------------
# Version
__version__ = "3.9.0"
__version__ = "3.9.1"

# ----------------------------------------------------------------------------
# Errors
Expand Down Expand Up @@ -1826,6 +1826,30 @@ def work_schedule_update(

return self._call_rpc("work_schedule_update", params)

def export_page(self, page_id, format, layout_name=None):
"""
Export the specified page to the given format.
This method allows you to export a page to CSV.
Respective layout or page should be marked as API Exportable in the Shotgun UI. << link to the web documentaion>>

If ``layout_name`` is not passed in, the default layout name will be used.

>>> sg.export_page(12345, "csv", layout_name="My Layout")
"ID,Name,Status\\n1,Shot 001,ip\\n2, Shot 002,rev\\n"
>>> sg.export_page(12345, "csv")
"ID,Name,Status\\n1,Shot 001,ip\\n2,Shot 002,rev\\n"

:param int page_id: The ID of the page to export.
:param str format: The format to export the page to. Supported format is ``"csv"``.
:param str layout_name: optional layout name. This should be the name of the layout seen in the Shotgun UI.
:returns: string containing data of the given page.
:rtype: string
"""

params = dict(format=format, page_id=page_id, layout_name=layout_name)

return self._call_rpc("export_page", params)

def follow(self, user, entity):
"""
Add the entity to the user's followed entities.
Expand Down
77 changes: 77 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,83 @@ def test_include_archived_projects(self):
self.sg.update("Project", self.project["id"], {"archived": False})


class TestExportPage(base.LiveTestBase):

def setUp(self):
super(TestExportPage, self).setUp("HumanUser")

def test_export_page_unavailable(self):
"""
Test export_page raises when report does not exist.
"""
if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22):
return

page_entity = self.sg.create("Page", {"entity_type": "Shot"})

with self.assertRaises(Exception) as cm:
self.sg.export_page(page_entity["id"], "csv")
self.assertIn(
f"Export for Page id={page_entity['id']} not available", str(cm.exception)
)

with self.assertRaises(Exception) as cm:
self.sg.export_page(page_entity["id"], "csv", layout_name="My Layout")
self.assertIn(
f"Export for Page id={page_entity['id']} not available", str(cm.exception)
)

def test_export_page_format_missing(self):
"""
Test export_page raises for invalid format.
"""
if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22):
return

with self.assertRaises(Exception) as cm:
self.sg.export_page(11, None)
self.assertIn("'format' missing", str(cm.exception))

with self.assertRaises(Exception) as cm:
self.sg.export_page(11, None, layout_name="My Layout")
self.assertIn("'format' missing", str(cm.exception))

def test_export_page_missing_page_id(self):
"""
Test export_page raises for missing page id.
"""
if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22):
return

with self.assertRaises(Exception) as cm:
self.sg.export_page(None, "csv")
self.assertIn("'page_id' missing", str(cm.exception))

with self.assertRaises(Exception) as cm:
self.sg.export_page(None, "csv", layout_name="My Layout")
self.assertIn("'page_id' missing", str(cm.exception))

@unittest.mock.patch("shotgun_api3.shotgun.Http.request")
def test_export_page_without_layout_name(self, mock_request):
Copy link
Author

@kuldeepgudekar kuldeepgudekar Sep 16, 2025

Choose a reason for hiding this comment

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

We have not exposed the ExportPage entity so that we can't create it from self.sg, we can only query it. We can create it from FPTR ui calls.

"""
Test export_page works when layout_name is not provided.
"""

if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22):
return

# Mock the underlying Http.request to return CSV content with appropriate headers
csv_body = "ID,Name,Status\n1,Shot 001,ip\n2,Shot 002,rev\n"
response = unittest.mock.MagicMock(name="response mock")
response.status = 200
response.reason = "OK"
response.items.return_value = [("content-type", "text/csv; charset=utf-8")]
mock_request.return_value = (response, csv_body)
result = self.sg.export_page(11, "csv")
self.assertIsInstance(result, str)
self.assertTrue(result.startswith("ID,Name,Status"))


class TestFollow(base.LiveTestBase):

def test_follow_unfollow(self):
Expand Down
Loading