The vavista.rpc module provides a very simple mechanism for calling VistA RPCs. Simply connect, and call the PRC you are interested in.
The vavista.rpc module does not depend on the M interfaces modules, i.e. it is not bound to a GT.M interpreter. It communicates client server over a TCP connection.
The brokerRPC.py code belongs to Caregraph.org's FMQL product. I put it here for ease of reuse.
Application Context
In order to access RPCs in Vista, you need to have a valid Application Context Id.
The application context provides the security management for RPCs. The context must exist in the table "OPTION" (19), with the Type = "Broker (client/server)" (B).
I created an option called "RPC DEMO". I did not assign it any RPCs, so only public RPCs should be available.
Select OPTION NAME: RPC DEMO Not a known package or a local namespace. Are you adding 'RPC DEMO' as a new OPTION (the 10852ND)? No// y (Yes) OPTION MENU TEXT: RPC DEMO MENU TEXT: RPC DEMO// TYPE: B Broker (Client/Server)
RPC Port
VistA provides a RPC (remote procedure call) broker. This sits on an agreed port.
Clients connect to the broker and create a session. In the GT.M context, a server is forked to process the request. The server waits, receives a request, processes it and returns a response. The mechanism is single-threaded and synchronous.
9210 - where is this configured ?? TODO RPC BROKER SITE PARAMETERS seems do describe other sites configurations, not this sites.
How individual Requests are Configured in Vista
The available RPCs are listed in Fileman file "REMOTE PROCEDURE" (8994).
TODO: rpc availability what is public??
The RPC may be restricted to one application. TODO: how???
How to set up package: TODO
The Option allows you to assign restricted RPCs to the application context.
All results from RPCs are returned as a single string. RPCs have the following return types:
1 SINGLE VALUE (string) 2 ARRAY (string split by \r\n) 3 WORD PROCESSING (string split by \r\n) 4 GLOBAL ARRAY (string split by \r\n) 5 GLOBAL INSTANCE (string)
Parameters can be:
1 LITERAL - PLiteral 2 LIST - PList 2 Word Processing - PLiteral 4 REFERENCE - PReference
Create a connection:
from vavista import rpc, PLiteral, PList, PReference c = rpc.connect(hostname, port, access-code, verify-code, context, debug=False) - TCP communication information (hostname, port) - VistA's security (access, verify) - application context - See note above - the debug flag is useful for interactive use.
This function creates a connection to the VISTA RPC server.
Methods
invoke calls the rpc identified by rpcid, passing through all parameters. l_invoke converts the response to a list (spliting in 'rn').
c.invoke(rpcid, [param1, [param2, ... [param n]], return_formatter=None) c.l_invoke(rpcid, [param1, [param2, ... [param n]])
return_formatter is a function, which take one parameter, the return value. If a return_formatter is passed in, it processes the return value, before the invoke method returns. An example is the list formatter, which is passed in by the l_invoke method.
The rpc module provides a number of helper classes to convert from your internal type to the encoded string for the wire.:
PLiteral - value is a literal PList - value is a list PReference - value is a reference PEncoded - the application encode the value, pass it straight through
If a parameter is not one of the above, if it is a dict, it is passed as a PList, otherwise it is passed as a PLiteral.
From prompt:
$ python >>> from vavista.rpc import connect, PLiteral, PList, PReference, PEncoded >>> c = connect('localhost', 9210, "VISTAIS#1", "#1ISVISTA", "RPC DEMO", debug=True) >>> print c.invoke("XWB EGCHO STRING", PLiteral("THIS IS A STRING")) THIS IS A STRING # types other than dicts default to type PLiteral >>> print c.invoke("XWB EGCHO STRING", "THIS IS A STRING") THIS IS A STRING # If the return type is a List, under normal conventions, the return # values are separated via '\r\n' characters. >>> print c.invoke("XWB EGCHO LIST")[:50] List Item #1 List Item #2 List Item #3 List Ite # However, you can make the call extract the return type >>> print c.l_invoke("XWB EGCHO LIST")[:4] ['List Item #1', 'List Item #2', 'List Item #3', 'List Item #4'] # This is how to pass an Array - M does not have a real concept of arrays. # It just has a dict style data type. This can be generated by either an list of # tuples or a dict. >>> print c.invoke("XWB EGCHO SORT LIST", "LO", PList([('1', ''), ('10', ''), ('190', ''), ('89', '')])) ['1', '10', '89', '190'] # A dict is assumed to be a list >>> print c.invoke("XWB EGCHO SORT LIST", "HI", {'1': '', '10': '', '190': '', '89': ''}) ['190', '89', '10', '1'] # You can encode the parameter yourself if you know what you are doing >>> print c.invoke("XWB EGCHO STRING", PEncoded("0014I ENCODED THISf")) I ENCODED THIS # Reference parameters are passed using the PReference type. >>> print c.invoke("XWB GET VARIABLE VALUE", PReference("DUZ")) 10000000020 >>> print c.invoke("XWB GET VARIABLE VALUE", PReference("DUZ(0)")) @
Simple script:
import getopt, sys from vavista.rpc import connect, PLiteral, PList, PReference context = "RPC DEMO" # see not above about creating this option. opts, args = getopt.getopt(sys.argv[1:], "") if len(args) < 4: print args sys.stderr.write("Enter <host> <port> <access> <verify>\n") sys.exit(1) host, port, access, verify = args[0], int(args[1]), args[2], args[3] c = connect(host, port, access, verify, context) # Prints out "THIS IS A STRING" print c.invoke("XWB EGCHO STRING", "THIS IS A STRING") # This "list" RPC returns a list of items delimited by the DOS line ending print c.invoke("XWB EGCHO LIST")[:100] print c.l_invoke("XWB EGCHO LIST")[:10] # This "list" RPC returns a list of items delimited by the DOS line ending l = c.l_invoke("XWB EGCHO BIG LIST") print l[:5], " ... ", l[-5:] # This is how 'arrays' are passed print c.l_invoke("XWB EGCHO SORT LIST", "HI", {'1': '', '10': '', '190': 'x', '89': ''}) print c.l_invoke("XWB EGCHO SORT LIST", "LO", PList([('1', ''), ('10', ''), ('190', 'x'), ('89', '')]))