This is an automated email from the ASF dual-hosted git repository. potiuk pushed a commit to branch v2-0-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit d3359a03a986c3674b9058e4e800c41190120e62 Author: Jyoti Dhiman <[email protected]> AuthorDate: Thu Feb 25 17:52:54 2021 +0530 Add Tableau provider separate from Salesforce Provider (#14030) Closes #13614 (cherry picked from commit 45e72ca83049a7db526b1f0fbd94c75f5f92cc75) --- CONTRIBUTING.rst | 1 + airflow/providers/dependencies.json | 3 + airflow/providers/salesforce/CHANGELOG.rst | 16 ++++ airflow/providers/salesforce/hooks/tableau.py | 104 ++------------------- .../operators/tableau_refresh_workbook.py | 88 ++--------------- airflow/providers/salesforce/provider.yaml | 6 +- .../salesforce/sensors/tableau_job_status.py | 68 +++----------- .../{salesforce => tableau}/CHANGELOG.rst | 0 .../provider.yaml => tableau/__init__.py} | 34 +------ .../example_dags/__init__.py} | 33 ------- .../example_tableau_refresh_workbook.py | 4 +- .../provider.yaml => tableau/hooks/__init__.py} | 34 +------ .../{salesforce => tableau}/hooks/tableau.py | 0 .../operators/__init__.py} | 33 ------- .../operators/tableau_refresh_workbook.py | 4 +- .../{salesforce => tableau}/provider.yaml | 26 +++--- .../provider.yaml => tableau/sensors/__init__.py} | 33 ------- .../sensors/tableau_job_status.py | 2 +- .../apache-airflow-providers-tableau/index.rst | 29 +++++- docs/integration-logos/tableau/tableau.png | Bin 0 -> 4142 bytes docs/spelling_wordlist.txt | 1 + .../run_install_and_test_provider_packages.sh | 2 +- setup.py | 4 +- tests/core/test_providers_manager.py | 1 + .../providers/tableau/hooks/__init__.py | 34 +------ .../{salesforce => tableau}/hooks/test_tableau.py | 32 +++++-- .../providers/tableau/operators/__init__.py | 33 ------- .../operators/test_tableau_refresh_workbook.py | 26 +++++- .../providers/tableau/sensors/__init__.py | 33 ------- .../sensors/test_tableau_job_status.py | 16 +++- 30 files changed, 162 insertions(+), 538 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0a6f381..857d3bb 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -654,6 +654,7 @@ microsoft.mssql odbc mysql amazon,presto,vertica opsgenie http postgres amazon +salesforce tableau sftp ssh slack http snowflake slack diff --git a/airflow/providers/dependencies.json b/airflow/providers/dependencies.json index 836020c..b01e96c 100644 --- a/airflow/providers/dependencies.json +++ b/airflow/providers/dependencies.json @@ -67,6 +67,9 @@ "postgres": [ "amazon" ], + "salesforce": [ + "tableau" + ], "sftp": [ "ssh" ], diff --git a/airflow/providers/salesforce/CHANGELOG.rst b/airflow/providers/salesforce/CHANGELOG.rst index cef7dda..b4eb0ed 100644 --- a/airflow/providers/salesforce/CHANGELOG.rst +++ b/airflow/providers/salesforce/CHANGELOG.rst @@ -19,6 +19,22 @@ Changelog --------- +1.0.2 +..... + +Tableau provider moved to separate 'tableau' provider + +Things done: + + - Tableau classes imports classes from 'tableau' provider with deprecation warning + + +1.0.1 +..... + +Updated documentation and readme files. + + 1.0.0 ..... diff --git a/airflow/providers/salesforce/hooks/tableau.py b/airflow/providers/salesforce/hooks/tableau.py index 51c2f98..cf5f7f3 100644 --- a/airflow/providers/salesforce/hooks/tableau.py +++ b/airflow/providers/salesforce/hooks/tableau.py @@ -14,102 +14,14 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from enum import Enum -from typing import Any, Optional -from tableauserverclient import Pager, PersonalAccessTokenAuth, Server, TableauAuth -from tableauserverclient.server import Auth +import warnings -from airflow.hooks.base import BaseHook +# pylint: disable=unused-import +from airflow.providers.tableau.hooks.tableau import TableauHook, TableauJobFinishCode # noqa - -class TableauJobFinishCode(Enum): - """ - The finish code indicates the status of the job. - - .. seealso:: https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm#query_job - - """ - - PENDING = -1 - SUCCESS = 0 - ERROR = 1 - CANCELED = 2 - - -class TableauHook(BaseHook): - """ - Connects to the Tableau Server Instance and allows to communicate with it. - - .. seealso:: https://tableau.github.io/server-client-python/docs/ - - :param site_id: The id of the site where the workbook belongs to. - It will connect to the default site if you don't provide an id. - :type site_id: Optional[str] - :param tableau_conn_id: The Tableau Connection id containing the credentials - to authenticate to the Tableau Server. - :type tableau_conn_id: str - """ - - conn_name_attr = 'tableau_conn_id' - default_conn_name = 'tableau_default' - conn_type = 'tableau' - hook_name = 'Tableau' - - def __init__(self, site_id: Optional[str] = None, tableau_conn_id: str = default_conn_name) -> None: - super().__init__() - self.tableau_conn_id = tableau_conn_id - self.conn = self.get_connection(self.tableau_conn_id) - self.site_id = site_id or self.conn.extra_dejson.get('site_id', '') - self.server = Server(self.conn.host, use_server_version=True) - self.tableau_conn = None - - def __enter__(self): - if not self.tableau_conn: - self.tableau_conn = self.get_conn() - return self - - def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: - self.server.auth.sign_out() - - def get_conn(self) -> Auth.contextmgr: - """ - Signs in to the Tableau Server and automatically signs out if used as ContextManager. - - :return: an authorized Tableau Server Context Manager object. - :rtype: tableauserverclient.server.Auth.contextmgr - """ - if self.conn.login and self.conn.password: - return self._auth_via_password() - if 'token_name' in self.conn.extra_dejson and 'personal_access_token' in self.conn.extra_dejson: - return self._auth_via_token() - raise NotImplementedError('No Authentication method found for given Credentials!') - - def _auth_via_password(self) -> Auth.contextmgr: - tableau_auth = TableauAuth( - username=self.conn.login, password=self.conn.password, site_id=self.site_id - ) - return self.server.auth.sign_in(tableau_auth) - - def _auth_via_token(self) -> Auth.contextmgr: - tableau_auth = PersonalAccessTokenAuth( - token_name=self.conn.extra_dejson['token_name'], - personal_access_token=self.conn.extra_dejson['personal_access_token'], - site_id=self.site_id, - ) - return self.server.auth.sign_in_with_personal_access_token(tableau_auth) - - def get_all(self, resource_name: str) -> Pager: - """ - Get all items of the given resource. - - .. seealso:: https://tableau.github.io/server-client-python/docs/page-through-results - - :param resource_name: The name of the resource to paginate. - For example: jobs or workbooks - :type resource_name: str - :return: all items by returning a Pager. - :rtype: tableauserverclient.Pager - """ - resource = getattr(self.server, resource_name) - return Pager(resource.get) +warnings.warn( + "This module is deprecated. Please use `airflow.providers.tableau.hooks.tableau`.", + DeprecationWarning, + stacklevel=2, +) diff --git a/airflow/providers/salesforce/operators/tableau_refresh_workbook.py b/airflow/providers/salesforce/operators/tableau_refresh_workbook.py index 7d4ffdc..309af33 100644 --- a/airflow/providers/salesforce/operators/tableau_refresh_workbook.py +++ b/airflow/providers/salesforce/operators/tableau_refresh_workbook.py @@ -14,84 +14,16 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from typing import Optional -from tableauserverclient import WorkbookItem +import warnings -from airflow.exceptions import AirflowException -from airflow.models import BaseOperator -from airflow.providers.salesforce.hooks.tableau import TableauHook -from airflow.utils.decorators import apply_defaults +# pylint: disable=unused-import +from airflow.providers.tableau.operators.tableau_refresh_workbook import ( # noqa + TableauRefreshWorkbookOperator, +) - -class TableauRefreshWorkbookOperator(BaseOperator): - """ - Refreshes a Tableau Workbook/Extract - - .. seealso:: https://tableau.github.io/server-client-python/docs/api-ref#workbooks - - :param workbook_name: The name of the workbook to refresh. - :type workbook_name: str - :param site_id: The id of the site where the workbook belongs to. - :type site_id: Optional[str] - :param blocking: By default the extract refresh will be blocking means it will wait until it has finished. - :type blocking: bool - :param tableau_conn_id: The Tableau Connection id containing the credentials - to authenticate to the Tableau Server. - :type tableau_conn_id: str - """ - - @apply_defaults - def __init__( - self, - *, - workbook_name: str, - site_id: Optional[str] = None, - blocking: bool = True, - tableau_conn_id: str = 'tableau_default', - **kwargs, - ) -> None: - super().__init__(**kwargs) - self.workbook_name = workbook_name - self.site_id = site_id - self.blocking = blocking - self.tableau_conn_id = tableau_conn_id - - def execute(self, context: dict) -> str: - """ - Executes the Tableau Extract Refresh and pushes the job id to xcom. - - :param context: The task context during execution. - :type context: dict - :return: the id of the job that executes the extract refresh - :rtype: str - """ - with TableauHook(self.site_id, self.tableau_conn_id) as tableau_hook: - workbook = self._get_workbook_by_name(tableau_hook) - - job_id = self._refresh_workbook(tableau_hook, workbook.id) - if self.blocking: - from airflow.providers.salesforce.sensors.tableau_job_status import TableauJobStatusSensor - - TableauJobStatusSensor( - job_id=job_id, - site_id=self.site_id, - tableau_conn_id=self.tableau_conn_id, - task_id='wait_until_succeeded', - dag=None, - ).execute(context={}) - self.log.info('Workbook %s has been successfully refreshed.', self.workbook_name) - return job_id - - def _get_workbook_by_name(self, tableau_hook: TableauHook) -> WorkbookItem: - for workbook in tableau_hook.get_all(resource_name='workbooks'): - if workbook.name == self.workbook_name: - self.log.info('Found matching workbook with id %s', workbook.id) - return workbook - - raise AirflowException(f'Workbook {self.workbook_name} not found!') - - def _refresh_workbook(self, tableau_hook: TableauHook, workbook_id: str) -> str: - job = tableau_hook.server.workbooks.refresh(workbook_id) - self.log.info('Refreshing Workbook %s...', self.workbook_name) - return job.id +warnings.warn( + "This module is deprecated. Please use `airflow.providers.tableau.operators.tableau_refresh_workbook`.", + DeprecationWarning, + stacklevel=2, +) diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/salesforce/provider.yaml index fe739ff..c0992d8 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/airflow/providers/salesforce/provider.yaml @@ -22,6 +22,8 @@ description: | `Salesforce <https://www.salesforce.com/>`__ versions: + - 1.0.2 + - 1.0.1 - 1.0.0 integrations: @@ -40,10 +42,12 @@ sensors: - airflow.providers.salesforce.sensors.tableau_job_status hooks: + - integration-name: Tableau + python-modules: + - airflow.providers.salesforce.hooks.tableau - integration-name: Salesforce python-modules: - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau hook-class-names: - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/airflow/providers/salesforce/sensors/tableau_job_status.py b/airflow/providers/salesforce/sensors/tableau_job_status.py index 4939203..076159e 100644 --- a/airflow/providers/salesforce/sensors/tableau_job_status.py +++ b/airflow/providers/salesforce/sensors/tableau_job_status.py @@ -14,63 +14,17 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from typing import Optional -from airflow.exceptions import AirflowException -from airflow.providers.salesforce.hooks.tableau import TableauHook, TableauJobFinishCode -from airflow.sensors.base import BaseSensorOperator -from airflow.utils.decorators import apply_defaults +import warnings +# pylint: disable=unused-import +from airflow.providers.tableau.sensors.tableau_job_status import ( # noqa + TableauJobFailedException, + TableauJobStatusSensor, +) -class TableauJobFailedException(AirflowException): - """An exception that indicates that a Job failed to complete.""" - - -class TableauJobStatusSensor(BaseSensorOperator): - """ - Watches the status of a Tableau Server Job. - - .. seealso:: https://tableau.github.io/server-client-python/docs/api-ref#jobs - - :param job_id: The job to watch. - :type job_id: str - :param site_id: The id of the site where the workbook belongs to. - :type site_id: Optional[str] - :param tableau_conn_id: The Tableau Connection id containing the credentials - to authenticate to the Tableau Server. - :type tableau_conn_id: str - """ - - template_fields = ('job_id',) - - @apply_defaults - def __init__( - self, - *, - job_id: str, - site_id: Optional[str] = None, - tableau_conn_id: str = 'tableau_default', - **kwargs, - ) -> None: - super().__init__(**kwargs) - self.tableau_conn_id = tableau_conn_id - self.job_id = job_id - self.site_id = site_id - - def poke(self, context: dict) -> bool: - """ - Pokes until the job has successfully finished. - - :param context: The task context during execution. - :type context: dict - :return: True if it succeeded and False if not. - :rtype: bool - """ - with TableauHook(self.site_id, self.tableau_conn_id) as tableau_hook: - finish_code = TableauJobFinishCode( - int(tableau_hook.server.jobs.get_by_id(self.job_id).finish_code) - ) - self.log.info('Current finishCode is %s (%s)', finish_code.name, finish_code.value) - if finish_code in [TableauJobFinishCode.ERROR, TableauJobFinishCode.CANCELED]: - raise TableauJobFailedException('The Tableau Refresh Workbook Job failed!') - return finish_code == TableauJobFinishCode.SUCCESS +warnings.warn( + "This module is deprecated. Please use `airflow.providers.tableau.sensors.tableau_job_status`.", + DeprecationWarning, + stacklevel=2, +) diff --git a/airflow/providers/salesforce/CHANGELOG.rst b/airflow/providers/tableau/CHANGELOG.rst similarity index 100% copy from airflow/providers/salesforce/CHANGELOG.rst copy to airflow/providers/tableau/CHANGELOG.rst diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/__init__.py similarity index 50% copy from airflow/providers/salesforce/provider.yaml copy to airflow/providers/tableau/__init__.py index fe739ff..217e5db 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/airflow/providers/tableau/__init__.py @@ -1,3 +1,4 @@ +# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -14,36 +15,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - ---- -package-name: apache-airflow-providers-salesforce -name: Salesforce -description: | - `Salesforce <https://www.salesforce.com/>`__ - -versions: - - 1.0.0 - -integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ - tags: [service] - -operators: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook - -sensors: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status - -hooks: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau - -hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/example_dags/__init__.py similarity index 50% copy from airflow/providers/salesforce/provider.yaml copy to airflow/providers/tableau/example_dags/__init__.py index fe739ff..13a8339 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/airflow/providers/tableau/example_dags/__init__.py @@ -14,36 +14,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - ---- -package-name: apache-airflow-providers-salesforce -name: Salesforce -description: | - `Salesforce <https://www.salesforce.com/>`__ - -versions: - - 1.0.0 - -integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ - tags: [service] - -operators: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook - -sensors: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status - -hooks: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau - -hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/airflow/providers/salesforce/example_dags/example_tableau_refresh_workbook.py b/airflow/providers/tableau/example_dags/example_tableau_refresh_workbook.py similarity index 92% rename from airflow/providers/salesforce/example_dags/example_tableau_refresh_workbook.py rename to airflow/providers/tableau/example_dags/example_tableau_refresh_workbook.py index 32b347c..da1cc8b 100644 --- a/airflow/providers/salesforce/example_dags/example_tableau_refresh_workbook.py +++ b/airflow/providers/tableau/example_dags/example_tableau_refresh_workbook.py @@ -23,8 +23,8 @@ when the operation actually finishes. That's why we have another task that check from datetime import timedelta from airflow import DAG -from airflow.providers.salesforce.operators.tableau_refresh_workbook import TableauRefreshWorkbookOperator -from airflow.providers.salesforce.sensors.tableau_job_status import TableauJobStatusSensor +from airflow.providers.tableau.operators.tableau_refresh_workbook import TableauRefreshWorkbookOperator +from airflow.providers.tableau.sensors.tableau_job_status import TableauJobStatusSensor from airflow.utils.dates import days_ago DEFAULT_ARGS = { diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/hooks/__init__.py similarity index 50% copy from airflow/providers/salesforce/provider.yaml copy to airflow/providers/tableau/hooks/__init__.py index fe739ff..217e5db 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/airflow/providers/tableau/hooks/__init__.py @@ -1,3 +1,4 @@ +# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -14,36 +15,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - ---- -package-name: apache-airflow-providers-salesforce -name: Salesforce -description: | - `Salesforce <https://www.salesforce.com/>`__ - -versions: - - 1.0.0 - -integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ - tags: [service] - -operators: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook - -sensors: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status - -hooks: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau - -hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/airflow/providers/salesforce/hooks/tableau.py b/airflow/providers/tableau/hooks/tableau.py similarity index 100% copy from airflow/providers/salesforce/hooks/tableau.py copy to airflow/providers/tableau/hooks/tableau.py diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/operators/__init__.py similarity index 50% copy from airflow/providers/salesforce/provider.yaml copy to airflow/providers/tableau/operators/__init__.py index fe739ff..13a8339 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/airflow/providers/tableau/operators/__init__.py @@ -14,36 +14,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - ---- -package-name: apache-airflow-providers-salesforce -name: Salesforce -description: | - `Salesforce <https://www.salesforce.com/>`__ - -versions: - - 1.0.0 - -integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ - tags: [service] - -operators: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook - -sensors: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status - -hooks: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau - -hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/airflow/providers/salesforce/operators/tableau_refresh_workbook.py b/airflow/providers/tableau/operators/tableau_refresh_workbook.py similarity index 95% copy from airflow/providers/salesforce/operators/tableau_refresh_workbook.py copy to airflow/providers/tableau/operators/tableau_refresh_workbook.py index 7d4ffdc..25ca77b 100644 --- a/airflow/providers/salesforce/operators/tableau_refresh_workbook.py +++ b/airflow/providers/tableau/operators/tableau_refresh_workbook.py @@ -20,7 +20,7 @@ from tableauserverclient import WorkbookItem from airflow.exceptions import AirflowException from airflow.models import BaseOperator -from airflow.providers.salesforce.hooks.tableau import TableauHook +from airflow.providers.tableau.hooks.tableau import TableauHook from airflow.utils.decorators import apply_defaults @@ -71,7 +71,7 @@ class TableauRefreshWorkbookOperator(BaseOperator): job_id = self._refresh_workbook(tableau_hook, workbook.id) if self.blocking: - from airflow.providers.salesforce.sensors.tableau_job_status import TableauJobStatusSensor + from airflow.providers.tableau.sensors.tableau_job_status import TableauJobStatusSensor TableauJobStatusSensor( job_id=job_id, diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/provider.yaml similarity index 61% copy from airflow/providers/salesforce/provider.yaml copy to airflow/providers/tableau/provider.yaml index fe739ff..e777947 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/airflow/providers/tableau/provider.yaml @@ -16,34 +16,34 @@ # under the License. --- -package-name: apache-airflow-providers-salesforce -name: Salesforce +package-name: apache-airflow-providers-tableau +name: Tableau description: | - `Salesforce <https://www.salesforce.com/>`__ + `Tableau <https://www.tableau.com/>`__ versions: - 1.0.0 integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ + - integration-name: Tableau + external-doc-url: https://www.tableau.com/ + logo: /integration-logos/tableau/tableau.png tags: [service] operators: - - integration-name: Salesforce + - integration-name: Tableau python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook + - airflow.providers.tableau.operators.tableau_refresh_workbook sensors: - - integration-name: Salesforce + - integration-name: Tableau python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status + - airflow.providers.tableau.sensors.tableau_job_status hooks: - - integration-name: Salesforce + - integration-name: Tableau python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau + - airflow.providers.tableau.hooks.tableau hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook + - airflow.providers.tableau.hooks.tableau.TableauHook diff --git a/airflow/providers/salesforce/provider.yaml b/airflow/providers/tableau/sensors/__init__.py similarity index 50% copy from airflow/providers/salesforce/provider.yaml copy to airflow/providers/tableau/sensors/__init__.py index fe739ff..13a8339 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/airflow/providers/tableau/sensors/__init__.py @@ -14,36 +14,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - ---- -package-name: apache-airflow-providers-salesforce -name: Salesforce -description: | - `Salesforce <https://www.salesforce.com/>`__ - -versions: - - 1.0.0 - -integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ - tags: [service] - -operators: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook - -sensors: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status - -hooks: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau - -hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/airflow/providers/salesforce/sensors/tableau_job_status.py b/airflow/providers/tableau/sensors/tableau_job_status.py similarity index 96% copy from airflow/providers/salesforce/sensors/tableau_job_status.py copy to airflow/providers/tableau/sensors/tableau_job_status.py index 4939203..518e2f0 100644 --- a/airflow/providers/salesforce/sensors/tableau_job_status.py +++ b/airflow/providers/tableau/sensors/tableau_job_status.py @@ -17,7 +17,7 @@ from typing import Optional from airflow.exceptions import AirflowException -from airflow.providers.salesforce.hooks.tableau import TableauHook, TableauJobFinishCode +from airflow.providers.tableau.hooks.tableau import TableauHook, TableauJobFinishCode from airflow.sensors.base import BaseSensorOperator from airflow.utils.decorators import apply_defaults diff --git a/airflow/providers/salesforce/CHANGELOG.rst b/docs/apache-airflow-providers-tableau/index.rst similarity index 56% copy from airflow/providers/salesforce/CHANGELOG.rst copy to docs/apache-airflow-providers-tableau/index.rst index cef7dda..47ace94 100644 --- a/airflow/providers/salesforce/CHANGELOG.rst +++ b/docs/apache-airflow-providers-tableau/index.rst @@ -1,3 +1,4 @@ + .. Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information @@ -15,11 +16,29 @@ specific language governing permissions and limitations under the License. +``apache-airflow-providers-tableau`` +======================================= + +Content +------- + +.. toctree:: + :maxdepth: 1 + :caption: Guides + + Connection types <connections/tableau> + +.. toctree:: + :maxdepth: 1 + :caption: References + + Python API <_api/airflow/providers/tableau/index> -Changelog ---------- +.. toctree:: + :maxdepth: 1 + :caption: Resources -1.0.0 -..... + Example DAGs <https://github.com/apache/airflow/tree/master/airflow/providers/tableau/example_dags> + PyPI Repository <https://pypi.org/project/apache-airflow-providers-tableau/> -Initial version of the provider. +.. THE REMINDER OF THE FILE IS AUTOMATICALLY GENERATED. IT WILL BE OVERWRITTEN AT RELEASE TIME! diff --git a/docs/integration-logos/tableau/tableau.png b/docs/integration-logos/tableau/tableau.png new file mode 100644 index 0000000..4ec356c Binary files /dev/null and b/docs/integration-logos/tableau/tableau.png differ diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 71f9e34..0e89285 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1280,6 +1280,7 @@ sync'ed sys syspath systemd +tableau tableauserverclient tablefmt tagKey diff --git a/scripts/in_container/run_install_and_test_provider_packages.sh b/scripts/in_container/run_install_and_test_provider_packages.sh index 76d41e4..5eb039a 100755 --- a/scripts/in_container/run_install_and_test_provider_packages.sh +++ b/scripts/in_container/run_install_and_test_provider_packages.sh @@ -95,7 +95,7 @@ function discover_all_provider_packages() { # Columns is to force it wider, so it doesn't wrap at 80 characters COLUMNS=180 airflow providers list - local expected_number_of_providers=63 + local expected_number_of_providers=64 local actual_number_of_providers actual_providers=$(airflow providers list --output yaml | grep package_name) actual_number_of_providers=$(wc -l <<<"$actual_providers") diff --git a/setup.py b/setup.py index 2867b36..4ee7a5c 100644 --- a/setup.py +++ b/setup.py @@ -444,7 +444,7 @@ statsd = [ 'statsd>=3.3.0, <4.0', ] tableau = [ - 'tableauserverclient~=0.12', + 'tableauserverclient', ] telegram = [ 'python-telegram-bot==13.0', @@ -576,6 +576,7 @@ PROVIDERS_REQUIREMENTS: Dict[str, List[str]] = { 'snowflake': snowflake, 'sqlite': [], 'ssh': ssh, + 'tableau': tableau, 'telegram': telegram, 'vertica': vertica, 'yandex': yandex, @@ -608,7 +609,6 @@ CORE_EXTRAS_REQUIREMENTS: Dict[str, List[str]] = { 'rabbitmq': rabbitmq, 'sentry': sentry, 'statsd': statsd, - 'tableau': tableau, 'virtualenv': virtualenv, } diff --git a/tests/core/test_providers_manager.py b/tests/core/test_providers_manager.py index 39ee588..9112d5e 100644 --- a/tests/core/test_providers_manager.py +++ b/tests/core/test_providers_manager.py @@ -81,6 +81,7 @@ ALL_PROVIDERS = [ # 'apache-airflow-providers-snowflake', 'apache-airflow-providers-sqlite', 'apache-airflow-providers-ssh', + 'apache-airflow-providers-tableau', 'apache-airflow-providers-telegram', 'apache-airflow-providers-vertica', 'apache-airflow-providers-yandex', diff --git a/airflow/providers/salesforce/provider.yaml b/tests/providers/tableau/hooks/__init__.py similarity index 50% copy from airflow/providers/salesforce/provider.yaml copy to tests/providers/tableau/hooks/__init__.py index fe739ff..217e5db 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/tests/providers/tableau/hooks/__init__.py @@ -1,3 +1,4 @@ +# # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information @@ -14,36 +15,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - ---- -package-name: apache-airflow-providers-salesforce -name: Salesforce -description: | - `Salesforce <https://www.salesforce.com/>`__ - -versions: - - 1.0.0 - -integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ - tags: [service] - -operators: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook - -sensors: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status - -hooks: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau - -hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/tests/providers/salesforce/hooks/test_tableau.py b/tests/providers/tableau/hooks/test_tableau.py similarity index 81% rename from tests/providers/salesforce/hooks/test_tableau.py rename to tests/providers/tableau/hooks/test_tableau.py index 130746d..66ecdf7 100644 --- a/tests/providers/salesforce/hooks/test_tableau.py +++ b/tests/providers/tableau/hooks/test_tableau.py @@ -19,12 +19,19 @@ import unittest from unittest.mock import patch from airflow import configuration, models -from airflow.providers.salesforce.hooks.tableau import TableauHook +from airflow.providers.tableau.hooks.tableau import TableauHook from airflow.utils import db class TestTableauHook(unittest.TestCase): + """ + Test class for TableauHook + """ + def setUp(self): + """ + setup + """ configuration.conf.load_test_config() db.merge_conn( @@ -46,9 +53,12 @@ class TestTableauHook(unittest.TestCase): ) ) - @patch('airflow.providers.salesforce.hooks.tableau.TableauAuth') - @patch('airflow.providers.salesforce.hooks.tableau.Server') + @patch('airflow.providers.tableau.hooks.tableau.TableauAuth') + @patch('airflow.providers.tableau.hooks.tableau.Server') def test_get_conn_auth_via_password_and_site_in_connection(self, mock_server, mock_tableau_auth): + """ + Test get conn auth via password + """ with TableauHook(tableau_conn_id='tableau_test_password') as tableau_hook: mock_server.assert_called_once_with(tableau_hook.conn.host, use_server_version=True) mock_tableau_auth.assert_called_once_with( @@ -59,9 +69,12 @@ class TestTableauHook(unittest.TestCase): mock_server.return_value.auth.sign_in.assert_called_once_with(mock_tableau_auth.return_value) mock_server.return_value.auth.sign_out.assert_called_once_with() - @patch('airflow.providers.salesforce.hooks.tableau.PersonalAccessTokenAuth') - @patch('airflow.providers.salesforce.hooks.tableau.Server') + @patch('airflow.providers.tableau.hooks.tableau.PersonalAccessTokenAuth') + @patch('airflow.providers.tableau.hooks.tableau.Server') def test_get_conn_auth_via_token_and_site_in_init(self, mock_server, mock_tableau_auth): + """ + Test get conn auth via token + """ with TableauHook(site_id='test', tableau_conn_id='tableau_test_token') as tableau_hook: mock_server.assert_called_once_with(tableau_hook.conn.host, use_server_version=True) mock_tableau_auth.assert_called_once_with( @@ -74,10 +87,13 @@ class TestTableauHook(unittest.TestCase): ) mock_server.return_value.auth.sign_out.assert_called_once_with() - @patch('airflow.providers.salesforce.hooks.tableau.TableauAuth') - @patch('airflow.providers.salesforce.hooks.tableau.Server') - @patch('airflow.providers.salesforce.hooks.tableau.Pager', return_value=[1, 2, 3]) + @patch('airflow.providers.tableau.hooks.tableau.TableauAuth') + @patch('airflow.providers.tableau.hooks.tableau.Server') + @patch('airflow.providers.tableau.hooks.tableau.Pager', return_value=[1, 2, 3]) def test_get_all(self, mock_pager, mock_server, mock_tableau_auth): # pylint: disable=unused-argument + """ + Test get all + """ with TableauHook(tableau_conn_id='tableau_test_password') as tableau_hook: jobs = tableau_hook.get_all(resource_name='jobs') assert jobs == mock_pager.return_value diff --git a/airflow/providers/salesforce/provider.yaml b/tests/providers/tableau/operators/__init__.py similarity index 50% copy from airflow/providers/salesforce/provider.yaml copy to tests/providers/tableau/operators/__init__.py index fe739ff..13a8339 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/tests/providers/tableau/operators/__init__.py @@ -14,36 +14,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - ---- -package-name: apache-airflow-providers-salesforce -name: Salesforce -description: | - `Salesforce <https://www.salesforce.com/>`__ - -versions: - - 1.0.0 - -integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ - tags: [service] - -operators: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook - -sensors: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status - -hooks: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau - -hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/tests/providers/salesforce/operators/test_tableau_refresh_workbook.py b/tests/providers/tableau/operators/test_tableau_refresh_workbook.py similarity index 80% rename from tests/providers/salesforce/operators/test_tableau_refresh_workbook.py rename to tests/providers/tableau/operators/test_tableau_refresh_workbook.py index 77139c1..72377a5 100644 --- a/tests/providers/salesforce/operators/test_tableau_refresh_workbook.py +++ b/tests/providers/tableau/operators/test_tableau_refresh_workbook.py @@ -21,11 +21,18 @@ from unittest.mock import Mock, patch import pytest from airflow.exceptions import AirflowException -from airflow.providers.salesforce.operators.tableau_refresh_workbook import TableauRefreshWorkbookOperator +from airflow.providers.tableau.operators.tableau_refresh_workbook import TableauRefreshWorkbookOperator class TestTableauRefreshWorkbookOperator(unittest.TestCase): + """ + Test class for TableauRefreshWorkbookOperator + """ + def setUp(self): + """ + setup + """ self.mocked_workbooks = [] for i in range(3): mock_workbook = Mock() @@ -34,8 +41,11 @@ class TestTableauRefreshWorkbookOperator(unittest.TestCase): self.mocked_workbooks.append(mock_workbook) self.kwargs = {'site_id': 'test_site', 'task_id': 'task', 'dag': None} - @patch('airflow.providers.salesforce.operators.tableau_refresh_workbook.TableauHook') + @patch('airflow.providers.tableau.operators.tableau_refresh_workbook.TableauHook') def test_execute(self, mock_tableau_hook): + """ + Test Execute + """ mock_tableau_hook.get_all = Mock(return_value=self.mocked_workbooks) mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook) operator = TableauRefreshWorkbookOperator(blocking=False, workbook_name='wb_2', **self.kwargs) @@ -45,9 +55,12 @@ class TestTableauRefreshWorkbookOperator(unittest.TestCase): mock_tableau_hook.server.workbooks.refresh.assert_called_once_with(2) assert mock_tableau_hook.server.workbooks.refresh.return_value.id == job_id - @patch('airflow.providers.salesforce.sensors.tableau_job_status.TableauJobStatusSensor') - @patch('airflow.providers.salesforce.operators.tableau_refresh_workbook.TableauHook') + @patch('airflow.providers.tableau.sensors.tableau_job_status.TableauJobStatusSensor') + @patch('airflow.providers.tableau.operators.tableau_refresh_workbook.TableauHook') def test_execute_blocking(self, mock_tableau_hook, mock_tableau_job_status_sensor): + """ + Test execute blocking + """ mock_tableau_hook.get_all = Mock(return_value=self.mocked_workbooks) mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook) operator = TableauRefreshWorkbookOperator(workbook_name='wb_2', **self.kwargs) @@ -64,8 +77,11 @@ class TestTableauRefreshWorkbookOperator(unittest.TestCase): dag=None, ) - @patch('airflow.providers.salesforce.operators.tableau_refresh_workbook.TableauHook') + @patch('airflow.providers.tableau.operators.tableau_refresh_workbook.TableauHook') def test_execute_missing_workbook(self, mock_tableau_hook): + """ + Test execute missing workbook + """ mock_tableau_hook.get_all = Mock(return_value=self.mocked_workbooks) mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook) operator = TableauRefreshWorkbookOperator(workbook_name='test', **self.kwargs) diff --git a/airflow/providers/salesforce/provider.yaml b/tests/providers/tableau/sensors/__init__.py similarity index 50% copy from airflow/providers/salesforce/provider.yaml copy to tests/providers/tableau/sensors/__init__.py index fe739ff..13a8339 100644 --- a/airflow/providers/salesforce/provider.yaml +++ b/tests/providers/tableau/sensors/__init__.py @@ -14,36 +14,3 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - ---- -package-name: apache-airflow-providers-salesforce -name: Salesforce -description: | - `Salesforce <https://www.salesforce.com/>`__ - -versions: - - 1.0.0 - -integrations: - - integration-name: Salesforce - external-doc-url: https://www.salesforce.com/ - tags: [service] - -operators: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.operators.tableau_refresh_workbook - -sensors: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.sensors.tableau_job_status - -hooks: - - integration-name: Salesforce - python-modules: - - airflow.providers.salesforce.hooks.salesforce - - airflow.providers.salesforce.hooks.tableau - -hook-class-names: - - airflow.providers.salesforce.hooks.tableau.TableauHook diff --git a/tests/providers/salesforce/sensors/test_tableau_job_status.py b/tests/providers/tableau/sensors/test_tableau_job_status.py similarity index 84% rename from tests/providers/salesforce/sensors/test_tableau_job_status.py rename to tests/providers/tableau/sensors/test_tableau_job_status.py index 7f01011..ea6eeb2 100644 --- a/tests/providers/salesforce/sensors/test_tableau_job_status.py +++ b/tests/providers/tableau/sensors/test_tableau_job_status.py @@ -21,18 +21,25 @@ from unittest.mock import Mock, patch import pytest from parameterized import parameterized -from airflow.providers.salesforce.sensors.tableau_job_status import ( +from airflow.providers.tableau.sensors.tableau_job_status import ( TableauJobFailedException, TableauJobStatusSensor, ) class TestTableauJobStatusSensor(unittest.TestCase): + """ + Test Class for JobStatusSensor + """ + def setUp(self): self.kwargs = {'job_id': 'job_2', 'site_id': 'test_site', 'task_id': 'task', 'dag': None} - @patch('airflow.providers.salesforce.sensors.tableau_job_status.TableauHook') + @patch('airflow.providers.tableau.sensors.tableau_job_status.TableauHook') def test_poke(self, mock_tableau_hook): + """ + Test poke + """ mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook) mock_get = mock_tableau_hook.server.jobs.get_by_id mock_get.return_value.finish_code = '0' @@ -44,8 +51,11 @@ class TestTableauJobStatusSensor(unittest.TestCase): mock_get.assert_called_once_with(sensor.job_id) @parameterized.expand([('1',), ('2',)]) - @patch('airflow.providers.salesforce.sensors.tableau_job_status.TableauHook') + @patch('airflow.providers.tableau.sensors.tableau_job_status.TableauHook') def test_poke_failed(self, finish_code, mock_tableau_hook): + """ + Test poke failed + """ mock_tableau_hook.return_value.__enter__ = Mock(return_value=mock_tableau_hook) mock_get = mock_tableau_hook.server.jobs.get_by_id mock_get.return_value.finish_code = finish_code
