diff --git a/changelog.md b/changelog.md index 6d0fe0104b09c65a734d6c3641189ba07196695d_Y2hhbmdlbG9nLm1k..6c07b79a3b2b7cd6590090fda544a6c4228a2666_Y2hhbmdlbG9nLm1k 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,13 @@ PyMQI changelog --------------- +* **1.11.0** (2020-06-11) + + * Moved from MQAI to raw PCF, making it possible to use PCF commands on z/OS in addition to other systems + * Added a wait (expiry) interval to PCF messages (needed for large responses) + * Added ability to request a conversion of response PCF messages (required for z/OS) + * Thanks to @salapat11 for the assistance in preparing this release + * **1.10.1** (2020-02-15) * Added [automatic conversion of Unicode to bytes](https://dsuch.github.io/pymqi/examples.html#sending-unicode-data-vs-bytes) diff --git a/code/pymqi/__init__.py b/code/pymqi/__init__.py index 6d0fe0104b09c65a734d6c3641189ba07196695d_Y29kZS9weW1xaS9fX2luaXRfXy5weQ==..6c07b79a3b2b7cd6590090fda544a6c4228a2666_Y29kZS9weW1xaS9fX2luaXRfXy5weQ== 100644 --- a/code/pymqi/__init__.py +++ b/code/pymqi/__init__.py @@ -97,6 +97,8 @@ import ctypes import sys +from typing import Any, Optional, Union, Dict + # import xml parser. lxml/etree only available since python 2.5 use_minidom = False try: @@ -100,6 +102,5 @@ # import xml parser. lxml/etree only available since python 2.5 use_minidom = False try: - # noinspection PyUnresolvedReferences - import lxml.etree + import lxml.etree # type: ignore except ImportError: @@ -105,6 +106,6 @@ except ImportError: - from xml.dom.minidom import parseString + from xml.dom.minidom import parseString # type: ignore use_minidom = True # Python 3.8+ DLL loading try: @@ -107,8 +108,8 @@ use_minidom = True # Python 3.8+ DLL loading try: - from os import add_dll_directory + from os import add_dll_directory # type: ignore from os import environ from os.path import join from os.path import exists @@ -125,5 +126,5 @@ # PyMQI try: - from . import pymqe + from . import pymqe # type: ignore except ImportError: @@ -129,5 +130,5 @@ except ImportError: - import pymqe # Backward compatibility + import pymqe # type: ignore # Backward compatibility from pymqi import CMQCFC from pymqi import CMQC, CMQXC, CMQZC @@ -136,7 +137,7 @@ CMQZC = CMQZC unicode = object() -__version__ = '1.10.1' +__version__ = '1.11.0' __mqlevels__ = pymqe.__mqlevels__ __mqbuild__ = pymqe.__mqbuild__ @@ -178,6 +179,12 @@ else: return s +def padded_count(count, boundary=4): + # type: (int, int) -> int + """Calculate padded bytes count + """ + return count + ((boundary - count & (boundary - 1)) & (boundary - 1)) + # # 64bit suppport courtesy of Brent S. Elmer, Ph.D. (mailto:webe3vt@aim.com) # @@ -249,6 +256,7 @@ """ def __init__(self, memlist, **kw): + # type: (list, Any) -> None """ Initialise the option structure. 'list' is a list of structure member names, default values and pack/unpack formats. 'kw' is an optional keyword dictionary that may be used to override default @@ -260,10 +268,10 @@ # Dict to store c_char arrays to prevent memory addresses # from getting overwritten - self.__vs_ctype_store = {} + self.__vs_ctype_store = {} # type: Dict[str, Any] # Create the structure members as instance attributes and build # the struct.pack/unpack format string. The attribute name is # identical to the 'C' structure member name. for i in memlist: setattr(self, i[0], i[1]) @@ -264,10 +272,14 @@ # Create the structure members as instance attributes and build # the struct.pack/unpack format string. The attribute name is # identical to the 'C' structure member name. for i in memlist: setattr(self, i[0], i[1]) - self.__format = self.__format + i[2] - self.set(**kw) + try: + i[3] + except: + i.append(1) + self.__format = self.__format + i[2] * i[3] + self.set(**kw), list def pack(self): @@ -272,5 +284,6 @@ def pack(self): + # type: () -> bytes """ Pack the attributes into a 'C' structure to be passed to MQI calls. The pack order is as defined to the MQOpts ctor. Returns the structure as a string buffer. @@ -294,6 +307,7 @@ return struct.pack(*args) def unpack(self, buff): + # type (bytes) """ Unpack a 'C' structure 'buff' into self. """ ensure_not_unicode(buff) # Python 3 bytes check @@ -308,8 +322,17 @@ r = struct.unpack(self.__format, buff) x = 0 for i in self.__list: - ensure_not_unicode(r[x]) # Python 3 bytes check - setattr(self, i[0], r[x]) - x = x + 1 + + if isinstance(i[1], list): + l = [] + for j in range(i[3]): + ensure_not_unicode(r[x]) # Python 3 bytes check + l.append(r[x]) + x = x + 1 + setattr(self, i[0], l) + else: + ensure_not_unicode(r[x]) # Python 3 bytes check + setattr(self, i[0], r[x]) + x = x + 1 def set(self, **kw): @@ -314,5 +337,6 @@ def set(self, **kw): + # types: (Dict[str, Any]) """ Set a structure member using the keyword dictionary 'kw'. An AttributeError exception is raised for invalid member names. """ @@ -325,6 +349,7 @@ setattr(self, str(i), kw[i]) def __setitem__(self, key, value): + # types: (str, Any) """ Set the structure member attribute 'key' to 'value', as in obj['Attr'] = 42. """ # Only set if the attribute already exists. getattr raises an @@ -334,6 +359,7 @@ setattr(self, key, value) def get(self): + # types: () -> dict """ Return a dictionary of the current structure member values. The dictionary is keyed by a 'C' member name. """ d = {} @@ -342,8 +368,9 @@ return d def __getitem__(self, key): + # types: (str) -> Any """Return the member value associated with key, as in print obj['Attr']. """ return getattr(self, key) def __str__(self): @@ -345,8 +372,9 @@ """Return the member value associated with key, as in print obj['Attr']. """ return getattr(self, key) def __str__(self): + # types: () -> str rv = '' for i in self.__list: rv = rv + str(i[0]) + ': ' + str(getattr(self, i[0])) + '\n' @@ -354,8 +382,9 @@ return rv[:-1] def __repr__(self): + # types: () -> str """ Return the packed buffer as a printable string. """ return str(self.pack()) def get_length(self): @@ -357,10 +386,11 @@ """ Return the packed buffer as a printable string. """ return str(self.pack()) def get_length(self): + # types: () -> int """ Returns the length of the (would be) packed buffer. """ return struct.calcsize(self.__format) def set_vs(self, vs_name, vs_value=None, vs_offset=0, vs_buffer_size=0, vs_ccsid=0): @@ -362,8 +392,9 @@ """ Returns the length of the (would be) packed buffer. """ return struct.calcsize(self.__format) def set_vs(self, vs_name, vs_value=None, vs_offset=0, vs_buffer_size=0, vs_ccsid=0): + # types: (str, Union[bytes, str, None], int, int, int) """ This method aids in the setting of the MQCHARV (variable length string) types in MQ structures. The type contains a pointer to a variable length string. A common example of a MQCHARV type @@ -410,6 +441,7 @@ self.__vs_ctype_store[vs_name] = c_vs_value def get_vs(self, vs_name): + # types: (str) -> Union[bytes, str, None] """ This method returns the string to which the VSPtr pointer points to. """ # if the VSPtr name is passed - remove VSPtr to be left with name. @@ -425,6 +457,7 @@ return c_vs_value + # # Sub-classes of MQOpts representing real MQI structures. # @@ -1134,6 +1167,186 @@ super(XQH, self).__init__(tuple(opts), **kw) + +class CFH(MQOpts): + """ Construct an MQCFH Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None + opts = [['Type', CMQCFC.MQCFT_COMMAND, MQLONG_TYPE], + ['StrucLength', CMQCFC.MQCFH_STRUC_LENGTH, MQLONG_TYPE], + ['Version', CMQCFC.MQCFH_VERSION_1, MQLONG_TYPE], + ['Command', CMQCFC.MQCMD_NONE, MQLONG_TYPE], + ['MsgSeqNumber', 1, MQLONG_TYPE], + ['Control', CMQCFC.MQCFC_LAST, MQLONG_TYPE], + ['CompCode', CMQC.MQCC_OK, MQLONG_TYPE], + ['Reason', CMQC.MQRC_NONE, MQLONG_TYPE], + ['ParameterCount', 0, MQLONG_TYPE] + ] + super(CFH, self).__init__(tuple(opts), **kw) + +class CFBF(MQOpts): + """ Construct an MQCFBF Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None + filter_value = kw.pop('FilterValue', '') + filter_value_length = kw.pop('FilterValueLength', len(filter_value)) + padded_filter_value_length = padded_count(filter_value_length) + + opts = [['Type', CMQCFC.MQCFT_BYTE_STRING_FILTER, MQLONG_TYPE], + ['StrucLength', + CMQCFC.MQCFBF_STRUC_LENGTH_FIXED + padded_filter_value_length, MQLONG_TYPE], + ['Parameter', 0, MQLONG_TYPE], + ['Operator', 0, MQLONG_TYPE], + ['FilterValueLength', filter_value_length, MQLONG_TYPE], + ['FilterValue', filter_value, '{}s'.format(padded_filter_value_length)] + ] + + super(CFBF, self).__init__(tuple(opts), **kw) + +class CFBS(MQOpts): + """ Construct an MQCFBS Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None + string = kw.pop('String', '') + string_length = kw.pop('StringLength', len(string)) + padded_string_length = padded_count(string_length) + + opts = [['Type', CMQCFC.MQCFT_BYTE_STRING, MQLONG_TYPE], + ['StrucLength', + CMQCFC.MQCFBS_STRUC_LENGTH_FIXED + padded_string_length, MQLONG_TYPE], + ['Parameter', 0, MQLONG_TYPE], + ['StringLength', string_length, MQLONG_TYPE], + ['String', string, '{}s'.format(padded_string_length)] + ] + + super(CFBS, self).__init__(tuple(opts), **kw) + +class CFIF(MQOpts): + """ Construct an MQCFIF Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None + opts = [['Type', CMQCFC.MQCFT_INTEGER_FILTER, MQLONG_TYPE], + ['StrucLength', CMQCFC.MQCFIF_STRUC_LENGTH, MQLONG_TYPE], + ['Parameter', 0, MQLONG_TYPE], + ['Operator', 0, MQLONG_TYPE], + ['FilterValue', 0, MQLONG_TYPE] + ] + + super(CFIF, self).__init__(tuple(opts), **kw) + +class CFIL(MQOpts): + """ Construct an MQCFIL Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None + values = kw.pop('Values', []) + count = kw.pop('Count', len(values)) + + opts = [['Type', CMQCFC.MQCFT_INTEGER_LIST, MQLONG_TYPE], + ['StrucLength', CMQCFC.MQCFIL_STRUC_LENGTH_FIXED + 4 * count, MQLONG_TYPE], # Check python 2 + ['Parameter', 0, MQLONG_TYPE], + ['Count', count, MQLONG_TYPE], + ['Values', values, MQLONG_TYPE, count], + ] + super(CFIL, self).__init__(tuple(opts), **kw) + +class CFIN(MQOpts): + """ Construct an MQCFIN Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None -> None + + opts = [['Type', CMQCFC.MQCFT_INTEGER, MQLONG_TYPE], + ['StrucLength', CMQCFC.MQCFIN_STRUC_LENGTH, MQLONG_TYPE], + ['Parameter', 0, MQLONG_TYPE], + ['Value', 0, MQLONG_TYPE], + ] + super(CFIN, self).__init__(tuple(opts), **kw) + +class CFSF(MQOpts): + """ Construct an MQCFSF Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None + filter_value = kw.pop('FilterValue', '') + filter_value_length = kw.pop('FilterValueLength', len(filter_value)) + padded_filter_value_length = padded_count(filter_value_length) + + opts = [['Type', CMQCFC.MQCFT_STRING_FILTER, MQLONG_TYPE], + ['StrucLength', + CMQCFC.MQCFSF_STRUC_LENGTH_FIXED + padded_filter_value_length, MQLONG_TYPE], + ['Parameter', 0, MQLONG_TYPE], + ['Operator', 0, MQLONG_TYPE], + ['CodedCharSetId', CMQC.MQCCSI_DEFAULT, MQLONG_TYPE], + ['FilterValueLength', filter_value_length, MQLONG_TYPE], + ['FilterValue', filter_value, '{}s'.format(padded_filter_value_length)] + ] + + super(CFSF, self).__init__(tuple(opts), **kw) + +class CFSL(MQOpts): + """ Construct an MQCFSL Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None + strings = kw.pop('Strings', []) + string_length = kw.pop('StringLength', len(max(strings, key=len)) if strings else 0) + + strings_count = len(strings) + count = kw.pop('Count', strings_count) + + max_string_length = padded_count(string_length) if count else 0 + padded_strings_length = (max_string_length) * strings_count + + opts = [['Type', CMQCFC.MQCFT_STRING_LIST, MQLONG_TYPE], + ['StrucLength', CMQCFC.MQCFSL_STRUC_LENGTH_FIXED + padded_strings_length, MQLONG_TYPE], + ['Parameter', 0, MQLONG_TYPE], + ['CodedCharSetId', CMQC.MQCCSI_DEFAULT, MQLONG_TYPE], + ['Count', count, MQLONG_TYPE], + ['StringLength', max_string_length, MQLONG_TYPE], + ['Strings', strings if strings else [b''], '{}s'.format(max_string_length), (count if count else 1)] + ] + + super(CFSL, self).__init__(tuple(opts), **kw) + +class CFST(MQOpts): + """ Construct an MQCFST Structure with default values as per MQI. + The default values may be overridden by the optional keyword arguments 'kw'. + """ + + def __init__(self, **kw): + # types: (Dict[str, Any]) -> None + string = kw.pop('String', '') + string_length = kw.pop('StringLength', len(string)) + padded_string_length = padded_count(string_length) + + opts = [['Type', CMQCFC.MQCFT_STRING, MQLONG_TYPE], + ['StrucLength', CMQCFC.MQCFST_STRUC_LENGTH_FIXED + padded_string_length, MQLONG_TYPE], + ['Parameter', 0, MQLONG_TYPE], + ['CodedCharSetId', CMQC.MQCCSI_DEFAULT, MQLONG_TYPE], + ['StringLength', string_length, MQLONG_TYPE], + ['String', string, '{}s'.format(padded_string_length)] + ] + + super(CFST, self).__init__(tuple(opts), **kw) + # # A utility to convert a MQ constant to its string mnemonic by groping # a module dictonary @@ -1187,5 +1400,7 @@ """ Exception class for MQI low level errors. """ errStringDicts = (_MQConst2String(CMQC, 'MQRC_'), _MQConst2String(CMQCFC, 'MQRCCF_'),) + comp = CMQC.MQCC_OK + reason = CMQC.MQRC_NONE def __init__(self, comp, reason, **kw): @@ -1190,5 +1405,6 @@ def __init__(self, comp, reason, **kw): + # types: (int, int, Dict[str, Any]) -> None """ Construct the error object with MQI completion code 'comp' and reason code 'reason'. """ self.comp, self.reason = comp, reason @@ -1197,6 +1413,7 @@ setattr(self, key, kw[key]) def __str__(self): + # types: () -> str return 'MQI Error. Comp: %d, Reason %d: %s' % (self.comp, self.reason, self.errorAsString()) def errorAsString(self): @@ -1200,6 +1417,7 @@ return 'MQI Error. Comp: %d, Reason %d: %s' % (self.comp, self.reason, self.errorAsString()) def errorAsString(self): + # types: () -> str """ Return the exception object MQI warning/failed reason as its mnemonic string. """ if self.comp == CMQC.MQCC_OK: @@ -1234,7 +1452,9 @@ default, the Queue Manager is implicitly connected. If required, the connection may be deferred until a call to connect(). """ - def __init__(self, name='', disconnect_on_exit=True, bytes_encoding=default.bytes_encoding, default_ccsid=default.ccsid): + def __init__(self, name='', disconnect_on_exit=True, + bytes_encoding=default.bytes_encoding, default_ccsid=default.ccsid): + # type: (Optional[str], bool, str, int) -> None """ Connect to the Queue Manager 'name' (default value ''). If 'name' is None, don't connect now, but defer the connection until connect() is called. Input 'bytes_encoding' and 'default_ccsid' are the encodings that will be used in PCF, MQPUT and MQPUT1 calls @@ -1253,6 +1473,7 @@ self.connect(name) def __del__(self): + # types: () """ Disconnect from the queue Manager, if connected. """ if self.__handle: @@ -1269,6 +1490,7 @@ pass def connect(self, name): + # types: (str) """connect(name) Connect immediately to the Queue Manager 'name'.""" @@ -1284,6 +1506,7 @@ # Connect options suggested by Jaco Smuts (mailto:JSmuts@clover.co.za) def connect_with_options(self, name, *args, **kwargs): + # types: (str, Any, Dict[str, Any]) """connect_with_options(name [, opts=cnoopts][ ,cd=mqcd][ ,sco=mqsco]) connect_with_options(name, cd, [sco]) @@ -1341,6 +1564,10 @@ rv = pymqe.MQCONNX(name, options, cd, user_password, sco.pack()) + if rv[1] <= CMQC.MQCC_WARNING: + self.__handle = rv[0] + self.__name = name + if rv[1]: raise MQMIError(rv[1], rv[2]) @@ -1344,10 +1571,8 @@ if rv[1]: raise MQMIError(rv[1], rv[2]) - self.__handle = rv[0] - self.__name = name # Backward compatibility connectWithOptions = connect_with_options def connect_tcp_client(self, name, cd, channel, conn_name, user, password): @@ -1349,9 +1574,9 @@ # Backward compatibility connectWithOptions = connect_with_options def connect_tcp_client(self, name, cd, channel, conn_name, user, password): - # type: (str, CD, str, str, str, str) + # type: (str, CD, str, str, str, str) -> None """ Connect immediately to the remote Queue Manager 'name', using a TCP Client connection, with channnel 'channel' and the TCP connection string 'conn_name'. All other connection @@ -1375,6 +1600,7 @@ connectTCPClient = connect_tcp_client def disconnect(self): + # type: () -> None """ Disconnect from queue manager, if connected. """ if not self.__handle: @@ -1383,6 +1609,7 @@ self.__handle = self.__qmobj = None def get_handle(self): + # type: () -> None """ Get the queue manager handle. The handle is used for other pymqi calls. """ if self.__handle: @@ -1394,6 +1621,7 @@ getHandle = get_handle def begin(self): + # type: () -> None """ Begin a new global transaction. """ rv = pymqe.MQBEGIN(self.__handle) @@ -1401,6 +1629,7 @@ raise MQMIError(rv[0], rv[1]) def commit(self): + # type: () -> None """ Commits any outstanding gets/puts in the current unit of work. """ rv = pymqe.MQCMIT(self.__handle) @@ -1408,6 +1637,7 @@ raise MQMIError(rv[0], rv[1]) def backout(self): + # type: () -> None """ Backout any outstanding gets/puts in the current unit of work. """ rv = pymqe.MQBACK(self.__handle) @@ -1415,6 +1645,7 @@ raise MQMIError(rv[0], rv[1]) def put1(self, qDesc, msg, *opts): + # type: (Union[str, bytes, OD], Optional[bytes], Union[MD, OD]) -> None """ Put the single message in string buffer 'msg' on the queue using the MQI PUT1 call. This encapsulates calls to MQOPEN, MQPUT and MQCLOSE. put1 is the optimal way to put a single @@ -1438,10 +1669,10 @@ if not isinstance(msg, bytes): if ( - (is_py3 and isinstance(msg, str)) # Python 3 string is unicode - or - (is_py2 and isinstance(msg, unicode)) # Python 2.7 string can be unicode - ): + (is_py3 and isinstance(msg, str)) # Python 3 string is unicode + or + (is_py2 and isinstance(msg, unicode)) # type: ignore # Python 2.7 string can be unicode + ): msg = msg.encode(self.bytes_encoding) m_desc.CodedCharSetId = self.default_ccsid m_desc.Format = CMQC.MQFMT_STRING @@ -1460,6 +1691,7 @@ put_opts.unpack(rv[1]) def inquire(self, attribute): + # types: (str) -> Any """ Inquire on queue manager 'attribute'. Returns either the integer or string value for the attribute. """ attribute = ensure_bytes(attribute) # Python 3 strings to be converted to bytes @@ -1477,9 +1709,10 @@ return rv[0] def _is_connected(self): + # types: () -> bool """ Try pinging the queue manager in order to see whether the application is connected to it. Note that the method is merely a convienece wrapper around MQCMD_PING_Q_MGR, in particular, there's still possibility that the app will disconnect between checking QueueManager.is_connected and the next MQ call. """ @@ -1480,8 +1713,8 @@ """ Try pinging the queue manager in order to see whether the application is connected to it. Note that the method is merely a convienece wrapper around MQCMD_PING_Q_MGR, in particular, there's still possibility that the app will disconnect between checking QueueManager.is_connected and the next MQ call. """ - pcf = PCFExecute(self) + try: @@ -1487,4 +1720,5 @@ try: + pcf = PCFExecute(self) pcf.MQCMD_PING_Q_MGR() except Exception: return False @@ -1495,6 +1729,7 @@ # Some support functions for Queue ops. def make_q_desc(qDescOrString): + # types: (Union[str, bytes, OD]) -> OD """Maybe make MQOD from string. Module Private""" if isinstance(qDescOrString, (str, bytes)): return OD(ObjectName=ensure_bytes(qDescOrString)) # Python 3 strings to be converted to bytes @@ -1621,10 +1856,10 @@ if not isinstance(msg, bytes): if ( - (is_py3 and isinstance(msg, str)) # Python 3 string is unicode - or - (is_py2 and isinstance(msg, unicode)) # Python 2.7 string can be unicode - ): + (is_py3 and isinstance(msg, str)) # Python 3 string is unicode + or + (is_py2 and isinstance(msg, unicode)) # Python 2.7 string can be unicode + ): msg = msg.encode(self.__qMgr.bytes_encoding) m_desc.CodedCharSetId = self.__qMgr.default_ccsid m_desc.Format = CMQC.MQFMT_STRING @@ -1723,10 +1958,10 @@ # Accept truncated message if ((rv[-1] == CMQC.MQRC_TRUNCATED_MSG_ACCEPTED) or - # Do not reread message with original length - (rv[-1] == CMQC.MQRC_TRUNCATED_MSG_FAILED and maxLength is not None) or - # Other errors - (rv[-1] != CMQC.MQRC_TRUNCATED_MSG_FAILED)): + # Do not reread message with original length + (rv[-1] == CMQC.MQRC_TRUNCATED_MSG_FAILED and maxLength is not None) or + # Other errors + (rv[-1] != CMQC.MQRC_TRUNCATED_MSG_FAILED)): if rv[-2] == CMQC.MQCC_WARNING: m_desc.unpack(rv[1]) get_opts.unpack(rv[2]) @@ -1785,6 +2020,7 @@ return msg def close(self, options=CMQC.MQCO_NONE): + # type: (int) -> None """ Close a queue, using options. """ if not self.__qHandle: @@ -1827,7 +2063,7 @@ """ self.__qHandle = queue_handle - def get_handle(self): + def get_handle(self): # type: () -> Queue """ Get the queue handle. """ return self.__qHandle @@ -2235,7 +2471,7 @@ the selector, value and the operator to use. For instance, the can be respectively MQCA_Q_DESC, 'MY.QUEUE.*', MQCFOP_LIKE. Compare with the pymqi.Filter class. """ - _pymqi_filter_type = None + _pymqi_filter_type = None # type: Optional[str] def __init__(self, selector, value, operator): self.selector = selector # this is int @@ -2325,7 +2561,8 @@ # class _Method: def __init__(self, pcf, name): + # types: (PCFExecute, str) -> None self.__pcf = pcf self.__name = name def __getattr__(self, name): @@ -2328,7 +2565,8 @@ self.__pcf = pcf self.__name = name def __getattr__(self, name): + # types: (str) -> _Method return _Method(self.__pcf, '%s.%s' % (self.__name, name)) def __call__(self, *args): @@ -2332,6 +2570,7 @@ return _Method(self.__pcf, '%s.%s' % (self.__name, name)) def __call__(self, *args): + # types: (Unions[dict, list, _Filter]) -> list if self.__name[0:7] == 'CMQCFC.': self.__name = self.__name[7:] if self.__pcf.qm: @@ -2352,7 +2591,10 @@ else: args_dict, filters = {}, [] - # Assuming that a given PCF call requires any arguments, There will be a dictionary at args[0] - # On the lower lever, pymqe expects that values of this dictionary will be bytes objects. - # Hence we need to iterate over this dictionary and convert any Unicode objects to bytes. + mqcfh = CFH(Version=CMQCFC.MQCFH_VERSION_3, + Command=CMQCFC.__dict__[self.__name], + Type=CMQCFC.MQCFT_COMMAND_XR, + ParameterCount=len(args_dict) + len(filters)) + message = mqcfh.pack() + if args_dict: @@ -2358,12 +2600,95 @@ if args_dict: - for key, value in args_dict.items(): - if is_unicode(value): - args_dict[key] = value.encode(bytes_encoding) - - rv = pymqe.mqaiExecute(qm_handle, CMQCFC.__dict__[self.__name], args_dict, filters) - if rv[1]: - raise MQMIError(rv[-2], rv[-1]) - return rv[0] + if isinstance(args_dict, dict): + for key, value in args_dict.items(): + if isinstance(value, (str, bytes)): + if is_unicode(value): + value = value.encode(bytes_encoding) + parameter = CFST(Parameter=key, + String=value) + elif (isinstance(value, ByteString)): + parameter = CFBS(Parameter=key, + String=value.value.encode(bytes_encoding)) + elif isinstance(value, int): + # Backward compatibility for MQAI behaviour + # for single value instead of list + is_list = False + for item in CMQCFC.__dict__: + if ((item[:7] == 'MQIACF_' + or + item[:7] == 'MQIACH_') + and item[-6:] == '_ATTRS' + and CMQCFC.__dict__[item] == key): + is_list = True + break + if not is_list: + parameter = CFIN(Parameter=key, + Value=value) + else: + parameter = CFIL(Parameter=key, + Values=[value]) + elif (isinstance(value, list) + and isinstance(value[0], int)): + parameter = CFIL(Parameter=key, + Values=value) + + message = message + parameter.pack() + elif isinstance(args_dict, list): + for parameter in args_dict: + message = message + parameter.pack() + + if filters: + for pcf_filter in filters: + if isinstance(pcf_filter, _Filter): + if pcf_filter._pymqi_filter_type == 'string': + pcf_filter = CFSF(Parameter=pcf_filter.selector, + Operator=pcf_filter.operator, + FilterValue=pcf_filter.value) + elif pcf_filter._pymqi_filter_type == 'integer': + pcf_filter = CFIF(Parameter=pcf_filter.selector, + Operator=pcf_filter.operator, + FilterValue=pcf_filter.value) + + message = message + pcf_filter.pack() + + command_queue = Queue(self.__pcf.qm, + self.__pcf._command_queue_name, + CMQC.MQOO_OUTPUT) + + put_md = MD(Format=CMQC.MQFMT_ADMIN, + MsgType=CMQC.MQMT_REQUEST, + ReplyToQ=self.__pcf._reply_queue_name, + Feedback=CMQC.MQFB_NONE, + Expiry=self.__pcf.response_wait_interval, + Report=CMQC.MQRO_PASS_DISCARD_AND_EXPIRY | CMQC.MQRO_DISCARD_MSG) + put_opts = PMO(Options=CMQC.MQPMO_NO_SYNCPOINT) + + command_queue.put(message, put_md, put_opts) + command_queue.close() + + gmo_options = CMQC.MQGMO_NO_SYNCPOINT + CMQC.MQGMO_FAIL_IF_QUIESCING + \ + CMQC.MQGMO_WAIT + + if self.__pcf.convert: + gmo_options = gmo_options + CMQC.MQGMO_CONVERT + + get_opts = GMO( + Options=gmo_options, + Version=CMQC.MQGMO_VERSION_2, + MatchOptions=CMQC.MQMO_MATCH_CORREL_ID, + WaitInterval=self.__pcf.response_wait_interval) + get_md = MD(CorrelId=put_md.MsgId) + + ress = [] + while True: + message = self.__pcf._reply_queue.get(None, get_md, get_opts) + res, control = self.__pcf.unpack(message) + + ress.append(res) + + if control == CMQCFC.MQCFC_LAST: + break + + return ress # # Execute a PCF commmand. Inspired by Maas-Maarten Zeeman @@ -2377,6 +2702,13 @@ its used. PCF commands are executed by calling a CMQC defined MQCMD_* method on the object. """ + _reply_queue = None # type: Queue + _reply_queue_name = None # type: str + _command_queue_name = b'SYSTEM.ADMIN.COMMAND.QUEUE' # type: bytes + response_wait_interval = 0 + + qm = None # type: Optional[QueueManager] + iaStringDict = _MQConst2String(CMQC, 'MQIA_') caStringDict = _MQConst2String(CMQC, 'MQCA_') @@ -2380,10 +2712,17 @@ iaStringDict = _MQConst2String(CMQC, 'MQIA_') caStringDict = _MQConst2String(CMQC, 'MQCA_') - def __init__(self, name=''): + def __init__(self, name=None, + disconnect_on_exit=True, + model_queue_name=b'SYSTEM.DEFAULT.MODEL.QUEUE', + dynamic_queue_name=b'PYMQPCF.*', + command_queue_name=b'', + response_wait_interval=100, + convert=False): + # type: (Any, bool, bytes, bytes, bytes) -> None """PCFExecute(name = '') Connect to the Queue Manager 'name' (default value '') ready for a PCF command. If name is a QueueManager instance, it is used for the connection, otherwise a new connection is made """ @@ -2384,9 +2723,15 @@ """PCFExecute(name = '') Connect to the Queue Manager 'name' (default value '') ready for a PCF command. If name is a QueueManager instance, it is used for the connection, otherwise a new connection is made """ + self.response_wait_interval = response_wait_interval + self.convert = convert + + if command_queue_name: + self._command_queue_name = command_queue_name + if isinstance(name, QueueManager): self.qm = name super(PCFExecute, self).__init__(None) @@ -2394,6 +2739,14 @@ self.qm = None super(PCFExecute, self).__init__(name) + if not self._reply_queue and not self._reply_queue_name: + od = OD(ObjectName=model_queue_name, + DynamicQName=dynamic_queue_name) + + self._reply_queue = Queue(self.qm, od, CMQC.MQOO_INPUT_EXCLUSIVE) + self._reply_queue_name = od.ObjectName.strip() + + def __getattr__(self, name): """MQCMD_*(attrDict) @@ -2440,6 +2793,83 @@ # Backward compatibility stringifyKeys = stringify_keys + def disconnect(self): + """ Disconnect from reply_queue + """ + try: + if self._reply_queue and self._reply_queue.get_handle(): + self._reply_queue.close() + except MQMIError as ex: + pass + finally: + self._reply_queue = None + self._reply_queue_name = None + + @staticmethod + def unpack(message): # type: (bytes) -> dict + """Unpack PCF message to dictionary + """ + + mqcfh = CFH(Version=CMQCFC.MQCFH_VERSION_1) + mqcfh.unpack(message[:CMQCFC.MQCFH_STRUC_LENGTH]) + + if mqcfh.Version != CMQCFC.MQCFH_VERSION_1: + mqcfh = CFH(Version=mqcfh.Version) + mqcfh.unpack(message[:CMQCFC.MQCFH_STRUC_LENGTH]) + + if mqcfh.CompCode: + raise MQMIError(mqcfh.CompCode, mqcfh.Reason) + + res = {} + index = mqcfh.ParameterCount + cursor = CMQCFC.MQCFH_STRUC_LENGTH + parameter = None # type: Optional[MQOpts] + while (index > 0): + if message[cursor] == CMQCFC.MQCFT_STRING: + parameter = CFST() + parameter.unpack(message[cursor:cursor + CMQCFC.MQCFST_STRUC_LENGTH_FIXED]) + if parameter.StringLength > 1: + parameter = CFST(StringLength=parameter.StringLength) + parameter.unpack(message[cursor:cursor + parameter.StrucLength]) + value = parameter.String + elif message[cursor] == CMQCFC.MQCFT_STRING_LIST: + parameter = CFSL() + parameter.unpack(message[cursor:cursor + CMQCFC.MQCFSL_STRUC_LENGTH_FIXED]) + if parameter.StringLength > 1: + parameter = CFSL(StringLength=parameter.StringLength, + Count=parameter.Count, + StrucLength=parameter.StrucLength) + parameter.unpack(message[cursor:cursor + parameter.StrucLength]) + value = parameter.Strings + elif message[cursor] == CMQCFC.MQCFT_INTEGER: + parameter = CFIN() + parameter.unpack(message[cursor:cursor + CMQCFC.MQCFIN_STRUC_LENGTH]) + value = parameter.Value + elif message[cursor] == CMQCFC.MQCFT_INTEGER_LIST: + parameter = CFIL() + parameter.unpack(message[cursor:cursor + CMQCFC.MQCFIL_STRUC_LENGTH_FIXED]) + if parameter.Count > 0: + parameter = CFIL(Count=parameter.Count, + StrucLength=parameter.StrucLength) + parameter.unpack(message[cursor:cursor + parameter.StrucLength]) + value = parameter.Values + elif message[cursor] == CMQCFC.MQCFT_BYTE_STRING: + parameter = CFBS() + parameter.unpack(message[cursor:cursor + CMQCFC.MQCFBS_STRUC_LENGTH_FIXED]) + if parameter.StringLength > 1: + parameter = CFBS(StringLength=parameter.StringLength) + parameter.unpack(message[cursor:cursor + parameter.StrucLength]) + value = parameter.String + else: + pcf_type = struct.unpack(MQLONG_TYPE, message[cursor:cursor + 4]) + raise NotImplementedError('Unpack for type ({}) not implemented'.format(pcf_type)) + index -= 1 + cursor += parameter.StrucLength + res[parameter.Parameter] = value + + return res, mqcfh.Control + + class ByteString(object): """ A simple wrapper around string values, suitable for passing into PyMQI calls wherever IBM's docs state a 'byte string' object should be passed in. @@ -2452,7 +2882,7 @@ return len(self.value) def connect(queue_manager, channel=None, conn_info=None, user=None, password=None, disconnect_on_exit=True, - bytes_encoding=default.bytes_encoding, default_ccsid=default.ccsid): + bytes_encoding=default.bytes_encoding, default_ccsid=default.ccsid): """ A convenience wrapper for connecting to MQ queue managers. If given the 'queue_manager' parameter only, will try connecting to it in bindings mode. If given both 'channel' and 'conn_info' will connect in client mode. @@ -2464,7 +2894,8 @@ return qmgr elif queue_manager: - qmgr = QueueManager(queue_manager, disconnect_on_exit, bytes_encoding=bytes_encoding, default_ccsid=default.ccsid) + qmgr = QueueManager(queue_manager, disconnect_on_exit, + bytes_encoding=bytes_encoding, default_ccsid=default.ccsid) return qmgr else: diff --git a/code/pymqi/pymqe.c b/code/pymqi/pymqe.c index 6d0fe0104b09c65a734d6c3641189ba07196695d_Y29kZS9weW1xaS9weW1xZS5j..6c07b79a3b2b7cd6590090fda544a6c4228a2666_Y29kZS9weW1xaS9weW1xZS5j 100644 --- a/code/pymqi/pymqe.c +++ b/code/pymqi/pymqe.c @@ -45,7 +45,7 @@ * */ -static char __version__[] = "1.10.1"; +static char __version__[] = "1.11.0"; static char pymqe_doc[] = " \ pymqe - A Python MQ Extension. This presents a low-level Python \ diff --git a/code/setup.py b/code/setup.py index 6d0fe0104b09c65a734d6c3641189ba07196695d_Y29kZS9zZXR1cC5weQ==..6c07b79a3b2b7cd6590090fda544a6c4228a2666_Y29kZS9zZXR1cC5weQ== 100644 --- a/code/setup.py +++ b/code/setup.py @@ -16,7 +16,7 @@ from distutils import spawn from struct import calcsize -version = '1.10.1' +version = '1.11.0' # Build either in bindings or client mode. bindings_mode = 0 diff --git a/code/tests/requirements.txt b/code/tests/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..6c07b79a3b2b7cd6590090fda544a6c4228a2666_Y29kZS90ZXN0cy9yZXF1aXJlbWVudHMudHh0 --- /dev/null +++ b/code/tests/requirements.txt @@ -0,0 +1,3 @@ +pytest +testfixtures +nose \ No newline at end of file diff --git a/code/tests/test_pcf.py b/code/tests/test_pcf.py index 6d0fe0104b09c65a734d6c3641189ba07196695d_Y29kZS90ZXN0cy90ZXN0X3BjZi5weQ==..6c07b79a3b2b7cd6590090fda544a6c4228a2666_Y29kZS90ZXN0cy90ZXN0X3BjZi5weQ== 100644 --- a/code/tests/test_pcf.py +++ b/code/tests/test_pcf.py @@ -1,3 +1,5 @@ -"""Test PCF usage -""" +"""Test PCF usage.""" +from unittest import skip +from ddt import data +from ddt import ddt @@ -3,9 +5,6 @@ -import unittest - -import config -import utils -import env +from test_setup import Tests # noqa +from test_setup import main # noqa import pymqi @@ -9,6 +8,15 @@ import pymqi -class TestPCF(unittest.TestCase): - def setUp(self): +@ddt +class TestPCF(Tests): + """Class for MQ PCF testing.""" + + pcf = None + + @classmethod + def setUpClass(cls): + """Initialize test environment.""" + super(TestPCF, cls).setUpClass() + # max length of queue names is 48 characters @@ -14,9 +22,4 @@ # max length of queue names is 48 characters - self.queue_name = "{prefix}PCF.QUEUE".format(prefix=config.MQ.QUEUE.PREFIX) - self.queue_manager = config.MQ.QM.NAME - self.channel = config.MQ.QM.CHANNEL - self.host = config.MQ.QM.HOST - self.port = config.MQ.QM.PORT - self.user = config.MQ.QM.USER - self.password = config.MQ.QM.PASSWORD + cls.queue_name = "{prefix}PCF.QUEUE".format(prefix=cls.prefix) + cls.pcf = pymqi.PCFExecute(cls.qmgr, response_wait_interval=600) @@ -22,3 +25,6 @@ - self.conn_info = "{0}({1})".format(self.host, self.port) + @classmethod + def tearDownClass(cls): + """Tear down test environment.""" + cls.pcf.disconnect() @@ -24,7 +30,10 @@ - self.qmgr = pymqi.QueueManager(None) - self.qmgr.connectTCPClient(self.queue_manager, pymqi.CD(), self.channel, self.conn_info, self.user, self.password) + super(TestPCF, cls).tearDownClass() + + def setUp(self): + """Set up tesing environment.""" + super(TestPCF, self).setUp() self.create_queue(self.queue_name) def tearDown(self): @@ -27,8 +36,7 @@ self.create_queue(self.queue_name) def tearDown(self): - """Delete the created objects. - """ + """Delete the created objects.""" if self.queue_name: self.delete_queue(self.queue_name) @@ -33,4 +41,53 @@ if self.queue_name: self.delete_queue(self.queue_name) - self.qmgr.disconnect() + + super(TestPCF, self).tearDown() + + @skip('Not implemented') + def test_mqcfbf(self): + """Test MQCFBF PCF byte string filter parameter.""" + + def test_mqcfbs(self): + """Test MQCFBS PCF byte string parameter. + + Also uses MQCFIN and MQCFIL as parameters + """ + attrs = [] + attrs.append(pymqi.CFBS(Parameter=pymqi.CMQCFC.MQBACF_GENERIC_CONNECTION_ID, + String=b'')) + attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACF_CONN_INFO_TYPE, + Value=pymqi.CMQCFC.MQIACF_CONN_INFO_CONN)) + attrs.append(pymqi.CFIL(Parameter=pymqi.CMQCFC.MQIACF_CONNECTION_ATTRS, + Values=[pymqi.CMQCFC.MQIACF_ALL])) + + object_filters = [] + object_filters.append( + pymqi.CFIF(Parameter=pymqi.CMQC.MQIA_APPL_TYPE, + Operator=pymqi.CMQCFC.MQCFOP_EQUAL, + FilterValue=pymqi.CMQC.MQAT_USER)) + + results = self.pcf.MQCMD_INQUIRE_CONNECTION(attrs, object_filters) + + self.assertGreater(len(results), 0) + + def test_mqcfif(self): + """Test string filter MQCFIF. + + Also uses MQCFST, MQCFIN and MQCFIL as parameters + """ + attrs = [] + attrs.append(pymqi.CFST(Parameter=pymqi.CMQC.MQCA_Q_NAME, + String=b'*')) + attrs.append(pymqi.CFIN(Parameter=pymqi.CMQC.MQIA_Q_TYPE, + Value=pymqi.CMQC.MQQT_LOCAL)) + attrs.append(pymqi.CFIL(Parameter=pymqi.CMQCFC.MQIACF_Q_ATTRS, + Values=[pymqi.CMQC.MQIA_CURRENT_Q_DEPTH, pymqi.CMQC.MQCA_Q_DESC])) + + object_filters = [] + object_filters.append( + pymqi.CFIF(Parameter=pymqi.CMQC.MQIA_CURRENT_Q_DEPTH, + Operator=pymqi.CMQCFC.MQCFOP_GREATER, + FilterValue=0)) + + results = self.pcf.MQCMD_INQUIRE_Q(attrs, object_filters) @@ -36,5 +93,62 @@ - def create_queue(self, queue_name): - queue_type = pymqi.CMQC.MQQT_LOCAL - max_depth = 5000 + self.assertTrue(results, 'Queue not found') + for result in results: + self.assertTrue(result[pymqi.CMQC.MQIA_CURRENT_Q_DEPTH] > 0, + 'Found Queue with depth {}'.format(result[pymqi.CMQC.MQIA_CURRENT_Q_DEPTH])) + def test_mqcfsf(self): + """Test string filter MQCFSF. + + Also uses MQCFST, MQCFIN and MQCFIL as parameters + """ + attrs = [] + attrs.append(pymqi.CFST(Parameter=pymqi.CMQC.MQCA_Q_NAME, + String=b'*')) + attrs.append(pymqi.CFIN(Parameter=pymqi.CMQC.MQIA_Q_TYPE, + Value=pymqi.CMQC.MQQT_LOCAL)) + attrs.append(pymqi.CFIL(Parameter=pymqi.CMQCFC.MQIACF_Q_ATTRS, + Values=[pymqi.CMQC.MQIA_CURRENT_Q_DEPTH, pymqi.CMQC.MQCA_Q_DESC])) + + object_filters = [] + object_filters.append( + pymqi.CFSF(Parameter=pymqi.CMQC.MQCA_Q_DESC, + Operator=pymqi.CMQCFC.MQCFOP_LIKE, + FilterValue=b'IBM MQ*')) + + results = self.pcf.MQCMD_INQUIRE_Q(attrs, object_filters) + + self.assertTrue(results, 'Queue not found') + for result in results: + self.assertTrue(not result[pymqi.CMQC.MQCA_Q_DESC].startswith(b'MQ'), + 'Found Queue with description {}'.format(result[pymqi.CMQC.MQCA_Q_DESC])) + self.assertTrue(pymqi.CMQC.MQCA_Q_DESC in result, + 'Attribute {} is not returned'.format(result[pymqi.CMQC.MQCA_Q_DESC])) + + @data([], [b'One'], [b'One', b'Two', b'Three']) + def test_mqcfsl(self, value): + """Test MQCFSL PCF string list parameter. + + Also uses MQCFST and MQCFIN as parameters + """ + attrs = [] + attrs.append(pymqi.CFST(Parameter=pymqi.CMQC.MQCA_NAMELIST_NAME, + String='{}NAMELIST'.format(self.prefix).encode())) + attrs.append(pymqi.CFSL(Parameter=pymqi.CMQC.MQCA_NAMES, + Strings=value)) + attrs.append(pymqi.CFIN(Parameter=pymqi.CMQCFC.MQIACF_REPLACE, + Value=pymqi.CMQCFC.MQRP_YES)) + + try: + self.pcf.MQCMD_CREATE_NAMELIST(attrs) + except Exception: # pylint: disable=broad-except + self.fail('Exception occurs!') + else: + attrs = [] + attrs.append(pymqi.CFST(Parameter=pymqi.CMQC.MQCA_NAMELIST_NAME, + String='{}NAMELIST'.format(self.prefix).encode())) + attrs.append(pymqi.CFIL(Parameter=pymqi.CMQCFC.MQIACF_NAMELIST_ATTRS, + Values=[pymqi.CMQC.MQCA_NAMES, pymqi.CMQC.MQIA_NAME_COUNT])) + + results = self.pcf.MQCMD_INQUIRE_NAMELIST(attrs) + + self.assertEqual(results[0][pymqi.CMQC.MQIA_NAME_COUNT], len(value)) @@ -40,9 +154,59 @@ - args = {pymqi.CMQC.MQCA_Q_NAME: utils.py3str2bytes(queue_name), - pymqi.CMQC.MQIA_Q_TYPE: queue_type, - pymqi.CMQC.MQCA_Q_DESC: utils.py3str2bytes('PCF testing'), - pymqi.CMQCFC.MQIACF_REPLACE: pymqi.CMQCFC.MQRP_YES} - pcf = pymqi.PCFExecute(self.qmgr) - pcf.MQCMD_CREATE_Q(args) - pcf.disconnect + if results[0][pymqi.CMQC.MQIA_NAME_COUNT] > 0: + for item in results[0][pymqi.CMQC.MQCA_NAMES]: + item = item.strip() + self.assertTrue(item in value, '{} value not in values list'.format(item)) + value.remove(item) + + attrs = [] + attrs.append(pymqi.CFST(Parameter=pymqi.CMQC.MQCA_NAMELIST_NAME, + String='{}NAMELIST'.format(self.prefix).encode())) + self.pcf.MQCMD_DELETE_NAMELIST(attrs) + + + @data([], [1], [1, 2, 3, 4, 5]) + def test_arbitrary_message_with_mqcfil(self, value): + """Test arbitrary message with MQCFIL.""" + message = pymqi.CFH(Version=pymqi.CMQCFC.MQCFH_VERSION_1, + Type=pymqi.CMQCFC.MQCFT_USER, + ParameterCount=1).pack() + message = message + pymqi.CFIL(Parameter=1, + Values=value).pack() + + queue = pymqi.Queue(self.qmgr, self.queue_name, + pymqi.CMQC.MQOO_INPUT_AS_Q_DEF + pymqi.CMQC.MQOO_OUTPUT) + + put_md = pymqi.MD(Format=pymqi.CMQC.MQFMT_PCF) + queue.put(message, put_md) + + get_opts = pymqi.GMO( + Options=pymqi.CMQC.MQGMO_NO_SYNCPOINT + pymqi.CMQC.MQGMO_FAIL_IF_QUIESCING, + Version=pymqi.CMQC.MQGMO_VERSION_2, + MatchOptions=pymqi.CMQC.MQMO_MATCH_CORREL_ID) + get_md = pymqi.MD(MsgId=put_md.MsgId) # pylint: disable=no-member + message = queue.get(None, get_md, get_opts) + queue.close() + message = pymqi.PCFExecute.unpack(message) + + self.assertTrue(isinstance(message[0][1], list), + 'Returned value is not list: {}'.format(type(message[0][1]))) + + self.assertTrue(len(message[0][1]) == len(value), 'List length is different!') + + for item in message[0][1]: + self.assertTrue(item in value, '{} value not in values list'.format(item)) + value.remove(item) + + def test_mqcfbs_old(self): + """Test byte string MQCFBS with old style.""" + attrs = { + pymqi.CMQCFC.MQBACF_GENERIC_CONNECTION_ID: pymqi.ByteString(''), + pymqi.CMQCFC.MQIACF_CONN_INFO_TYPE: pymqi.CMQCFC.MQIACF_CONN_INFO_CONN, + pymqi.CMQCFC.MQIACF_CONNECTION_ATTRS: [pymqi.CMQCFC.MQIACF_ALL] + } + fltr = pymqi.Filter(pymqi.CMQC.MQIA_APPL_TYPE).equal(pymqi.CMQC.MQAT_USER) + + results = self.pcf.MQCMD_INQUIRE_CONNECTION(attrs, [fltr]) + + self.assertGreater(len(results), 0) @@ -48,4 +212,60 @@ - def delete_queue(self, queue_name): + @data(pymqi.CMQCFC.MQIACF_ALL, [pymqi.CMQCFC.MQIACF_ALL], + pymqi.CMQC.MQCA_Q_DESC, [pymqi.CMQC.MQCA_Q_DESC], + [pymqi.CMQC.MQIA_CURRENT_Q_DEPTH, pymqi.CMQC.MQCA_Q_DESC]) + def test_object_filter_int_old_queue(self, value): + """Test object filter with integer attribute. Old style.""" + attrs = { + pymqi.CMQC.MQCA_Q_NAME: b'*', + pymqi.CMQCFC.MQIACF_Q_ATTRS: value + } + + filter_depth = pymqi.Filter(pymqi.CMQC.MQIA_CURRENT_Q_DEPTH).greater(0) + + results = self.pcf.MQCMD_INQUIRE_Q(attrs, [filter_depth]) + + self.assertTrue(results, 'Queue not found') + for result in results: + self.assertTrue(result[pymqi.CMQC.MQIA_CURRENT_Q_DEPTH] > 0, + 'Found Queue with depth {}'.format(result[pymqi.CMQC.MQIA_CURRENT_Q_DEPTH])) + + @skip('https://stackoverflow.com/questions/62250844/ibm-mq-pcf-parameters-order') + @data(pymqi.CMQCFC.MQIACF_ALL, [pymqi.CMQCFC.MQIACF_ALL], + pymqi.CMQCFC.MQCACH_DESC, [pymqi.CMQCFC.MQCACH_DESC], + [pymqi.CMQCFC.MQCACH_DESC, pymqi.CMQCFC.MQIACH_CHANNEL_TYPE]) + def test_object_filter_int_old_channel(self, value): + """Test object filter with integer attribute. Old style.""" + attrs = { + pymqi.CMQCFC.MQCACH_CHANNEL_NAME: b'*', + pymqi.CMQCFC.MQIACF_CHANNEL_ATTRS: value} + + filter_type = pymqi.Filter(pymqi.CMQCFC.MQIACH_CHANNEL_TYPE).equal(pymqi.CMQC.MQCHT_SVRCONN) + + results = self.pcf.MQCMD_INQUIRE_CHANNEL(attrs, [filter_type]) + + self.assertTrue(results, 'Channel not found') + for result in results: + self.assertTrue(result[pymqi.CMQCFC.MQIACH_CHANNEL_TYPE] == pymqi.CMQC.MQCHT_SVRCONN, + 'Found Channel with type {}'.format(result[pymqi.CMQCFC.MQIACH_CHANNEL_TYPE])) + + def test_object_filter_str_old(self): + """Test object filter with string attribute. Old style.""" + attrs = { + pymqi.CMQC.MQCA_Q_NAME: b'*', + pymqi.CMQCFC.MQIACF_Q_ATTRS: [pymqi.CMQC.MQIA_CURRENT_Q_DEPTH, pymqi.CMQC.MQCA_Q_DESC] + } + + filter_depth = pymqi.Filter(pymqi.CMQC.MQCA_Q_DESC).like(b'IBM MQ *') + + results = self.pcf.MQCMD_INQUIRE_Q(attrs, [filter_depth]) + + self.assertTrue(results, 'Queue not found') + for result in results: + self.assertTrue(not result[pymqi.CMQC.MQCA_Q_DESC].startswith(b'MQ'), + 'Found Queue with description {}'.format(result[pymqi.CMQC.MQCA_Q_DESC])) + + def test_disconnect(self): + """Test disconnect for PCF object.""" + # pylint: disable=protected-access pcf = pymqi.PCFExecute(self.qmgr) @@ -50,7 +270,3 @@ pcf = pymqi.PCFExecute(self.qmgr) - args = {pymqi.CMQC.MQCA_Q_NAME: utils.py3str2bytes(queue_name), - pymqi.CMQCFC.MQIACF_PURGE: pymqi.CMQCFC.MQPO_YES} - pcf.MQCMD_DELETE_Q(args) - pcf.disconnect @@ -56,8 +272,4 @@ - def test_object_inquire_multiple_attributes(self): - attrs = { - pymqi.CMQC.MQCA_Q_NAME : utils.py3str2bytes(self.queue_name), - pymqi.CMQC.MQIA_Q_TYPE : pymqi.CMQC.MQQT_LOCAL, - pymqi.CMQCFC.MQIACF_Q_ATTRS : [pymqi.CMQC.MQIA_CURRENT_Q_DEPTH, pymqi.CMQC.MQCA_Q_DESC] - } + self.assertTrue(pcf._reply_queue) + self.assertTrue(pcf._reply_queue_name) @@ -63,14 +275,8 @@ - pcf = pymqi.PCFExecute(self.qmgr) - results = pcf.MQCMD_INQUIRE_Q(attrs) - pcf.disconnect - - queue_inquired = False - for result in results: - if result.get(pymqi.CMQC.MQCA_Q_NAME).decode().strip() == self.queue_name: - if pymqi.CMQC.MQIA_CURRENT_Q_DEPTH in result and pymqi.CMQC.MQCA_Q_DESC in result: - queue_inquired = True - - self.assertEqual(True, queue_inquired) + pcf.disconnect() + + self.assertTrue(self.qmgr) + self.assertFalse(pcf._reply_queue) + self.assertFalse(pcf._reply_queue_name) if __name__ == "__main__": @@ -75,3 +281,3 @@ if __name__ == "__main__": - unittest.main() + main(module="test_pcf") diff --git a/code/tests/test_queue_manager.py b/code/tests/test_queue_manager.py index 6d0fe0104b09c65a734d6c3641189ba07196695d_Y29kZS90ZXN0cy90ZXN0X3F1ZXVlX21hbmFnZXIucHk=..6c07b79a3b2b7cd6590090fda544a6c4228a2666_Y29kZS90ZXN0cy90ZXN0X3F1ZXVlX21hbmFnZXIucHk= 100644 --- a/code/tests/test_queue_manager.py +++ b/code/tests/test_queue_manager.py @@ -20,5 +20,5 @@ import pymqi import pymqi.CMQC - + class TestQueueManager(unittest.TestCase): @@ -24,5 +24,5 @@ class TestQueueManager(unittest.TestCase): - + qm_name = config.MQ.QM.NAME channel = config.MQ.QM.CHANNEL host = config.MQ.QM.HOST @@ -33,7 +33,7 @@ user = config.MQ.QM.USER password = config.MQ.QM.PASSWORD - + def test_init_none(self): qmgr = pymqi.QueueManager(None) self.assertFalse(qmgr.is_connected) @@ -67,7 +67,7 @@ qmgr.connect(self.qm_name) self.assertTrue(qmgr.is_connected) qmgr.disconnect() - + def test_connect_tcp_client(self): qmgr = pymqi.QueueManager(None) qmgr.connect_tcp_client( @@ -79,8 +79,7 @@ def test_connect_tcp_client_conection_list(self): qmgr = pymqi.QueueManager(None) self.conn_info = '127.0.0.1(22),{0}'.format(self.conn_info) - #self.conn_info = '127.0.0.1(1314)' qmgr.connect_tcp_client( self.qm_name, pymqi.cd(), self.channel, self.conn_info, user=self.user, password=self.password) self.assertTrue(qmgr.is_connected) @@ -83,8 +82,8 @@ qmgr.connect_tcp_client( self.qm_name, pymqi.cd(), self.channel, self.conn_info, user=self.user, password=self.password) self.assertTrue(qmgr.is_connected) - qmgr.disconnect() + qmgr.disconnect() # This test overlaps with # test_mq80.test_successful_connect_without_optional_credentials, @@ -122,7 +121,7 @@ handle = qmgr.get_handle() # assertIsInstance is available >= Python2.7 self.assertTrue(isinstance(handle, int)) - + @unittest.skip('Not implemented yet') def test_begin(self): pass @@ -155,5 +154,5 @@ attribute = pymqi.CMQC.MQCA_Q_MGR_NAME expected_value = utils.py3str2bytes(self.qm_name) attribute_value = qmgr.inquire(attribute) - self.assertEqual(len(attribute_value), pymqi.CMQC.MQ_Q_MGR_NAME_LENGTH) + self.assertEqual(len(attribute_value), pymqi.CMQC.MQ_Q_MGR_NAME_LENGTH) self.assertEqual(attribute_value.strip(), expected_value) @@ -159,5 +158,5 @@ self.assertEqual(attribute_value.strip(), expected_value) - + def test_is_connected(self): """Makes sure the QueueManager's 'is_connected' property works as expected. @@ -201,7 +200,7 @@ password) eq_(qmgr.is_connected, expected) - + if __name__ == '__main__': unittest.main() diff --git a/code/tests/test_setup.py b/code/tests/test_setup.py index 6d0fe0104b09c65a734d6c3641189ba07196695d_Y29kZS90ZXN0cy90ZXN0X3NldHVwLnB5..6c07b79a3b2b7cd6590090fda544a6c4228a2666_Y29kZS90ZXN0cy90ZXN0X3NldHVwLnB5 100644 --- a/code/tests/test_setup.py +++ b/code/tests/test_setup.py @@ -1,5 +1,7 @@ """Setup tests environment.""" import os.path -import unittest +from unittest import TestCase # noqa +from unittest import main # pylint: disable=unused-import + import pymqi @@ -4,5 +6,5 @@ import pymqi -import config -import utils +import config # noqa +import utils # noqa @@ -8,5 +10,6 @@ -class Tests(unittest.TestCase): + +class Tests(TestCase): """Setup and tearsdown tests environment.""" version = '0000000' @@ -19,4 +22,6 @@ user = '' password = '' + prefix = '' + qmgr = None @@ -22,6 +27,6 @@ qmgr = None - + @classmethod def setUpClass(cls): """Initialize test environment.""" @@ -24,8 +29,8 @@ @classmethod def setUpClass(cls): """Initialize test environment.""" - cls.prefix = os.environ.get('PYMQI_TEST_OBJECT_PREFIX', '') + cls.prefix = os.environ.get('PYMQI_TEST_OBJECT_PREFIX', 'PYMQI.') # max length of queue names is 48 characters cls.queue_name = "{prefix}MSG.QUEUE".format(prefix=config.MQ.QUEUE.PREFIX) @@ -39,7 +44,11 @@ cls.conn_info = "{0}({1})".format(cls.host, cls.port) cls.qmgr = pymqi.QueueManager(None) - cls.qmgr.connectTCPClient(cls.queue_manager, pymqi.CD(), cls.channel, cls.conn_info, cls.user, cls.password) + try: + cls.qmgr.connectTCPClient(cls.queue_manager, pymqi.CD(), cls.channel, cls.conn_info, cls.user, cls.password) + except pymqi.MQMIError as ex: + if ex.comp == pymqi.CMQC.MQCC_FAILED: + raise ex cls.version = cls.inquire_qmgr_version().decode() @@ -49,11 +58,7 @@ cls.qmgr.disconnect() def setUp(self): - """Setup tests environmet. - Configuration for setup provided by config.py - Creates connection `self.qmgr` to Queue Manager `self.queue_manager` - and creates queue `self.queue_name` - """ + """Set up tesing environment.""" def tearDown(self): """Clear test environment.""" @@ -57,6 +62,6 @@ def tearDown(self): """Clear test environment.""" - + @classmethod def inquire_qmgr_version(cls): @@ -61,5 +66,6 @@ @classmethod def inquire_qmgr_version(cls): + """Inqure Queue Manager version.""" return cls.qmgr.inquire(pymqi.CMQC.MQCA_VERSION) def create_queue(self, queue_name, max_depth=5000, args=None): @@ -63,6 +69,7 @@ return cls.qmgr.inquire(pymqi.CMQC.MQCA_VERSION) def create_queue(self, queue_name, max_depth=5000, args=None): + """Create queue.""" if args: args[pymqi.CMQC.MQCA_Q_NAME] = utils.py3str2bytes(queue_name) else: @@ -74,7 +81,8 @@ pcf.MQCMD_CREATE_Q(args) def delete_queue(self, queue_name): + """Delete queue.""" pcf = pymqi.PCFExecute(self.qmgr) args = {pymqi.CMQC.MQCA_Q_NAME: utils.py3str2bytes(queue_name), pymqi.CMQCFC.MQIACF_PURGE: pymqi.CMQCFC.MQPO_YES} pcf.MQCMD_DELETE_Q(args) @@ -77,6 +85,6 @@ pcf = pymqi.PCFExecute(self.qmgr) args = {pymqi.CMQC.MQCA_Q_NAME: utils.py3str2bytes(queue_name), pymqi.CMQCFC.MQIACF_PURGE: pymqi.CMQCFC.MQPO_YES} pcf.MQCMD_DELETE_Q(args) - + def create_channel(self, channel_name, args=None): @@ -82,4 +90,5 @@ def create_channel(self, channel_name, args=None): + """Create channle.""" if args: args[pymqi.CMQCFC.MQCACH_CHANNEL_NAME] = utils.py3str2bytes(channel_name) else: @@ -90,8 +99,9 @@ pcf.MQCMD_CREATE_CHANNEL(args) def delete_channel(self, channel_name): + """Delete channel.""" pcf = pymqi.PCFExecute(self.qmgr) args = {pymqi.CMQCFC.MQCACH_CHANNEL_NAME: utils.py3str2bytes(channel_name)} pcf.MQCMD_DELETE_CHANNEL(args) def create_auth_rec(self, args): @@ -93,7 +103,8 @@ pcf = pymqi.PCFExecute(self.qmgr) args = {pymqi.CMQCFC.MQCACH_CHANNEL_NAME: utils.py3str2bytes(channel_name)} pcf.MQCMD_DELETE_CHANNEL(args) def create_auth_rec(self, args): + """Create authentication recoed.""" pcf = pymqi.PCFExecute(self.qmgr) pcf.MQCMD_SET_CHLAUTH_REC(args) @@ -98,4 +109,4 @@ pcf = pymqi.PCFExecute(self.qmgr) pcf.MQCMD_SET_CHLAUTH_REC(args) - + def delete_auth_rec(self, args): @@ -101,3 +112,4 @@ def delete_auth_rec(self, args): + """Delete authentication recoed.""" pcf = pymqi.PCFExecute(self.qmgr) pcf.MQCMD_SET_CHLAUTH_REC(args) @@ -102,5 +114,5 @@ pcf = pymqi.PCFExecute(self.qmgr) pcf.MQCMD_SET_CHLAUTH_REC(args) - + @classmethod def edit_qmgr(cls, args): @@ -105,5 +117,6 @@ @classmethod def edit_qmgr(cls, args): + """Edit connected Queue Manager.""" pcf = pymqi.PCFExecute(cls.qmgr) pcf.MQCMD_CHANGE_Q_MGR(args) diff --git a/docs/examples.rst b/docs/examples.rst index 6d0fe0104b09c65a734d6c3641189ba07196695d_ZG9jcy9leGFtcGxlcy5yc3Q=..6c07b79a3b2b7cd6590090fda544a6c4228a2666_ZG9jcy9leGFtcGxlcy5yc3Q= 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1278,7 +1278,7 @@ filter1 = pymqi.Filter(CMQC.MQCA_Q_DESC).like('IBM MQ *') filter2 = pymqi.Filter(CMQC.MQIA_CURRENT_Q_DEPTH).greater(2) - result = pcf.MQCMD_INQUIRE_Q(attrs, [f1, f2]) + result = pcf.MQCMD_INQUIRE_Q(attrs, [filter1, filter2]) logging.info('Result is %s', result)