diff --git a/HISTORY.rst b/HISTORY.rst index 30b68b79..24ba7d11 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -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) =================== diff --git a/docs/reference.rst b/docs/reference.rst index 6b16a37b..f00bda7a 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -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 @@ -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 ================== diff --git a/setup.py b/setup.py index 64766109..3b116f55 100644 --- a/setup.py +++ b/setup.py @@ -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", diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 67996c9f..e1e7fae1 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -81,7 +81,7 @@ # ---------------------------------------------------------------------------- # Version -__version__ = "3.9.0" +__version__ = "3.9.1" # ---------------------------------------------------------------------------- # Errors @@ -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. diff --git a/tests/test_api.py b/tests/test_api.py index 19b738f6..f39248df 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -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): + """ + 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):