diff --git a/.gitignore b/.gitignore index b2679c7..40bfdfd 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,4 @@ cython_debug/ # Names of the files where access token and expire seconds are stored access_token expire_seconds +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c9ed6d7..2295192 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,21 @@ repos: - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 26.1.0 hooks: - id: black - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 7.0.0 hooks: - id: isort name: isort args: [--profile, black, --skip, migrations] - repo: https://github.com/pycqa/flake8 - rev: "6.0.0" # pick a git hash / tag to point to + rev: "7.3.0" hooks: - id: flake8 + additional_dependencies: ["pyflakes>=3.2.0"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.2.0" + rev: "v1.19.1" hooks: - id: mypy args: [--no-strict-optional, --ignore-missing-imports] diff --git a/.vscode/settings.json b/.vscode/settings.json index f0f682a..df863d7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,10 +24,7 @@ "python.analysis.inlayHints.functionReturnTypes": true, "editor.formatOnSave": true, "isort.check": true, - "isort.args": [ - "--profile", - "black" - ], + "isort.args": ["--profile", "black"], "python.testing.pytestEnabled": true, "python.testing.pytestArgs": [ "tests", @@ -38,7 +35,7 @@ ], "[python]": { "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports": "explicit" }, "editor.defaultFormatter": "ms-python.black-formatter", "editor.codeLens": true @@ -50,6 +47,6 @@ "files.exclude": { "**/__pycache__": true, "**/.pytest_cache": true, - "**/.mypy*": true, - }, -} \ No newline at end of file + "**/.mypy*": true + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e8d0b11 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,122 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.3] - 2026-02-17 + +### Changed +- Allow calling streaming status with a settings object (support for transcriptions) + +## [0.2.2] - 2023-08-30 + +### Added +- Calendars component for interacting with Zoom calendar resources +- Tests for calendars component +- Example script for interacting with calendars + +### Changed +- Updated documentation with new calendar endpoints + +## [0.2.1] - 2023-08-29 + +### Changed +- Upgraded project dependencies +- Updated dev dependencies + +### Fixed +- Fixed test returning content on 204 status +- Removed debug logging from production code + +## [0.2.0] - 2023-07-12 + +### Added +- Zoom Rooms component for interacting with Zoom Rooms API +- Example script to interact with Zoom Rooms +- Tests for Rooms component +- Utility function to create dict from TypedDict with NotRequired fields + +### Changed +- Improved typed dict parameters to work with `from` and `to` date fields +- Renamed `generate_parameters_dict` function +- Simplified `get_project_dir()` function + +### Fixed +- Fixed typo in import inside example in README +- Fixed typo in CONTRIBUTING.md + +## [0.1.1] - 2023-06-01 + +### Added +- Retry mechanism for generating a new token on 401 responses +- Support for loading/saving access token from/to file system +- Poetry for dependency management + +### Changed +- Changed LICENSE to MIT +- Set minimum dependencies instead of strict versions +- Lowered minimum required requests version to 2.23.0 +- Added Pypi install instructions to README +- Published beta version on PyPI + +## [0.1.0] - 2023-05-30 + +### Changed +- Set minimum dependencies instead of strict versions + +## [0.0.6] - 2023-05-30 + +### Added +- Support for loading and saving access token from/to file + +## [0.0.5] - 2023-05-22 + +### Changed +- Lowered the minimum required requests version to 2.23.0 + +## [0.0.4] - 2023-05-15 + +### Added +- Retry mechanism on 401 unauthorized responses + +## [0.0.3] - 2023-05-05 + +### Added +- Resolution parameter support for live streams + +## [0.0.2] - 2023-05-05 + +### Added +- Meeting token endpoint (`get_meeting_token`) +- Updated workflows + +## [0.0.1] - 2023-05-05 + +### Added +- Initial release +- Server-to-Server OAuth token support +- `ZoomApiClient` with `init_from_env` and `init_from_dotenv` methods +- Users component with `get_user` and `get_user_meetings` endpoints +- Meetings component with `get_meeting` endpoint +- Meeting livestreams component +- Webinars component +- Webinar livestreams component +- Logging utilities +- Pre-commit hooks with mypy, isort, flake8, and black +- GitHub Actions for testing, pre-commit, and CodeQL analysis +- Code coverage reporting with codecov + +[0.2.3]: https://github.com/cern-vc/zoom-python-client/compare/v0.2.2...v0.2.3 +[0.2.2]: https://github.com/cern-vc/zoom-python-client/compare/v0.2.1...v0.2.2 +[0.2.1]: https://github.com/cern-vc/zoom-python-client/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/cern-vc/zoom-python-client/compare/v0.1.1...v0.2.0 +[0.1.1]: https://github.com/cern-vc/zoom-python-client/compare/v0.1.0...v0.1.1 +[0.1.0]: https://github.com/cern-vc/zoom-python-client/compare/v0.0.6...v0.1.0 +[0.0.6]: https://github.com/cern-vc/zoom-python-client/compare/0.0.5...v0.0.6 +[0.0.5]: https://github.com/cern-vc/zoom-python-client/compare/0.0.4...0.0.5 +[0.0.4]: https://github.com/cern-vc/zoom-python-client/compare/0.0.3...0.0.4 +[0.0.3]: https://github.com/cern-vc/zoom-python-client/compare/0.0.2...0.0.3 +[0.0.2]: https://github.com/cern-vc/zoom-python-client/compare/0.0.1...0.0.2 +[0.0.1]: https://github.com/cern-vc/zoom-python-client/releases/tag/0.0.1 diff --git a/README.md b/README.md index 2e452b2..c7da040 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ setup_logs(log_level=logging.DEBUG) 1. get webinar live stream 2. update webinar live stream -3. update webinar livestream status +3. update webinar livestream status - Following [Zoom documentation](https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/meetingLiveStreamStatusUpdate). ### **zoom rooms**: diff --git a/example/get_meeting_livestream.py b/example/get_meeting_livestream.py index b225fa1..f4e4079 100644 --- a/example/get_meeting_livestream.py +++ b/example/get_meeting_livestream.py @@ -9,7 +9,5 @@ MEETING_ID = os.environ["MEETING_ID"] -result = zoom_client.meeting_livestreams.get_livestream( - MEETING_ID, -) +result = zoom_client.meeting_livestreams.get_livestream(MEETING_ID) print(result) diff --git a/example/start_meeting_livestream.py b/example/start_meeting_livestream.py new file mode 100644 index 0000000..a91c8e3 --- /dev/null +++ b/example/start_meeting_livestream.py @@ -0,0 +1,26 @@ +import logging +import os + +from zoom_python_client.utils.logger import setup_logs +from zoom_python_client.zoom_api_client import ZoomApiClient + +setup_logs(log_level=logging.DEBUG) +zoom_client = ZoomApiClient.init_from_dotenv() +MEETING_ID = os.environ["MEETING_ID"] + + +result = zoom_client.meeting_livestreams.get_livestream(MEETING_ID) +print(result) + +settings = { + "active_speaker_name": False, + "display_name": "CERN_Webcast_Service", + "layout": "follow_host", + "close_caption": "embedded", +} +started = zoom_client.meeting_livestreams.update_livestream_status( + MEETING_ID, + "start", + settings=settings, +) +print(started) diff --git a/pyproject.toml b/pyproject.toml index 459df3c..2bf6ac0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "zoom-python-client" -version = "0.2.2" +version = "0.2.3" authors = ["Rene Fernandez Sanchez "] maintainers = [ "Rene Fernandez Sanchez ", diff --git a/requirements.txt b/requirements.txt index 4deb882..49c3108 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ python-dotenv==1.0.0 requests==2.31.0 -typing_extensions==4.7.1 \ No newline at end of file +typing_extensions==4.7.1 +typed-ast==1.5.5 \ No newline at end of file diff --git a/tests/zoom_python_client/client_components/meeting_livestreams/test_meeting_livestreams_component.py b/tests/zoom_python_client/client_components/meeting_livestreams/test_meeting_livestreams_component.py index f724f21..0e47bea 100644 --- a/tests/zoom_python_client/client_components/meeting_livestreams/test_meeting_livestreams_component.py +++ b/tests/zoom_python_client/client_components/meeting_livestreams/test_meeting_livestreams_component.py @@ -81,14 +81,29 @@ def test_update_meeting_livestreams_component_no_response(self): @responses.activate def test_update_meeting_livestreams_status_component(self): + settings = { + "active_speaker_name": False, + "display_name": "CERN_Webcast_Service", + "layout": "follow_host", + "close_caption": "embedded", + } responses.add( responses.PATCH, "http://localhost/meetings/12345/livestream/status", status=204, + match=[ + responses.matchers.json_params_matcher( + {"action": "start", "settings": settings} + ) + ], ) zoom_client = ZoomApiClient("aaa", "bbb", "ccc", "http://localhost") component = MeetingLiveStreamsComponent(zoom_client) - result = component.update_livestream_status("12345", "start") + result = component.update_livestream_status( + "12345", + "start", + settings=settings, + ) assert result @responses.activate diff --git a/tests/zoom_python_client/client_components/webinar_livestreams/test_webinar_livestreams_component.py b/tests/zoom_python_client/client_components/webinar_livestreams/test_webinar_livestreams_component.py index 2167310..d76acc3 100644 --- a/tests/zoom_python_client/client_components/webinar_livestreams/test_webinar_livestreams_component.py +++ b/tests/zoom_python_client/client_components/webinar_livestreams/test_webinar_livestreams_component.py @@ -81,14 +81,29 @@ def test_update_webinar_livestreams_component_no_response(self): @responses.activate def test_update_webinar_livestreams_status_component(self): + settings = { + "active_speaker_name": False, + "display_name": "CERN_Webcast_Service", + "layout": "follow_host", + "close_caption": "embedded", + } responses.add( responses.PATCH, "http://localhost/webinars/12345/livestream/status", status=204, + match=[ + responses.matchers.json_params_matcher( + {"action": "start", "settings": settings} + ) + ], ) zoom_client = ZoomApiClient("aaa", "bbb", "ccc", "http://localhost") component = WebinarLiveStreamsComponent(zoom_client) - result = component.update_livestream_status("12345", "start") + result = component.update_livestream_status( + "12345", + "start", + settings=settings, + ) assert result @responses.activate diff --git a/zoom_python_client/client_components/meeting_livestreams/meeting_livestreams_component.py b/zoom_python_client/client_components/meeting_livestreams/meeting_livestreams_component.py index 6262c62..23734ad 100644 --- a/zoom_python_client/client_components/meeting_livestreams/meeting_livestreams_component.py +++ b/zoom_python_client/client_components/meeting_livestreams/meeting_livestreams_component.py @@ -1,5 +1,7 @@ from json import JSONDecodeError -from typing import TypedDict +from typing import Any, Dict, Optional + +from typing_extensions import NotRequired, TypedDict from zoom_python_client.zoom_auth_api.zoom_auth_api_client import ZoomAuthApiClientError from zoom_python_client.zoom_client_interface import ZoomClientInterface @@ -14,6 +16,7 @@ class LiveStreamDict(TypedDict): class LiveStreamStatusDict(TypedDict): action: str + settings: NotRequired[Dict[str, Any]] class MeetingLiveStreamsComponent: @@ -41,9 +44,16 @@ def update_livestream(self, meeting_id: str, data: LiveStreamDict) -> bool: return True return False - def update_livestream_status(self, meeting_id: str, action: str) -> bool: + def update_livestream_status( + self, + meeting_id: str, + action: str, + settings: Optional[Dict[str, Any]] = None, + ) -> bool: api_path = f"/meetings/{meeting_id}/livestream/status" data: LiveStreamStatusDict = {"action": action} + if settings is not None: + data["settings"] = settings response = self.client.make_patch_request(api_path, data) if response and response.status_code == 204: return True diff --git a/zoom_python_client/client_components/webinar_livestreams/webinar_livestreams_component.py b/zoom_python_client/client_components/webinar_livestreams/webinar_livestreams_component.py index 9f5b5c8..8702afc 100644 --- a/zoom_python_client/client_components/webinar_livestreams/webinar_livestreams_component.py +++ b/zoom_python_client/client_components/webinar_livestreams/webinar_livestreams_component.py @@ -1,5 +1,7 @@ from json import JSONDecodeError -from typing import TypedDict +from typing import Any, Dict, Optional + +from typing_extensions import NotRequired, TypedDict from zoom_python_client.zoom_auth_api.zoom_auth_api_client import ZoomAuthApiClientError from zoom_python_client.zoom_client_interface import ZoomClientInterface @@ -14,6 +16,7 @@ class LiveStreamDict(TypedDict): class LiveStreamStatusDict(TypedDict): action: str + settings: NotRequired[Dict[str, Any]] class WebinarLiveStreamsComponent: @@ -38,9 +41,16 @@ def update_livestream(self, meeting_id: str, data: LiveStreamDict) -> bool: return True return False - def update_livestream_status(self, meeting_id: str, action: str) -> bool: + def update_livestream_status( + self, + meeting_id: str, + action: str, + settings: Optional[Dict[str, Any]] = None, + ) -> bool: api_path = f"/webinars/{meeting_id}/livestream/status" data: LiveStreamStatusDict = {"action": action} + if settings is not None: + data["settings"] = settings response = self.client.make_patch_request(api_path, data) if response.status_code == 204: return True diff --git a/zoom_python_client/utils/typed_dict_parameters.py b/zoom_python_client/utils/typed_dict_parameters.py index b958304..889762c 100644 --- a/zoom_python_client/utils/typed_dict_parameters.py +++ b/zoom_python_client/utils/typed_dict_parameters.py @@ -6,7 +6,7 @@ class DataType(TypedDict, total=False): - ... + pass def generate_parameters_dict(data: DataType) -> dict: