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_yieldMJSON

  • payload -- 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 van json.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.

    • json: het resultaat wordt geïnterpreteerd als een in JSON geserialiseerd Python object. Het Python object wordt teruggeven.

    • str: het resultaat wordt geïnterpreteerd als een Python string.

    • bin: het resultaat wordt gezien als Python bytes

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 action

  • ref -- 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_yieldMJSON

  • payload -- 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 van json.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_yieldMJSON

  • payload -- 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 van json.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.