Package: tuxedo
Python3 bindings for writing Oracle Tuxedo clients and servers. With Python2 support. With a book Modernizing Oracle Tuxedo Applications with Python:Why?I’m a fan of the waytuxmoduleenables you to interact with Oracle Tuxedo. Unfortunately, it’s out-dated and somehow limited. So I cloned tuxmodule and started to clean up compiler warnings and work on some features I had in mind:A multi-threaded serverSupport nested FML32 buffers and more typesSupport newest Oracle Tuxedo features liketpadvertisex()andtpappthrinit()Receive response even when the service returns TPFAIL (instead of exception)But I realized that’s too much of C for me, so I decided to write my own Python module for Oracle Tuxedo in C++ andpybind11focusing on the parts I find most important first.Supported platformsThe code is tested on the latest versions of Ubuntu and Oracle Linux using GCC 8 compiler. It should work on other Linux distributions and other UNIXes with no or minor adjustments.Windows runtime requirements (experimental)On Windows, the Visual C++ redistributable packages are a runtime requirement for this project. It can be foundhere.I have successfully built the module with Python 3.7.7.Python 3.8.3 fails to import the modulesimilar tobut I have no solution yet.ImportError:DLLloadfailedwhileimportingtuxedo:Thespecifiedmodulecouldnotbefound.Alternatives to Oracle TuxedoTuxedo-Python can also be used withOpen Source alternative to Oracle Tuxedo called Fuxedo. Just exportTUXDIRpointing to the folder whereFuxedois installed everything should work.All demo code provided with the module works with both Oracle Tuxedo and Fuxedo and you can avoid vendor lock-in by using Python and Tuxedo-Python module.Generaltuxedomodule supports onlySTRING,CARRAYandFML32buffer types at the moment.CARRAYis mapped to/from Pythonbytestype.STRINGis mapped to/from Pythonstrtype.FML32is mapped to/from Pythondicttype with field names (str) as keys and lists (list) of different types (int,str,floatordict) as values.dicttoFML32conversion also treats typesint,str,floatordictas lists with a single element:{'TA_CLASS':'Single value'}converted toFML32and then back todictbecomes{'TA_CLASS':['Single value']}All XATMI functions that take buffer and length arguments in C take only buffer argument in Python.Calling a servicetuxedo.tpcall()andtuxedo.tpgetrply()functions return a tuple with 3 elements or throw an exception when no data is received. This is the part I believetuxmodulegot wrong: a service may return a response
both when it succeeds (TPSUCCESS) and fails (TPFAIL) and often the failure response contains some important information.0 orTPESVCFAILtpurcode(the second argument totpreturn)data bufferrval,rcode,data=t.tpcall('.TMIB',{'TA_CLASS':'T_SVCGRP','TA_OPERATION':'GET'})ifrval==0:# Service returned TPSUCCESSelse:# rval == tuxedo.TPESVCFAIL# Service returned TPFAILWriting serversTuxedo servers are written as Python classes.tpsvrinitmethod of object will be called when Tuxedo callstpsvrinit(3c)function and it must return 0 on success or -1 on error. A common task fortpsvrinitis to advertise services the server provides by callingtuxedo.tpadvertise()with a service name. A method with the same name must exist.tpsvrdone,tpsvrthrinitandtpsvrthrdonewill be called when Tuxedo calls corresponding functions. All of these 4 methods are optional andtuxedomodule always callstpopen()andtpclose()functions before calling user-supplied methods.Each service method receives a single argument with incoming buffer and service must end with either call totuxedo.tpreturn()ortuxedo.tpforward(). Unlike in Ctuxedo.tpreturn()andtuxedo.tpforward()do not performlongjmpbut set up arguments for those calls once service method will return. You can have a code that will execute after Python’stpreturnand it plays nicely with context managers. Following two code fragments are equivalent but I believe the first one is less error-prone.defECHO(self,args):returnt.tpreturn(t.TPSUCCESS,0,args)defECHO(self,args):t.tpreturn(t.TPSUCCESS,0,args)After thattuxedo.run()must be called with an instance of the class and command-line arguments to start Tuxedo server’s main loop.#!/usr/bin/env python3importsysimporttuxedoastclassServer:deftpsvrinit(self,args):t.tpadvertise('ECHO')return0deftpsvrthrinit(self,args):return0deftpsvrthrdone(self):passdeftpsvrdone(self):passdefECHO(self,args):returnt.tpreturn(t.TPSUCCESS,0,args)if__name__=='__main__':t.run(Server(),sys.argv)UBBCONFIGTo use Python code as Tuxedo server the file itself must be executable (chmod +x *.py) and it must contain shebang line with Python:#!/usr/bin/env python3After that you can use the*.pyfile as server executable inUBBCONFIG:"api.py" SRVGRP=GROUP1 SRVID=20 RQADDR="api" MIN=1 SECONDARYRQ=Y REPLYQ=YWriting clientsNothing special is needed to implement Tuxedo clients, just import the module and start calling XATMI functions.#!/usr/bin/env python3importsysimporttuxedoastrval,rcode,data=t.tpcall('.TMIB',{'TA_CLASS':'T_SVCGRP','TA_OPERATION':'GET'})Using Oracle DatabaseYou can access Oracle database withcx_Oraclelibrary and local transactions by just following the documentation ofcx_Oracle.If you want a server written in Python to participate in the global transaction first specify a resource manager name to use (similar tobuidserver).tuxedomodule currently supports:NONE default “null” resource managerOracle_XA for Oracle Databaset.run(Server(),sys.argv,'Oracle_XA')After that you should create a database connection intpsvrinitby usingtuxedo.xaoSvcCtx()function:deftpsvrinit(self,args):self.db=cx_Oracle.connect(handle=t.xaoSvcCtx())That is the only difference from standardcx_Oracleuse case. Here is a complete example for a single-threaded server:#!/usr/bin/env python3importsysimporttuxedoastimportcx_OracleclassServer:deftpsvrinit(self,args):t.userlog('Server startup')self.db=cx_Oracle.connect(handle=t.xaoSvcCtx())t.tpadvertise('DB')return0defDB(self,args):dbc=self.db.cursor()dbc.execute('insert into pymsg(msg) values (:1)',['Hello from python'])returnt.tpreturn(t.TPSUCCESS,0,args)if__name__=='__main__':t.run(Server(),sys.argv,'Oracle_XA')For a multi-threaded server new connections for each thread must be created intpsvrthrinit()(instead oftpsvrinit()) and stored in thread-local storage ofthreading.local().Server must belong to a group withOracle_XAas resource manager, something like this inUBBCONFIG*GROUPS
GROUP2 LMID=tuxapp GRPNO=2 TMSNAME=ORACLETMS OPENINFO="Oracle_XA:Oracle_XA+Objects=true+Acc=P/scott/tiger+SqlNet=ORCL+SesTm=60+LogDir=/tmp+Threads=true"
*SERVERS
"db.py" SRVGRP=GROUP2 SRVID=2 CLOPT="-A"tpadmcalltpadmcallis made available for application administration even while application is down. It also has no service call overhead compared to calling.TMIBservice. The Python function looks and behaves similary totpcallexceptrcode(2nd element in result tuple) is always a constant 0.#!/usr/bin/env python3importtuxedoastrval,_,data=t.tpadmcall({'TA_CLASS':'T_DOMAIN','TA_OPERATION':'GET'})Global transactionsTransactions can be started and committed or aborted by usingtuxedo.tpbegin(),tuxedo.tpcommit(),tuxedo.tpabort(). These functions take the same arguments as their corresponding C functions.Buffer export and importFML32 identifiersFname32andFldid32are available to find map from field identifier to name or the other way.Functions to determine field number and type from identifier:assertt.Fldtype32(t.Fmkfldid32(t.FLD_STRING,10))==t.FLD_STRINGassertt.Fldno32(t.Fmkfldid32(t.FLD_STRING,10))==10ExceptionsOn errors eitherXatmiExceptionorFml32Exceptionare raised by the module. Exceptions contain additional attirbutecodethat contains the Tuxedo error code and you can compare it with defined errors likeTPENOENTorTPESYSTEM.try:t.tpcall("whatever",{})exceptt.XatmiExceptionase:ife.code==t.TPENOENT:print("Service does not exist")Demodemo/folder has some proof-of-concept code:client.pyOracle Tuxedo clientapi.pyHTTP+JSON server running inside Oracle Tuxedo serverecb.pyHTTP+XML client running inside Oracle Tuxedo servermem.pymulti-threaded in-memory cachedb.pyAccess Oracle Database using cx_Oracle module within global transactionbuf.pyDemo of tpimport/tpexport and FML32 identifiersTODOImplementing few more useful APIs