diff --git a/.github/workflows/test_garf_youtube_data_api.yaml b/.github/workflows/test_garf_youtube.yaml similarity index 56% rename from .github/workflows/test_garf_youtube_data_api.yaml rename to .github/workflows/test_garf_youtube.yaml index ca1d5c6..78372c5 100644 --- a/.github/workflows/test_garf_youtube_data_api.yaml +++ b/.github/workflows/test_garf_youtube.yaml @@ -1,11 +1,11 @@ -name: Run e2e tests for garf-youtube-data-api +name: Run tests for garf-youtube on: workflow_dispatch: pull_request: branches: [main] paths: - - 'libs/community/google/youtube/youtube-data-api/**' + - 'libs/community/google/youtube/**' env: UV_SYSTEM_PYTHON: 1 @@ -26,14 +26,24 @@ jobs: enable-cache: true - name: Install dependencies run: | - uv pip install pytest python-dotenv - - name: Test ${{ matrix.library }} + - name: Run unit tests run: | uv pip install -e libs/core/.[all] uv pip install -e libs/io/.[test,all] uv pip install -e libs/executors/.[all] - cd libs/community/google/youtube/youtube-data-api/ - uv pip install -e . + cd libs/community/google/youtube/ + uv pip install -e .[test] + pytest tests/unit + env: + GARF_YOUTUBE_DATA_API_KEY: ${{ secrets.GARF_YOUTUBE_DATA_API_KEY }} + YOUTUBE_ID: ${{ secrets.YOUTUBE_ID }} + - name: Run end-to-end tests + run: | + uv pip install -e libs/core/.[all] + uv pip install -e libs/io/.[test,all] + uv pip install -e libs/executors/.[all] + cd libs/community/google/youtube/ + uv pip install -e .[test] pytest tests/e2e env: GARF_YOUTUBE_DATA_API_KEY: ${{ secrets.GARF_YOUTUBE_DATA_API_KEY }} diff --git a/libs/community/google/youtube/youtube-data-api/README.md b/libs/community/google/youtube/README.md similarity index 55% rename from libs/community/google/youtube/youtube-data-api/README.md rename to libs/community/google/youtube/README.md index 0ab7cd9..1daaebd 100644 --- a/libs/community/google/youtube/youtube-data-api/README.md +++ b/libs/community/google/youtube/README.md @@ -7,20 +7,39 @@ ## Prerequisites +### YouTube Data API + * [YouTube Data API](https://console.cloud.google.com/apis/library/youtube.googleapis.com) enabled. * [API key](https://support.google.com/googleapi/answer/6158862?hl=en) to access to access YouTube Data API. > Once generated expose API key as `export GARF_YOUTUBE_DATA_API_KEY=` +### YouTube Reporting API +* [YouTube Reporting API](https://console.cloud.google.com/apis/library/youtubereporting.googleapis.com) enabled. +* [Client ID, client secret](https://support.google.com/cloud/answer/6158849?hl=en) and refresh token generated. \ + > Please note you'll need to use another OAuth2 credentials type - *Web application*, and set "https://developers.google.com/oauthplayground" as redirect url in it. +* Refresh token. You can use [OAuth Playground](https://developers.google.com/oauthplayground/) to generate refresh token. + * Select `https://www.googleapis.com/auth/yt-analytics.readonly` scope + * Enter OAuth Client ID and OAuth Client secret under *Use your own OAuth credentials*; + * Click on *Authorize APIs* + +* Expose client id, client secret and refresh token as environmental variables: + +``` +export GARF_YOUTUBE_REPORTING_API_CLIENT_ID= +export GARF_YOUTUBE_REPORTING_API_CLIENT_SECRET= +export GARF_YOUTUBE_REPORTING_API_REFRESH_TOKEN= +``` + ## Installation -`pip install garf-youtube-data-api` +`pip install garf-youtube` ## Usage ### Run as a library ``` -from garf_youtube_data_api import report_fetcher -from garf_io import writer +from garf.community.google.youtube import report_fetcher +from garf.io import writer # Specify query @@ -37,7 +56,7 @@ console_writer = writer.create_writer('console') console_writer.write(fetched_report, 'output') ``` -Learn [more](https://google.github.io/garf/fetchers/youtube-data-api/#python) on library usage. +Learn [more](https://google.github.io/garf/fetchers/youtube/#python) on library usage. ### Run via CLI @@ -53,13 +72,13 @@ where: * `` - local or remove files containing queries * `` - output supported by [`garf-io` library](https://google.github.io/garf/usage/writers/). -* ` None: + """Initializes YouTubeAnalyticsApiClient.""" + if ( + not os.getenv('GARF_YOUTUBE_REPORTING_API_REFRESH_TOKEN') + or not os.getenv('GARF_YOUTUBE_REPORTING_API_CLIENT_ID') + or not os.getenv('GARF_YOUTUBE_REPORTING_API_CLIENT_SECRET') + ): + raise YouTubeAnalyticsApiClientError( + 'YouTubeAnalyticsApiClient requests all ENV variables to be set up: ' + 'GARF_YOUTUBE_REPORTING_API_REFRESH_TOKEN, ' + 'GARF_YOUTUBE_REPORTING_API_CLIENT_ID, ' + 'GARF_YOUTUBE_REPORTING_API_CLIENT_SECRET' + ) + self.api_version = api_version + self._credentials = None + self._service = None + + @property + def credentials(self) -> Credentials: + """OAuth2.0 credentials to access API.""" + if self._credentials: + return self._credentials + return Credentials( + None, + refresh_token=os.getenv('GARF_YOUTUBE_REPORTING_API_REFRESH_TOKEN'), + token_uri='https://oauth2.googleapis.com/token', + client_id=os.getenv('GARF_YOUTUBE_REPORTING_API_CLIENT_ID'), + client_secret=os.getenv('GARF_YOUTUBE_REPORTING_API_CLIENT_SECRET'), + ) + + @property + def service(self): + """Services for accessing YouTube Analytics API.""" + if self._service: + return self._service + return build( + 'youtubeAnalytics', self.api_version, credentials=self.credentials + ) + + @override + def get_response( + self, request: query_editor.BaseQueryElements, **kwargs: str + ) -> api_clients.GarfApiResponse: + metrics = [] + dimensions = [] + filters = [] + for field in request.fields: + if field.startswith('metrics'): + metrics.append(field.replace('metrics.', '')) + elif field.startswith('dimensions'): + dimensions.append(field.replace('dimensions.', '')) + for filter_statement in request.filters: + if filter_statement.startswith('channel'): + ids = filter_statement + elif filter_statement.startswith('startDate'): + start_date = filter_statement.split('=') + elif filter_statement.startswith('endDate'): + end_date = filter_statement.split('=') + else: + filters.append(filter_statement) + result = ( + self.service.reports() + .query( + dimensions=','.join(dimensions), + metrics=','.join(metrics), + filters=';'.join(filters), + ids=ids, + startDate=start_date[1].strip(), + endDate=end_date[1].strip(), + alt='json', + ) + .execute() + ) + results = [] + for row in result.get('rows'): + response_row: dict[str, dict[str, str]] = defaultdict(dict) + for position, header in enumerate(result.get('columnHeaders')): + header_name = header.get('name') + if header.get('columnType') == 'DIMENSION': + response_row['dimensions'].update({header_name: row[position]}) + elif header.get('columnType') == 'METRIC': + response_row['metrics'].update({header_name: row[position]}) + results.append(response_row) + return api_clients.GarfApiResponse(results=results) + + class Comparator(pydantic.BaseModel): field: str operator: str diff --git a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/exceptions.py b/libs/community/google/youtube/garf/community/google/youtube/builtins/__init__.py similarity index 75% rename from libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/exceptions.py rename to libs/community/google/youtube/garf/community/google/youtube/builtins/__init__.py index abf40d0..a6db40a 100644 --- a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/exceptions.py +++ b/libs/community/google/youtube/garf/community/google/youtube/builtins/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Defines common library exceptions.""" +from garf.community.google.youtube.builtins import channel_videos - -class GarfYouTubeReportingApiError(Exception): - """Base class for all library exceptions.""" +BUILTIN_QUERIES = { + 'channelVideos': channel_videos.get_youtube_channel_videos, +} diff --git a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/builtins/channel_videos.py b/libs/community/google/youtube/garf/community/google/youtube/builtins/channel_videos.py similarity index 94% rename from libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/builtins/channel_videos.py rename to libs/community/google/youtube/garf/community/google/youtube/builtins/channel_videos.py index 70756b2..6ec500f 100644 --- a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/builtins/channel_videos.py +++ b/libs/community/google/youtube/garf/community/google/youtube/builtins/channel_videos.py @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ def get_youtube_channel_videos( - report_fetcher: 'garf_youtube_data_api.YouTubeDataApiReportFetcher', + report_fetcher: 'garf.community.google.youtube.report_fetcher.YouTubeDataApiReportFetcher', **kwargs: str, ): channel_uploads_playlist_query = """ diff --git a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/exceptions.py b/libs/community/google/youtube/garf/community/google/youtube/exceptions.py similarity index 68% rename from libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/exceptions.py rename to libs/community/google/youtube/garf/community/google/youtube/exceptions.py index 178a9b2..f83ae40 100644 --- a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/exceptions.py +++ b/libs/community/google/youtube/garf/community/google/youtube/exceptions.py @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,9 +11,18 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Defines common library exceptions.""" +from garf.core import exceptions + -class GarfYouTubeDataApiError(Exception): +class GarfYouTubeApiError(exceptions.GarfError): """Base class for all library exceptions.""" + + +class GarfYouTubeDataApiError(GarfYouTubeApiError): + """YouTube Data API error.""" + + +class GarfYouTubeAnalyticsApiError(GarfYouTubeApiError): + """YouTube Analytics API error.""" diff --git a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/query_editor.py b/libs/community/google/youtube/garf/community/google/youtube/query_editor.py similarity index 71% rename from libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/query_editor.py rename to libs/community/google/youtube/garf/community/google/youtube/query_editor.py index f8e98d3..16ca8df 100644 --- a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/query_editor.py +++ b/libs/community/google/youtube/garf/community/google/youtube/query_editor.py @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,10 +11,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Defines YouTubeDataApiQuery.""" -from garf_core import query_editor +"""Defines YouTube specific queries.""" + +from garf.core import query_editor class YouTubeDataApiQuery(query_editor.QuerySpecification): - """Query to youtube data api.""" + """Query to YouTube Data API.""" + + +class YouTubeAnalyticsApiQuery(query_editor.QuerySpecification): + """Query to YouTube Analytics Api.""" diff --git a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/report_fetcher.py b/libs/community/google/youtube/garf/community/google/youtube/report_fetcher.py similarity index 73% rename from libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/report_fetcher.py rename to libs/community/google/youtube/garf/community/google/youtube/report_fetcher.py index 4106665..1750234 100644 --- a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/report_fetcher.py +++ b/libs/community/google/youtube/garf/community/google/youtube/report_fetcher.py @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,7 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Defines report fetcher.""" import functools @@ -20,14 +19,11 @@ from collections.abc import Iterable, MutableSequence from typing import Any, Final -import pandas as pd -from garf_core import parsers, report, report_fetcher +from garf.community.google.youtube import api_clients, builtins, query_editor +from garf.core import parsers, report, report_fetcher from typing_extensions import override -from garf_youtube_data_api import builtins, query_editor -from garf_youtube_data_api.api_clients import YouTubeDataApiClient - -ALLOWED_FILTERS: Final[set[str]] = ( +ALLOWED_FILTERS: Final[tuple[str, ...]] = ( 'id', 'regionCode', 'videoCategoryId', @@ -50,11 +46,13 @@ def _batched(iterable: Iterable[str], chunk_size: int): class YouTubeDataApiReportFetcher(report_fetcher.ApiReportFetcher): - """Defines report fetcher.""" + """Defines report fetcher for YouTube Data API.""" + + alias = 'youtube-data-api' def __init__( self, - api_client: YouTubeDataApiClient | None = None, + api_client: api_clients.YouTubeDataApiClient | None = None, parser: parsers.BaseParser = parsers.NumericConverterDictParser, query_spec: query_editor.YouTubeDataApiQuery = ( query_editor.YouTubeDataApiQuery @@ -64,7 +62,7 @@ def __init__( ) -> None: """Initializes YouTubeDataApiReportFetcher.""" if not api_client: - api_client = YouTubeDataApiClient() + api_client = api_clients.YouTubeDataApiClient(**kwargs) super().__init__(api_client, parser, query_spec, builtin_queries, **kwargs) @override @@ -112,3 +110,23 @@ def fetch( sorted_report.results_placeholder = res.results_placeholder return sorted_report return res + + +class YouTubeAnalyticsApiReportFetcher(report_fetcher.ApiReportFetcher): + """Defines report fetcher for YouTube Analytics API.""" + + alias = 'youtube-analytics' + + def __init__( + self, + api_client: api_clients.YouTubeAnalyticsApiClient | None = None, + parser: parsers.DictParser = parsers.DictParser, + query_spec: query_editor.YouTubeAnalyticsApiQuery = ( + query_editor.YouTubeAnalyticsApiQuery + ), + **kwargs: str, + ) -> None: + """Initializes YouTubeDataApiReportFetcher.""" + if not api_client: + api_client = api_clients.YouTubeAnalyticsApiClient() + super().__init__(api_client, parser, query_spec) diff --git a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/simulator.py b/libs/community/google/youtube/garf/community/google/youtube/simulator.py similarity index 90% rename from libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/simulator.py rename to libs/community/google/youtube/garf/community/google/youtube/simulator.py index 9de7a1a..8f5b6e9 100644 --- a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/simulator.py +++ b/libs/community/google/youtube/garf/community/google/youtube/simulator.py @@ -19,10 +19,8 @@ import logging from typing import Any -from garf_core import parsers, simulator - -from garf_youtube_data_api import query_editor -from garf_youtube_data_api.api_clients import YouTubeDataApiClient +from garf.community.google.youtube import YouTubeDataApiClient, query_editor +from garf.core import parsers, simulator logger = logging.getLogger(__name__) diff --git a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/telemetry.py b/libs/community/google/youtube/garf/community/google/youtube/telemetry.py similarity index 88% rename from libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/telemetry.py rename to libs/community/google/youtube/garf/community/google/youtube/telemetry.py index 7deabe2..2f98835 100644 --- a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/telemetry.py +++ b/libs/community/google/youtube/garf/community/google/youtube/telemetry.py @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,5 +16,5 @@ from opentelemetry import trace tracer = trace.get_tracer( - instrumenting_module_name='garf_youtube_data_api', + instrumenting_module_name='garf.community.google.youtube', ) diff --git a/libs/community/google/youtube/garf_youtube_data_api/__init__.py b/libs/community/google/youtube/garf_youtube_data_api/__init__.py new file mode 100644 index 0000000..ee075ac --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_data_api/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube import * + +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_data_api/api_clients.py b/libs/community/google/youtube/garf_youtube_data_api/api_clients.py new file mode 100644 index 0000000..e8625ed --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_data_api/api_clients.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.api_clients import * + +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_data_api/builtins/__init__.py b/libs/community/google/youtube/garf_youtube_data_api/builtins/__init__.py new file mode 100644 index 0000000..4c459c7 --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_data_api/builtins/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.builtins import * + +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_data_api/builtins/channel_videos.py b/libs/community/google/youtube/garf_youtube_data_api/builtins/channel_videos.py new file mode 100644 index 0000000..18206a7 --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_data_api/builtins/channel_videos.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.builtins.channel_videos import * + +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_data_api/exceptions.py b/libs/community/google/youtube/garf_youtube_data_api/exceptions.py new file mode 100644 index 0000000..1d6482b --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_data_api/exceptions.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.exceptions import * + +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/query_editor.py b/libs/community/google/youtube/garf_youtube_data_api/query_editor.py similarity index 66% rename from libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/query_editor.py rename to libs/community/google/youtube/garf_youtube_data_api/query_editor.py index 0d1b217..73dd31d 100644 --- a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/query_editor.py +++ b/libs/community/google/youtube/garf_youtube_data_api/query_editor.py @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -11,10 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Defines YouTubeDataApiQuery.""" +import warnings -from garf_core import query_editor +from garf.community.google.youtube.query_editor import * - -class YouTubeReportingApiQuery(query_editor.QuerySpecification): - """Query to youtube data api.""" +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_data_api/report_fetcher.py b/libs/community/google/youtube/garf_youtube_data_api/report_fetcher.py new file mode 100644 index 0000000..dc5c578 --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_data_api/report_fetcher.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.report_fetcher import * + +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_data_api/simulator.py b/libs/community/google/youtube/garf_youtube_data_api/simulator.py new file mode 100644 index 0000000..c3170f1 --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_data_api/simulator.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.simulator import * + +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_data_api/telemetry.py b/libs/community/google/youtube/garf_youtube_data_api/telemetry.py new file mode 100644 index 0000000..f1d7ff3 --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_data_api/telemetry.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.telemetry import * + +warnings.warn( + "The 'garf_youtube_data_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_reporting_api/__init__.py b/libs/community/google/youtube/garf_youtube_reporting_api/__init__.py new file mode 100644 index 0000000..eb0041a --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_reporting_api/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube import * + +warnings.warn( + "The 'garf_youtube_reporting_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_reporting_api/api_clients.py b/libs/community/google/youtube/garf_youtube_reporting_api/api_clients.py new file mode 100644 index 0000000..3fb8bbf --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_reporting_api/api_clients.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.api_clients import * + +warnings.warn( + "The 'garf_youtube_reporting_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_reporting_api/exceptions.py b/libs/community/google/youtube/garf_youtube_reporting_api/exceptions.py new file mode 100644 index 0000000..48a5ad6 --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_reporting_api/exceptions.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.exceptions import * + +warnings.warn( + "The 'garf_youtube_reporting_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_reporting_api/query_editor.py b/libs/community/google/youtube/garf_youtube_reporting_api/query_editor.py new file mode 100644 index 0000000..271a755 --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_reporting_api/query_editor.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.query_editor import * + +warnings.warn( + "The 'garf_youtube_reporting_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/garf_youtube_reporting_api/report_fetcher.py b/libs/community/google/youtube/garf_youtube_reporting_api/report_fetcher.py new file mode 100644 index 0000000..2394585 --- /dev/null +++ b/libs/community/google/youtube/garf_youtube_reporting_api/report_fetcher.py @@ -0,0 +1,23 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings + +from garf.community.google.youtube.report_fetcher import * + +warnings.warn( + "The 'garf_youtube_reporting_api' namespace is deprecated. " + "Please use 'garf.community.google.youtube' instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/libs/community/google/youtube/youtube-data-api/pyproject.toml b/libs/community/google/youtube/pyproject.toml similarity index 53% rename from libs/community/google/youtube/youtube-data-api/pyproject.toml rename to libs/community/google/youtube/pyproject.toml index 1a1a855..bf9571d 100644 --- a/libs/community/google/youtube/youtube-data-api/pyproject.toml +++ b/libs/community/google/youtube/pyproject.toml @@ -3,10 +3,10 @@ requires = ["setuptools >= 61.0"] build-backend = "setuptools.build_meta" [project] -name = "garf-youtube-data-api" +name = "garf-youtube" dependencies = [ - "garf-core[pandas]", - "garf-io", + "garf-core[pandas]>=1.0.0", + "garf-io>=1.0.0", "google-api-python-client", ] authors = [ @@ -14,8 +14,8 @@ authors = [ {name = "Google Inc. (gTech gPS CSE team)", email = "no-reply@google.com"}, ] license = {text = "Apache 2.0"} -requires-python = ">=3.8" -description = "description" +requires-python = ">=3.9" +description = "Garf connector to YouTube APIs" readme = "README.md" classifiers = [ "Development Status :: 4 - Beta", @@ -25,16 +25,20 @@ classifiers = [ dynamic=["version"] [tool.setuptools.dynamic] -version = {attr = "garf_youtube_data_api.__version__"} +version = {attr = "garf.community.google.youtube.__version__"} [project.entry-points.garf] -youtube-data-api = "garf_youtube_data_api.report_fetcher" +youtube-data-api = "garf.community.google.youtube.report_fetcher" +youtube-analytics = "garf.community.google.youtube.report_fetcher" -[options.extras_require] +[project.optional-dependencies] test = [ "pytest", "pytest-cov", + "pytest-mock", "python-dotenv", ] -[project.scripts] +[tool.setuptools.packages.find] +where = ["."] +include=["garf_youtube_data*", "garf_youtube_reporting_api*", "garf.*"] diff --git a/libs/community/google/youtube/youtube-data-api/tests/e2e/test_lib.py b/libs/community/google/youtube/tests/e2e/test_lib.py similarity index 59% rename from libs/community/google/youtube/youtube-data-api/tests/e2e/test_lib.py rename to libs/community/google/youtube/tests/e2e/test_lib.py index 47f7620..20ca798 100644 --- a/libs/community/google/youtube/youtube-data-api/tests/e2e/test_lib.py +++ b/libs/community/google/youtube/tests/e2e/test_lib.py @@ -14,7 +14,7 @@ import os import dotenv -from garf_youtube_data_api import api_clients, report_fetcher +from garf.community.google.youtube import api_clients, report_fetcher dotenv.load_dotenv() @@ -53,9 +53,10 @@ def test_query_with_kwargs_only_without_id(): fetched_report = fetcher.fetch(query, regionCode='US', chart='mostPopular') assert fetched_report[0] + def test_builtin_channel_videos_with_enhanced_attributes(): - """Test that builtin.channelVideos returns enhanced video attributes.""" - query = ''' + """Test that builtin.channelVideos returns enhanced video attributes.""" + query = """ SELECT channel_id, video_id, @@ -68,31 +69,31 @@ def test_builtin_channel_videos_with_enhanced_attributes(): duration, privacy_status FROM builtin.channelVideos - ''' - - # Use a test channel ID from environment - test_channel_id = os.getenv('YOUTUBE_CHANNEL_ID', 'UC_x5XG1OV2P6uZZ5FSM9Ttw') - - fetched_report = fetcher.fetch(query, id=[test_channel_id]) - - # Verify we got results - assert fetched_report, "No videos returned from channelVideos" - assert len(fetched_report) > 0, "channelVideos returned empty list" - - # Verify the enhanced attributes are present in the first video - first_video = fetched_report[0] - assert hasattr(first_video, 'channel_id'), "Missing channel_id attribute" - assert hasattr(first_video, 'video_id'), "Missing video_id attribute" - assert hasattr(first_video, 'title'), "Missing title attribute" - assert hasattr(first_video, 'view_count'), "Missing view_count attribute" - assert hasattr(first_video, 'duration'), "Missing duration attribute" - - # Verify the values are not None/empty - assert first_video.video_id, "video_id is empty" - assert first_video.title, "title is empty" - - print(f"✓ channelVideos test passed! Found {len(fetched_report)} videos") - print(f" First video: {first_video.title}") - print(f" Video ID: {first_video.video_id}") - if hasattr(first_video, 'view_count') and first_video.view_count: - print(f" Views: {first_video.view_count}") \ No newline at end of file + """ + + # Use a test channel ID from environment + test_channel_id = os.getenv('YOUTUBE_CHANNEL_ID', 'UC_x5XG1OV2P6uZZ5FSM9Ttw') + + fetched_report = fetcher.fetch(query, id=[test_channel_id]) + + # Verify we got results + assert fetched_report, 'No videos returned from channelVideos' + assert len(fetched_report) > 0, 'channelVideos returned empty list' + + # Verify the enhanced attributes are present in the first video + first_video = fetched_report[0] + assert hasattr(first_video, 'channel_id'), 'Missing channel_id attribute' + assert hasattr(first_video, 'video_id'), 'Missing video_id attribute' + assert hasattr(first_video, 'title'), 'Missing title attribute' + assert hasattr(first_video, 'view_count'), 'Missing view_count attribute' + assert hasattr(first_video, 'duration'), 'Missing duration attribute' + + # Verify the values are not None/empty + assert first_video.video_id, 'video_id is empty' + assert first_video.title, 'title is empty' + + print(f'✓ channelVideos test passed! Found {len(fetched_report)} videos') + print(f' First video: {first_video.title}') + print(f' Video ID: {first_video.video_id}') + if hasattr(first_video, 'view_count') and first_video.view_count: + print(f' Views: {first_video.view_count}') diff --git a/libs/community/google/youtube/youtube-data-api/tests/unit/test_report_fetcher.py b/libs/community/google/youtube/tests/unit/test_report_fetcher.py similarity index 86% rename from libs/community/google/youtube/youtube-data-api/tests/unit/test_report_fetcher.py rename to libs/community/google/youtube/tests/unit/test_report_fetcher.py index 0ce4823..9296d69 100644 --- a/libs/community/google/youtube/youtube-data-api/tests/unit/test_report_fetcher.py +++ b/libs/community/google/youtube/tests/unit/test_report_fetcher.py @@ -1,4 +1,4 @@ -# Copyright 2025 Google LLC +# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import garf_core import pytest -from garf_youtube_data_api.report_fetcher import YouTubeDataApiReportFetcher +from garf.community.google.youtube.report_fetcher import ( + YouTubeDataApiReportFetcher, +) +from garf.core import report class TestYouTubeDataApiReportFetcher: @@ -37,7 +39,7 @@ def test_fetch_returns_filtered_data(self, mocker, fetcher): """ mocker.patch( - 'garf_youtube_data_api.api_clients.YouTubeDataApiClient._list', + 'garf.community.google.youtube.api_clients.YouTubeDataApiClient._list', return_value={ 'items': [ { @@ -55,7 +57,7 @@ def test_fetch_returns_filtered_data(self, mocker, fetcher): ) result = fetcher.fetch(query, id=['1', '2']) - expected_report = garf_core.GarfReport( + expected_report = report.GarfReport( results=[[2, 11, 2, '2025-07-10T22:15:44Z']], column_names=['id', 'views', 'likes', 'published_at'], ) @@ -74,7 +76,7 @@ def test_fetch_returns_sorted_data(self, mocker, fetcher): """ mocker.patch( - 'garf_youtube_data_api.api_clients.YouTubeDataApiClient._list', + 'garf.community.google.youtube.api_clients.YouTubeDataApiClient._list', return_value={ 'items': [ { @@ -92,7 +94,7 @@ def test_fetch_returns_sorted_data(self, mocker, fetcher): ) result = fetcher.fetch(query, id=['1', '2']) - expected_report = garf_core.GarfReport( + expected_report = report.GarfReport( results=[ [2, 11, 2, '2025-07-10T22:15:44Z'], [1, 10, 1, '2024-07-10T22:15:44Z'], @@ -114,12 +116,12 @@ def test_fetch_returns_placeholder_data(self, mocker, fetcher): """ mocker.patch( - 'garf_youtube_data_api.api_clients.YouTubeDataApiClient._list', + 'garf.community.google.youtube.api_clients.YouTubeDataApiClient._list', return_value={'items': []}, ) result = fetcher.fetch(query, id=['1']) - expected_report = garf_core.GarfReport( + expected_report = report.GarfReport( results_placeholder=[ ['', 1, 1, '1970-01-01'], ], diff --git a/libs/community/google/youtube/youtube-data-api/tests/unit/test_simulator.py b/libs/community/google/youtube/tests/unit/test_simulator.py similarity index 94% rename from libs/community/google/youtube/youtube-data-api/tests/unit/test_simulator.py rename to libs/community/google/youtube/tests/unit/test_simulator.py index a2c1cbf..7a58f50 100644 --- a/libs/community/google/youtube/youtube-data-api/tests/unit/test_simulator.py +++ b/libs/community/google/youtube/tests/unit/test_simulator.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from garf_youtube_data_api import simulator +from garf.community.google.youtube import simulator class TestYouTubeDataApiReportSimulator: diff --git a/libs/community/google/youtube/youtube-data-api/Makefile b/libs/community/google/youtube/youtube-data-api/Makefile deleted file mode 100644 index fea13cc..0000000 --- a/libs/community/google/youtube/youtube-data-api/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -SHELL=/bin/bash -PYTHON=`which python` - -.PHONY: build -clean: - @echo "Removing the build/ dist/ and *.egg-info/ directories" - @rm -rf build dist *.egg-info - -upload: - @echo "Uploading built package to PyPI" - @${PYTHON} `which twine` upload dist/* - -bundle: - @echo "Bundling the code"; echo - @${PYTHON} -m build - -upload_pypi: | clean bundle upload diff --git a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/builtins/__init__.py b/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/builtins/__init__.py deleted file mode 100644 index 3d6d86f..0000000 --- a/libs/community/google/youtube/youtube-data-api/garf_youtube_data_api/builtins/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from garf_youtube_data_api.builtins import channel_videos - -BUILTIN_QUERIES = { - 'channelVideos': channel_videos.get_youtube_channel_videos, -} diff --git a/libs/community/google/youtube/youtube-reporting-api/Makefile b/libs/community/google/youtube/youtube-reporting-api/Makefile deleted file mode 100644 index fea13cc..0000000 --- a/libs/community/google/youtube/youtube-reporting-api/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -SHELL=/bin/bash -PYTHON=`which python` - -.PHONY: build -clean: - @echo "Removing the build/ dist/ and *.egg-info/ directories" - @rm -rf build dist *.egg-info - -upload: - @echo "Uploading built package to PyPI" - @${PYTHON} `which twine` upload dist/* - -bundle: - @echo "Bundling the code"; echo - @${PYTHON} -m build - -upload_pypi: | clean bundle upload diff --git a/libs/community/google/youtube/youtube-reporting-api/README.md b/libs/community/google/youtube/youtube-reporting-api/README.md deleted file mode 100644 index 6af6742..0000000 --- a/libs/community/google/youtube/youtube-reporting-api/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# `garf` for YouTube Reporting API - -[![PyPI](https://img.shields.io/pypi/v/garf-youtube-reporting-api?logo=pypi&logoColor=white&style=flat-square)](https://pypi.org/project/garf-youtube-reporting-api) -[![Downloads PyPI](https://img.shields.io/pypi/dw/garf-youtube-reporting-api?logo=pypi)](https://pypi.org/project/garf-youtube-reporting-api/) - -`garf-youtube-reporting-api` simplifies fetching data from YouTube Reporting API using [SQL-like queries](../../../garf_core/docs/how-to-write-queries.md). - -## Prerequisites - -* [YouTube Reporting API](https://console.cloud.google.com/apis/library/youtubereporting.googleapis.com) enabled. -* [Client ID, client secret](https://support.google.com/cloud/answer/6158849?hl=en) and refresh token generated. \ -> Please note you'll need to use another OAuth2 credentials type - *Web application*, and set "https://developers.google.com/oauthplayground" as redirect url in it. -* Refresh token. You can use [OAuth Playground](https://developers.google.com/oauthplayground/) to generate refresh token. - * Select `https://www.googleapis.com/auth/yt-analytics.readonly` scope - * Enter OAuth Client ID and OAuth Client secret under *Use your own OAuth credentials*; - * Click on *Authorize APIs* - -* Expose client id, client secret and refresh token as environmental variables: -``` -export YT_CLIENT_ID= -export YT_CLIENT_SECRET= -export YT_REFRESH_TOKEN= -``` - -## Installation - -`pip install garf-youtube-reporting-api` - -## Usage - -### Run as a library -``` -from garf_youtube_data_api import report_fetcher -from garf_io import writer - - -# Specify query -query = """ - SELECT - dimensions.day AS date, - metrics.views AS views - FROM channel - WHERE - channel==MINE - AND startDate = 2010-01-01 - AND endDate = 2024-01-01 - """ - -# Fetch report -fetched_report = report_fetcher.YouTubeReportingApiReportFetcher().fetch(query) - -# Write report to console -console_writer = writer.create_writer('console') -console_writer.write(fetched_report, 'output') -``` - -### Run via CLI - -> Install `garf-executors` package to run queries via CLI (`pip install garf-executors`). - -``` -garf --source youtube-reporting-api \ - --output \ -``` - -where: - -* `` - local or remove files containing queries -* `` - output supported by [`garf-io` library](../garf_io/README.md). diff --git a/libs/community/google/youtube/youtube-reporting-api/examples/README.md b/libs/community/google/youtube/youtube-reporting-api/examples/README.md deleted file mode 100644 index 432497b..0000000 --- a/libs/community/google/youtube/youtube-reporting-api/examples/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Example queries for `garf-youtube-reporting-api` - -To execute the queries you may run them in Python (check [Usage](../README.md#usage)) -or use `garf-executors` package (install with `pip install garf-executors`). - -* [channel_statistics](video_statistics.sql) - Gets views for owned channel for the last 30 days. - ``` - garf video_statistics.sql \ - --source youtube-reporting-api --output console \ - --macros.start_date=:YYYYMMDD-30 \ - --macros.start_date=:YYYYMMDD-1 - ``` - -* [video_retention](video_retention.sql) - Gets retention for a single video for the last 30 days. - garf video_retention.sql \ - --source youtube-reporting-api --output console \ - --macros.video_id= \ - --macros.start_date=:YYYYMMDD-30 \ - --macros.start_date=:YYYYMMDD-1 diff --git a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/__init__.py b/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/__init__.py deleted file mode 100644 index 8feb5d5..0000000 --- a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2025 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__version__ = '0.0.2' diff --git a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/api_clients.py b/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/api_clients.py deleted file mode 100644 index a1b7c89..0000000 --- a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/api_clients.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Creates API client for YouTube Reporting API.""" - -import os -from collections import defaultdict - -from garf_core import api_clients, query_editor -from google.oauth2.credentials import Credentials -from googleapiclient.discovery import build -from typing_extensions import override - -from garf_youtube_reporting_api import exceptions - - -class YouTubeReportingApiClientError(exceptions.GarfYouTubeReportingApiError): - """API client specific exception.""" - - -class YouTubeReportingApiClient(api_clients.BaseClient): - """Responsible for for getting data from YouTube Reporting API.""" - - def __init__(self, api_version: str = 'v2') -> None: - """Initializes YouTubeReportingApiClient.""" - if ( - not os.getenv('YT_REFRESH_TOKEN') - or not os.getenv('YT_CLIENT_ID') - or not os.getenv('YT_CLIENT_SECRET') - ): - raise YouTubeReportingApiClientError( - 'YouTubeReportingApiClient requests all ENV variables to be set up: ' - 'YT_REFRESH_TOKEN, YT_CLIENT_ID, YT_CLIENT_SECRET' - ) - self.api_version = api_version - self._credentials = None - self._service = None - - @property - def credentials(self) -> Credentials: - """OAuth2.0 credentials to access API.""" - if self._credentials: - return self._credentials - return Credentials( - None, - refresh_token=os.getenv('YT_REFRESH_TOKEN'), - token_uri='https://oauth2.googleapis.com/token', - client_id=os.getenv('YT_CLIENT_ID'), - client_secret=os.getenv('YT_CLIENT_SECRET'), - ) - - @property - def service(self): - """Services for accessing YouTube Analytics API.""" - if self._service: - return self._service - return build( - 'youtubeAnalytics', self.api_version, credentials=self.credentials - ) - - @override - def get_response( - self, request: query_editor.BaseQueryElements, **kwargs: str - ) -> api_clients.GarfApiResponse: - metrics = [] - dimensions = [] - filters = [] - for field in request.fields: - if field.startswith('metrics'): - metrics.append(field.replace('metrics.', '')) - elif field.startswith('dimensions'): - dimensions.append(field.replace('dimensions.', '')) - for filter_statement in request.filters: - if filter_statement.startswith('channel'): - ids = filter_statement - elif filter_statement.startswith('startDate'): - start_date = filter_statement.split('=') - elif filter_statement.startswith('endDate'): - end_date = filter_statement.split('=') - else: - filters.append(filter_statement) - result = ( - self.service.reports() - .query( - dimensions=','.join(dimensions), - metrics=','.join(metrics), - filters=';'.join(filters), - ids=ids, - startDate=start_date[1].strip(), - endDate=end_date[1].strip(), - alt='json', - ) - .execute() - ) - results = [] - for row in result.get('rows'): - response_row: dict[str, dict[str, str]] = defaultdict(dict) - for position, header in enumerate(result.get('columnHeaders')): - header_name = header.get('name') - if header.get('columnType') == 'DIMENSION': - response_row['dimensions'].update({header_name: row[position]}) - elif header.get('columnType') == 'METRIC': - response_row['metrics'].update({header_name: row[position]}) - results.append(response_row) - return api_clients.GarfApiResponse(results=results) diff --git a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/report_fetcher.py b/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/report_fetcher.py deleted file mode 100644 index 142ae94..0000000 --- a/libs/community/google/youtube/youtube-reporting-api/garf_youtube_reporting_api/report_fetcher.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Defines report fetcher.""" - -from garf_core import parsers, report_fetcher - -from garf_youtube_reporting_api import query_editor -from garf_youtube_reporting_api.api_clients import YouTubeReportingApiClient - - -class YouTubeReportingApiReportFetcher(report_fetcher.ApiReportFetcher): - """Defines report fetcher.""" - - def __init__( - self, - api_client: YouTubeReportingApiClient = YouTubeReportingApiClient(), - parser: parsers.DictParser = parsers.DictParser, - query_spec: query_editor.YouTubeReportingApiQuery = ( - query_editor.YouTubeReportingApiQuery - ), - ) -> None: - """Initializes YouTubeDataApiReportFetcher.""" - super().__init__(api_client, parser, query_spec) diff --git a/libs/community/google/youtube/youtube-reporting-api/pyproject.toml b/libs/community/google/youtube/youtube-reporting-api/pyproject.toml deleted file mode 100644 index 57fd7a9..0000000 --- a/libs/community/google/youtube/youtube-reporting-api/pyproject.toml +++ /dev/null @@ -1,35 +0,0 @@ -[build-system] -requires = ["setuptools >= 61.0"] -build-backend = "setuptools.build_meta" - -[project] -name = "garf-youtube-reporting-api" -dependencies = [ - "garf-core", - "garf-io", - "google-api-python-client", -] -authors = [ - {name = "Google Inc. (gTech gPS CSE team)", email = "no-reply@google.com"}, -] -license = {text = "Apache 2.0"} -requires-python = ">=3.8" -readme = "README.md" -classifiers = [ - "Development Status :: 4 - Beta", - "Programming Language :: Python" -] - -dynamic=["version"] - -[tool.setuptools.dynamic] -version = {attr = "garf_youtube_reporting_api.__version__"} - -[project.entry-points.garf] -youtube-reporting-api = "garf_youtube_reporting_api.report_fetcher" - -[options.extras_require] -test = [ - "pytest", - "pytest-cov" -]