Lee-W commented on code in PR #64591:
URL: https://github.com/apache/airflow/pull/64591#discussion_r3146096014
##########
providers/zendesk/src/airflow/providers/zendesk/hooks/zendesk.py:
##########
@@ -43,54 +59,152 @@ class ZendeskHook(BaseHook):
@classmethod
def get_ui_field_behaviour(cls) -> dict[str, Any]:
+ """Relabel fields for the Connection UI."""
return {
"hidden_fields": ["schema", "port", "extra"],
- "relabeling": {"host": "Zendesk domain", "login": "Zendesk email"},
+ "relabeling": {
+ "host": "Zendesk domain",
+ "login": "Zendesk email",
+ "password": "Password / API token",
+ },
+ }
+
+ @classmethod
+ def get_connection_form_widgets(cls) -> dict[str, Any]:
+ """Add custom widgets for the Connection UI."""
+ from flask_appbuilder.fieldwidgets import BS3PasswordFieldWidget
+ from wtforms import BooleanField, StringField
+
+ return {
+ "use_token": BooleanField(
+ "Use Token", description="If enabled, the password field is
treated as an API token."
+ ),
+ "token": StringField(
+ "API Token",
+ widget=BS3PasswordFieldWidget(),
+ description="Zendesk API token (alternative to password
field).",
+ ),
+ "oauth_token": StringField(
+ "OAuth Token",
+ widget=BS3PasswordFieldWidget(),
+ description="Zendesk OAuth token.",
+ ),
}
def __init__(self, zendesk_conn_id: str = default_conn_name) -> None:
super().__init__()
self.zendesk_conn_id = zendesk_conn_id
self.base_api: BaseApi | None = None
- zenpy_client, url = self._init_conn()
- self.zenpy_client = zenpy_client
Review Comment:
Since this is a public attribute, we need to keep it (or deprecate it
instead of removing it.). Is there any reason we need to make it
`_zenpy_client`?
##########
providers/zendesk/src/airflow/providers/zendesk/hooks/zendesk.py:
##########
@@ -43,54 +59,152 @@ class ZendeskHook(BaseHook):
@classmethod
def get_ui_field_behaviour(cls) -> dict[str, Any]:
+ """Relabel fields for the Connection UI."""
return {
"hidden_fields": ["schema", "port", "extra"],
- "relabeling": {"host": "Zendesk domain", "login": "Zendesk email"},
+ "relabeling": {
+ "host": "Zendesk domain",
+ "login": "Zendesk email",
+ "password": "Password / API token",
+ },
+ }
+
+ @classmethod
+ def get_connection_form_widgets(cls) -> dict[str, Any]:
+ """Add custom widgets for the Connection UI."""
+ from flask_appbuilder.fieldwidgets import BS3PasswordFieldWidget
+ from wtforms import BooleanField, StringField
+
+ return {
+ "use_token": BooleanField(
+ "Use Token", description="If enabled, the password field is
treated as an API token."
+ ),
+ "token": StringField(
+ "API Token",
+ widget=BS3PasswordFieldWidget(),
+ description="Zendesk API token (alternative to password
field).",
+ ),
+ "oauth_token": StringField(
+ "OAuth Token",
+ widget=BS3PasswordFieldWidget(),
+ description="Zendesk OAuth token.",
+ ),
}
def __init__(self, zendesk_conn_id: str = default_conn_name) -> None:
super().__init__()
self.zendesk_conn_id = zendesk_conn_id
self.base_api: BaseApi | None = None
- zenpy_client, url = self._init_conn()
- self.zenpy_client = zenpy_client
Review Comment:
```python
@cached_property
def zenpy_client(self) -> Zenpy:
client, self._url = self._init_conn()
return client
```
or we might want to try something like this
##########
providers/zendesk/src/airflow/providers/zendesk/hooks/zendesk.py:
##########
@@ -43,54 +59,152 @@ class ZendeskHook(BaseHook):
@classmethod
def get_ui_field_behaviour(cls) -> dict[str, Any]:
+ """Relabel fields for the Connection UI."""
return {
"hidden_fields": ["schema", "port", "extra"],
- "relabeling": {"host": "Zendesk domain", "login": "Zendesk email"},
+ "relabeling": {
+ "host": "Zendesk domain",
+ "login": "Zendesk email",
+ "password": "Password / API token",
+ },
+ }
+
+ @classmethod
+ def get_connection_form_widgets(cls) -> dict[str, Any]:
+ """Add custom widgets for the Connection UI."""
+ from flask_appbuilder.fieldwidgets import BS3PasswordFieldWidget
+ from wtforms import BooleanField, StringField
+
+ return {
+ "use_token": BooleanField(
+ "Use Token", description="If enabled, the password field is
treated as an API token."
+ ),
+ "token": StringField(
+ "API Token",
+ widget=BS3PasswordFieldWidget(),
+ description="Zendesk API token (alternative to password
field).",
+ ),
+ "oauth_token": StringField(
+ "OAuth Token",
+ widget=BS3PasswordFieldWidget(),
+ description="Zendesk OAuth token.",
+ ),
}
def __init__(self, zendesk_conn_id: str = default_conn_name) -> None:
super().__init__()
self.zendesk_conn_id = zendesk_conn_id
self.base_api: BaseApi | None = None
- zenpy_client, url = self._init_conn()
- self.zenpy_client = zenpy_client
- self.__url = url
- self.get = self.zenpy_client.users._get
+ self._zenpy_client: Zenpy | None = None
+ self._url: str = ""
def _init_conn(self) -> tuple[Zenpy, str]:
"""
- Create the Zenpy Client for our Zendesk connection.
+ Create the Zenpy Client for the Zendesk connection.
+
+ Parses the host into ``domain`` and (optionally) ``subdomain`` for
Zenpy.
+ For example, ``yoursubdomain.zendesk.com`` produces
+ ``domain="zendesk.com"`` and ``subdomain="yoursubdomain"``.
- :return: zenpy.Zenpy client and the url for the API.
+ Authentication kwargs are resolved from Connection extras according to
+ the precedence documented on the class docstring.
+
+ :return: (zenpy.Zenpy client, base URL string)
+ :raises ValueError: if the host is missing or has an invalid format.
"""
conn = self.get_connection(self.zendesk_conn_id)
- domain = ""
- url = ""
- subdomain: str | None = None
- if conn.host:
- url = "https://" + conn.host
- domain = conn.host
- if conn.host.count(".") >= 2:
- dot_splitted_string = conn.host.rsplit(".", 2)
- subdomain = dot_splitted_string[0]
- domain = ".".join(dot_splitted_string[1:])
- return Zenpy(domain=domain, subdomain=subdomain, email=conn.login,
password=conn.password), url
+
+ if not conn.host:
+ raise ValueError(
+ f"No host provided for connection '{self.zendesk_conn_id}'. "
+ "Set the host to your Zendesk domain, e.g.
'yoursubdomain.zendesk.com'."
+ )
+
+ # Parse host into subdomain + domain.
+ # Handle trailing dots and extract domain (last two parts) and
subdomain (the rest).
+ host = conn.host.strip("/")
+ if host.endswith("."):
+ host = host[:-1]
+
+ parts = host.split(".")
+ if len(parts) < 2:
+ raise ValueError(
+ f"Invalid host format '{conn.host}' for connection
'{self.zendesk_conn_id}'. "
+ "Expected a domain with at least one dot, e.g.
'yoursubdomain.zendesk.com'."
+ )
+
+ domain = ".".join(parts[-2:])
+ subdomain: str | None = ".".join(parts[:-2]) if len(parts) > 2 else
None
+ url = "https://" + host
Review Comment:
```suggestion
url = f"https://{host}"
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]