dabla commented on code in PR #62790:
URL: https://github.com/apache/airflow/pull/62790#discussion_r2964309065


##########
providers/ibm/mq/src/airflow/providers/ibm/mq/hooks/mq.py:
##########
@@ -0,0 +1,358 @@
+#
+# 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
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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
+#
+#   http://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.
+from __future__ import annotations
+
+import asyncio
+import json
+import threading
+from contextlib import contextmanager, suppress
+from typing import Any
+
+from asgiref.sync import sync_to_async
+
+from airflow.sdk.bases.hook import BaseHook
+
+# Backoff parameters for transient consume failures
+_BACKOFF_BASE: float = 1.0
+_BACKOFF_MAX: float = 60.0
+_BACKOFF_FACTOR: float = 2.0
+
+
+class IBMMQHook(BaseHook):
+    conn_name_attr = "conn_id"
+    default_conn_name = "mq_default"
+    conn_type = "mq"
+    hook_name = "IBM MQ"
+    default_open_options = "MQOO_INPUT_SHARED"
+
+    def __init__(
+        self,
+        conn_id: str = default_conn_name,
+        queue_manager: str | None = None,
+        channel: str | None = None,
+        open_options: int | None = None,
+    ):
+        super().__init__()
+        self.conn_id = conn_id
+        self.queue_manager = queue_manager
+        self.channel = channel
+        self.open_options = open_options
+        self._conn = None
+
+    @classmethod
+    def get_ui_field_behaviour(cls) -> dict[str, Any]:
+        """Return custom UI field behaviour for IBM MQ Connection."""
+        return {
+            "hidden_fields": ["schema"],
+            "placeholders": {
+                "host": "mq.example.com",
+                "port": "1414",
+                "login": "app_user",
+                "extra": json.dumps(
+                    {
+                        "queue_manager": "QM1",
+                        "channel": "DEV.APP.SVRCONN",
+                        "open_options": cls.default_open_options,
+                    },
+                    indent=2,
+                ),
+            },
+        }
+
+    @classmethod
+    def get_open_options_flags(cls, open_options: int) -> list[str]:
+        """
+        Return the symbolic MQ open option flags set in a given bitmask.
+
+        Each flag corresponds to a constant in 'ibmmq.CMQC' that starts with 
'MQOO_'.
+
+        :param open_options: The integer bitmask used when opening an MQ queue
+                             (e.g., 'MQOO_INPUT_EXCLUSIVE | 
MQOO_FAIL_IF_QUIESCING').
+
+        :return: A list of the names of the MQ open flags that are set in the 
bitmask.
+                 For example, '['MQOO_INPUT_EXCLUSIVE', 
'MQOO_FAIL_IF_QUIESCING']'.
+
+        Example:
+            >>> open_options = ibmmq.CMQC.MQOO_INPUT_SHARED | 
ibmmq.CMQC.MQOO_FAIL_IF_QUIESCING
+            >>> cls.get_open_options_flags(open_options)
+            ['MQOO_INPUT_SHARED', 'MQOO_FAIL_IF_QUIESCING']
+        """
+        import ibmmq
+
+        return [
+            name
+            for name, value in vars(ibmmq.CMQC).items()
+            if name.startswith("MQOO_") and (open_options & value)
+        ]
+
+    @contextmanager
+    def get_conn(self):
+        """
+        Sync context manager for IBM MQ connection lifecycle.
+
+        Must be called from the executor thread (not the event loop thread).
+        Retrieves the Airflow connection, extracts MQ parameters, and manages
+        the IBM MQ connection lifecycle.
+
+        :yield: IBM MQ connection object
+        """
+        import ibmmq
+
+        connection = BaseHook.get_connection(self.conn_id)
+        config = connection.extra_dejson
+        queue_manager = self.queue_manager or config.get("queue_manager")
+        channel = self.channel or config.get("channel")
+

Review Comment:
   I understand the concern about mutating `self.open_options` inside 
`get_conn()`. In this case, the mutation is intentional and scoped: it 
represents a one-time resolution of configuration into a concrete value, rather 
than state that changes across calls.
   
   `open_options` is effectively *derived configuration*. Resolving it once and 
storing it on the instance avoids repeating the lookup logic on every call to 
`get_conn()`, while still keeping the method focused on establishing a 
connection.
   
   To make that explicit and safe, the mutation is guarded:
   
   ```python
   if not self.open_options:
       self.open_options = getattr(
           ibmmq.CMQC,
           config.get("open_options", self.default_open_options),
           ibmmq.CMQC.MQOO_INPUT_EXCLUSIVE,
       )
   ```
   
   This ensures:
   - The value is resolved at most once per instance  
   - Any explicitly provided `open_options` is preserved  
   - We avoid recomputing or propagating additional state through the method  
   
   An additional practical consideration is that resolving `open_options` 
requires access to the connection config (e.g. from `extra_dejson`). If we were 
to use a local variable instead, we would need to fetch and parse that 
configuration on every call to `get_conn()`, which can trigger repeated lookups 
against the API server. Avoiding that extra overhead is another reason to cache 
the resolved value on the instance.
   
   Returning both a connection and `open_options` would complicate the API and 
blur the responsibility of `get_conn()`, whereas this approach keeps the 
interface simple and the behavior predictable.
   
   If needed, this could be clarified further by documenting that 
`open_options` is lazily initialized on first use.



-- 
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]

Reply via email to