47. Interactie tussen Python3 en Mumps
47.1. Inleiding
MUMPS en Python zijn de twee meest gebruikte programmeertalen in Brocade. Het is dan ook belangrijk goede interoperabiliteit te hebben tussen beiden. Deze laat immers toe om de superieure data-opslag van MUMPS te combineren met de moderne programmeertechnieken aanwezig in Python.
Deze documentatie beschrijft hoe Python3 toepassingen kunnen samenwerken met MUMPS.
47.2. MJSON
MJSON is een serialiseringsformaat voor een beperkte - maar belangrijke - groep van MUMPS structuren.
Deze structuur vormt de basis van de interoperabiliteit tussen de 2 programmeertalen.
47.3. Ontwikkeling
De code is terug te vinden in /core/python3/mumps.py
47.4. Foutafhandeling
Fouten in de MUMPS omgeving komen in Python tevoorschijn als een mumps.MumpsError exception.
Deze meldt 2 extra elementen:
een mogelijkheid om de MUMPS omgeving terug op te bouwen
een link naar de foutlogging in de Brocade interface. De ontwikkelaar moet wel ingelogd zijn.
>>> from anet.core import mumps
>>> action = 'd %ServCr1^stdtjapi(.RApayload)' # causes a divide by zero
>>> payload = {'count': '64'}
>>> result = mumps.serve(action, payload)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/library/process/py3/anet/core/mumps.py", line 336, in serve
raise result
anet.core.mumps.MumpsError:
### Mumps ###
action: d %ServCr1^stdtjapi(.RApayload)
data:
{'count': '64'}
### Info ###
{
"action": "d %ServCr1^stdtjapi(.RApayload)",
"payload": {
"count": "64"
}
}
M ERROR <,M9,Z150373210,> [= Divide by zero] on %ServCr1+3^stdtjapi
For more information, open link:
https://dev.anet.be/menu/error/64454:36
For reloading the error situation, execute in M:
d %LV^gserr("64454:36")
>>>
47.5. Python module anet.core.mumps
Er zijn 6 verschillende functies, elk met hun specialisatie:
47.5.1. mumps.write: schrijf opeenvolgende tekstlijnen in een M-global
- mumps.write(mumpsglo, iterable)
Schrijf een Python iterable naar een global. Deze iterable produceert tekst lijnen en deze worden weggeschreven te beginnen vanaf subscropt 1.
- Parameters
iterable -- Deze parameter zal meestal None zijn.
glo -- Een MUMPS global reference, bij voorkeur in path-notatie
Voorbeeld:
Note
Deze Python code maakt 2 global references aan:
^ZRP("a","b",1) (= "Hello")
^ZRP("a","b",2) (= "World")
from anet.core import mumps
mumps.write(["Hello", "World"], glo="/ZRP/a/b")
47.5.2. mumps.exe : Voer een M opdracht uit
- mumps.exe(action, *, ref=None, catch=False)
Voer een opdracht uit in de MUMPS omgeving en geeft - indien catch, True is - de output op stdout terug. Het MUMPS process stopt.
Deze functie is te gebruiken indien de Python toepassing slechts de output van een eenvoudig te formuleren opdracht in MUMPS nodig heeft.
Indien catch is True: het resultaat van deze Python functie is steeds van het type bytes, anders is het resultaat steeds None.
Hou ermee rekening dat de output omvangrijk kan zijn.
- Parameters
action (str) -- Een opdracht die in de MUMPS omgeving, via exec, wordt uitgevoerd.
ref -- Deze parameter zal meestal None zijn.
catch -- Deze parameter geeft aan of de stdout moet worden gecapteerd in de return waarde. Indien False (default), dan wordt de output onderdrukt.
Voorbeeld:
Note
Deze Python toepassing vraagt de tijd op zoals deze in MUMPS wordt geregistreerd.
from anet.core import mumps
# $H in M
print(mumps.exe("w $H", catch=True))
print(mumps.exe("w $H", catch=False))
47.5.3. mumps.query : Vraag en antwoord
- mumps.query(action, payload=None, *, ref=None, nature='json')
Bevraagt de MUMPS omgeving en geeft het antwoord terug. Het MUMPS process stopt.
Deze functie is te gebruiken indien de Python toepassing slechts een paar keer data nodig heeft uit de MUMPS omgeving. Bij elke activering wordt een nieuw MUMPS process opgestart en terug gestopt.
- Parameters
action (str) -- Een opdracht die in de MUMPS omgeving, via exec, wordt uitgevoerd. Deze opdracht maakt gebruik van de MUMPS array
RApayload
. Deze MUMPS toepassing schrijft lijnen op stdout. Dikwijls zijn deze te interpreteren als JSON en hebben deze een MJSON object als basis: dit schrijven op stdout kan dan worden gerealiseerd door de macro: m4_yieldMJSONpayload -- Wordt als een JSON structuur in de MUMPS omgeving als de
RApayload
variabele ter beschikking gesteld.payload
mag een list, dict, string, int of elke Python object zijn waarvoor een passende subclass vanjson.JSONEncoder
bestaat. Hou er echter wel rekening mee dat de datastructuur wordt omgezet naar MJSON.ref -- Deze parameter zal meestal None zijn.
nature (str) --
Deze parameter kan 3 waarden aannemen en bepaalt de natuur van het resultaat.
Voorbeeld:
Note
Deze Python toepassing vraagt voor een specifieke gebruiker (e:UA:9701) een aantal gegevens op uit de Brocade databank.
Het voorbeeld bestaat uit een MUMPS routine en dan de uiteindelijke Python code die gebruik maakt van deze routine.
; beuainfo.m
def %GetInfo(PApayload):
n x,y,z,ZAid,eloi,ZAjson
s eloi=PApayload("eloi")
m4_getEuserId(ZAid, eloi)
i $D(PApayload("firstname")) s ZAjson("firstname")=ZAid("vn")
i $D(PApayload("lastname")) s ZAjson("firstname")=ZAid("fn")
i $D(PApayload("homelib")) s ZAjson("firstname")=ZAid("lib")
m4_yieldMJSON(ZAjson)
q
from anet.core import mumps
# aanleveren van lezersinformatie
action = 'd %GetInfo^beuainf(.RApayload)'
# specificeer lezer en de velden die je terug wil hebben
payload = {
'eloi': 'e:UA:9701',
'firstname': '',
'lastname': '',
}
# bevraag M
result = mumps.query(action, payload)
# test
assert result['firstname'] == 'Richard'
assert result['lastname'] == 'Philips'
47.5.4. mumps.feed : Python is bron van data
- mumps.feed(action, iterable, *, ref=None)
Stuurt data naar MUMPS. Verwacht geen antwoord.
Deze functie is te gebruiken indien de Python toepassing massale hoeveelheden data naar MUMPS wil sturen. Er is geen return waarde.
- Parameters
action (str) -- Een opdracht die in de MUMPS omgeving, via exec, wordt uitgevoerd. Deze opdracht maakt gebruik van de MUMPS array RApayload
iterable (Iterable) -- Een Python iterable, die bij elke iteratie een MJSON variabele in
RApayload
in de MUMPS omgeving plaatst die dan wordt verwerkt door actionref -- Deze parameter zal meestal None zijn.
Voorbeeld:
Note
Deze Python toepassing verwerkt een XML-bestand in Brocade. Dit XML-bestand bevat clois en titels en overschrijft de eerste titel bij elke cloi.
Het voorbeeld bestaat uit 3 componenten:
een XML bestand dat potentieel groot kan zijn
een MUMPS routine die klaar staat om de gegevens uit dit XML bestand te verwerken
Python code die MUMPS voedt met de data uit het XML bestand
<!-- titles.xml -->
<database>
<title cloi="c:lvd:101">Deutsches Dichterlexicon</title>
<title cloi="c:lvd:102">Zelfportret, gevleid natuurlijk</title>
<title cloi="c:lvd:103">Hugo Claus : experiment en traditie</title>
<!-- ... -->
</database>
; bcastitle.m
def %SetTi(PApayload):
n x,y,z,ZAti,cloi
s cloi=PApayload("cloi")
m4_getCatIsbdTitles(ZAti, cloi)
s ZAti(1,"ti")=PApayload("title")
m4_setCatIsbdTitles(ZAti, cloi)
q
from lxml import etree
from anet.core import mumps
# opstellen van generator
def genTitles(filename):
for _, element in etree.iterparse(filename, tag='title'):
yield {'cloi': element.get('cloi'), 'title': element.text}
element.clear()
return
# specificatie van de opdracht
action = 'd %SetTi^bcastitl(.RApayload)'
# file in M
mumps.feed(action, genTitles("titles.xml"))
47.5.5. mumps.iter : Mumps is bron van data
- mumps.iter(action, payload=None, *, ref=None)
Maakt een generator object die bij elke iteratie een list of een dict geeft die de tegenhanger is van een MJSON object in MUMPS.
Deze functie is te gebruiken indien de MUMPS toepassing de bron is van een massale hoeveelheid verschillende records die stuk voor stuk moet worden verwerkt in Python.
- Parameters
action (str) -- Een opdracht die in de MUMPS omgeving, via exec, wordt uitgevoerd. Deze opdracht maakt gebruik van de MUMPS array
RApayload
. Deze MUMPS toepassing schrijft lijnen op stdout die te interpreteren zijn als JSON. Gebruikelijk is dat deze lijnen, een MJSON object als basis hebben. Dit schrijven op stdout worden gerealiseer door de macro: m4_yieldMJSONpayload -- Wordt als een JSON structuur in de MUMPS omgeving als de
RApayload
variabele ter beschikking gesteld.payload
mag een list, dict, string, int of elke Python object zijn waarvoor een passende subclass vanjson.JSONEncoder
bestaat. Hou er echter wel rekening mee dat de datastructuur wordt omgezet naar MJSON.ref -- Deze parameter zal meestal None zijn.
Voorbeeld:
Note
Deze Python toepassing doorloopt een catalografisch regelwerk en produceert een XML-bestand.
Het voorbeeld toont 2 componenten:
een MUMPS routine die gegevens ophaalt uit de databank
een Python routine die deze gegevens verwerkt tot een XML bestand
; bcaltitl.m
def %GetTi(PApayload):
n x,y,z,ZAti,cloi,RDcloi
s catsys=PApayload("catsys")
m4_loopCatsys(catsys,"One")
q
def One:
;pragma fos RDcloi
n x,y,z,ZAtitles,ZAjson
m4_getCatIsbdTitles(ZAtitles, RDcloi)
q:'$D(ZAtitles(1,"ti"))
s ZAjson("cloi")=RDcloi
s ZAjson("title")=ZAtitles(1,"ti")
m4_yieldMJSON(ZAjson)
q
from lxml.etree import Element, tostring
from anet.core import mumps
# specificatie van de opdracht
action = 'd %GetTi^bcaltitl(.RApayload)'
# extra gegevens
payload = {'catsys': 'lvd'}
# verwerk de titels
with open('titles.xml', 'w', encoding='utf-8') as xml:
xml.write('<database>')
for ti in mumps.iter(action, payload):
cloi = ti['cloi']
title = ti['title']
tixml = Element('title')
tixml.set('cloi', cloi)
tixml.text = title
xml.write(tostring(tixml))
xml.write('</database>')
47.5.6. mumps.serve : Mumps/Python sparring partners
- mumps.serve(action='', payload=None, *, ref=None)
Via deze functie kan men gestructureerde vragen stellen aan MUMPS en gestructureerde antwoorden terugkrijgen. In tegenstelling tot mumps.query wordt het MUMPS process slechts 1x opgestart, ook als Python verschillende vragen stelt aan MUMPS. Het MUMPS process wordt verwijderd van zodra de Python job stopt. Deze functie heeft ook geen verbose argument.
- Parameters
action (str) -- Een opdracht die in de MUMPS omgeving, via exec, wordt uitgevoerd. Deze opdracht maakt gebruik van de MUMPS array
RApayload
. Deze MUMPS toepassing schrijft een MJSON object op stdout door middel van de macro: m4_yieldMJSONpayload -- Wordt als een JSON structuur in de MUMPS omgeving als de
RApayload
variabele ter beschikking gesteld.payload
mag een list, dict, string, int of elke Python object zijn waarvoor een passende subclass vanjson.JSONEncoder
bestaat. Hou er echter wel rekening mee dat de datastructuur wordt omgezet naar MJSON.ref -- Deze parameter zal meestal None zijn.
Voorbeeld:
Note
Deze Python toepassing doorloopt een bestand met clois, vraagt in MUMPS de titel op en produceert een XML-bestand.
Het voorbeeld toont 3 componenten:
een bestand met clois.
een MUMPS routine die gegevens ophaalt uit de databank
een Python routine die deze gegevens verwerkt tot een XML bestand
# clois.txt
c:lvd:2357
c:lvd:368126
c:lvd:568726
# ...
; bcastitl.m
def %GetTi(PApayload):
n x,y,z,cloi,ZAtitles
s cloi=PApayload("cloi")
m4_getCatIsbdTitles(ZAtitles, cloi)
s ZAjson("cloi")=RDcloi
s ZAjson("title")=ZAtitles(1,"ti")
m4_yieldMJSON(ZAjson)
q
from lxml.etree import Element, tostring
from anet.core import mumps
# specificatie van de opdracht
action = 'd %GetTi^bcastitl(.RApayload)'
# verwerk de titels
with open('titles.xml', 'w', encoding='utf-8') as xml:
xml.write('<database>')
with open('clois.txt', 'r') as text:
for cloi in text:
if not cloi.startswith('c:'):
continue
payload = {'cloi': cloi}
ti = mumps.serve(action, payload)
cloi = ti['cloi']
title = ti['title']
tixml = Element('title')
tixml.set('cloi', cloi)
tixml.text = title
xml.write(tostring(tixml))
xml.write('</database>')
47.6. De ref parameter in de anet.core.mumps functions
Deze ref parameter dient om de MUMPS omgeving aan te sturen. In praktisch alle toepassingen is het voldoende om te werken met de default waarde None.
Er zijn essentieel 3 taken die deze parameter kan uitvoeren:
specificatie van de UCI
specificatie van de server die de MUMPS installatie bevat
opstarten van meerdere MUMPS processen
Python moet weten wat de instructie is om een MUMPS process op te starten en wat de UCI is voor dit MUMPS process.
47.6.1. Specificatie van de UCI
De UCI is steeds de standaard UCI zoals die door registry("m-db") wordt bijgehouden.
Enkel indien we willen werken met een andere waarde, moet deze expliciet worden opgegeven.
Dit gebeurt door ref de gewenste waarde voor de UCI te geven en steeds te laten volgen door een ;
Voorbeelden:
/backup;
/backup;legato
Staat er geen ; in de parameter ref, dan is de UCI steeds de standaard UCI
47.6.2. Specificatie van het MUMPS process
Het MUMPS process wordt standaard opgestart door registry("m-exe"). Het is echter ook mogelijk een MUMPS process op een andere installatie op te starten.
Daartoe wordt uit ref het stuk na de ; genomen (of gans de waarde van ref indien deze geen ; bevat). Laten we deze string server noemen. De software gaat dan de waarde van registry("m-exe-host-server) opvragen. Deze bevat alle parameters in JSON notatie om de connectie op te starten.
Voorbeeld:
Stel dat we, vertrekkend van een Linux machine, een verbinding willen met dev.anet.be (legato), via SSH, dan gaat registry("m-exe-host-legato) de waarde:
["ssh", "-q", "-o", "BatchMode=yes", "legato", "anetmumps '{routine}'"]
bevatten.
{routine} is een placeholder die passend wordt ingevuld.
Ook indien registry("m-exe-host-server) niet wordt teruggevonden, is deze parameter van nut: ref speelt dan de rol van identificatie van het MUMPS process en kan dan worden gebruikt om meerdere mumps.serve processen te hanteren.