User Tools

Site Tools


cisis_y_python

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cisis_y_python [14/05/2009 00:00]
cisis_y_python [14/05/2009 00:00] (current)
Line 1: Line 1:
 +====== Utilitarios CISIS y Python ======
 +
 +¿Cómo podemos acceder desde Python a las bases ISIS? Lo más simple es hacerlo a través de las herramientas de Bireme: utilitarios cisis y wxis.
 +
 +Aquí, algunos experimentos para lograr //​wrappers//​ en Python para estas herramientas.
 +
 +
 +
 +==== mx dict ====
 +
 +Para obtener algo aproximadamente igual a esto:
 +
 +  mx dict=biblio count=10 now "​pft=v1^t,​c8,​v1^*/"​
 +  3      -ANOTACION-ACCESO
 +  4      -ANOTACION-DESCR
 +  656    -ANOTACION-OTRA
 +  3      -ANOTACION-TEMA
 +  18     ​-BIOGR=YES
 +  1      -CREADO_POR=
 +  2384   ​-CREADO_POR=FG
 +  74     ​-CREADO_POR=LG
 +  33     ​-F=####​
 +  1      -F=1854
 +
 +podemos hacer algo así:
 +
 +<code python>
 +>>>​ import subprocess, re, os
 +>>>​ command = 'mx dict=biblio count=10 "​pft=v1/"​ now'
 +>>>​ p = subprocess.Popen(command,​ shell=True, stdout=subprocess.PIPE) ​
 +>>>​ out = p.communicate()[0]
 +>>>​ keys = [tuple(re.split('​\^[lstk]',​ line)) for line in out.split(os.linesep)[:​-1]]
 +>>>​ keys
 +[('​-ANOTACION-ACCESO',​ '​2',​ '​17',​ '​3',​ '​1'​),​ ('​-ANOTACION-DESCR',​ '​2',​ '​16',​ '​4',​ '​2'​),​ ('​-ANOTACION-OTRA',​ '​2',​ '​15',​ '​656',​ '​3'​),​ ('​-ANOTACION-TEMA',​ '​2',​ '​15',​ '​3',​ '​4'​),​ ('​-BIOGR=YES',​ '​1',​ '​10',​ '​18',​ '​5'​),​ ('​-CREADO_POR=',​ '​2',​ '​12',​ '​1',​ '​6'​),​ ('​-CREADO_POR=FG',​ '​2',​ '​14',​ '​2384',​ '​7'​),​ ('​-CREADO_POR=LG',​ '​2',​ '​14',​ '​74',​ '​8'​),​ ('​-F=####',​ '​1',​ '​7',​ '​33',​ '​9'​),​ ('​-F=1854',​ '​1',​ '​7',​ '​1',​ '​10'​)]
 +>>>​ for k in keys: print "​%8s ​ %s" % (k[3],k[0])
 +... 
 +       ​3 ​ -ANOTACION-ACCESO
 +       ​4 ​ -ANOTACION-DESCR
 +     ​656 ​ -ANOTACION-OTRA
 +       ​3 ​ -ANOTACION-TEMA
 +      18  -BIOGR=YES
 +       ​1 ​ -CREADO_POR=
 +    2384  -CREADO_POR=FG
 +      74  -CREADO_POR=LG
 +      33  -F=####
 +       ​1 ​ -F=1854
 +>>>​
 +</​code>​
 +
 +
 +
 +==== wxis modules ====
 +
 +Idea: usar Python para llamar a wxis y parsear el XML que devuelve:
 +
 +  fernando@ubuntu-fgomez:​~/​www/​bases/​cpd/​catalis/​bibima$ /​home/​fernando/​www/​cgi-bin/​wxis54 IsisScript=/​home/​fernando/​www/​html/​wxis_modules/​wxis-modules/​index.xis database=biblio count=10
 +
 +  Content-type:​text/​xml
 +  ​
 +  <?xml version="​1.0"​ encoding="​ISO-8859-1"?>​
 +  <​wxis-modules IsisScript="/​home/​fernando/​www/​html/​wxis_modules/​wxis-modules/​index.xis"​ version="​0.1">​
 +  <term mfn="​1">​
 +     <​Isis_Key>​
 +        <​occ>​-ANOTACION-ACCESO</​occ>​
 +     </​Isis_Key>​
 +     <​Isis_Postings>​
 +        <​occ>​3</​occ>​
 +     </​Isis_Postings>​
 +     <​Isis_Posting>​
 +        <​occ>​0</​occ>​
 +     </​Isis_Posting>​
 +  </​term>​
 +  ...
 +
 +También podemos cambiar los .xis para que generen otra cosa en vez de XML: JSON (http://​pypi.python.org/​pypi/​simplejson),​ Python, ...
 +
 +
 +Estudiar el contenido de la carpeta ''​wxis-php''​ en wxis-modules,​ y adaptarlo a Python?
 +
 +
 +
 +
 +
 +
 +==== Entendiendo wxis-modules ====
 +
 +TO-DO: ver si alguna diferencia entre las versiones Windows y Linux.
 +
 +Desde el cliente se llama a call.php con method='​post',​ y parámetros:​
 +
 +  * ''​wxis_parameters''​ (contenido de un textarea, formato xml)
 +  * ''​task''​ (una de 7 opciones: list, search, index, edit, write, delete, control)
 +        ​
 +En el caso particular de task="​write",​ se añade un parámetro:
 +
 +  * ''​wxis_write_content''​
 +
 +
 +
 +=== Archivo call.php ===
 +
 +
 +  * include('​wxis.php'​)
 +
 +wxis.php define 2 funciones "​privadas":​
 +
 +  * wxis_document_post => hace un petición HTTP con método POST
 +  * wxis_url => construye una URL para invocar a wxis
 +            ​
 +y estas 7 funciones "​públicas",​ una por cada posible valor de task:
 +
 +  * wxis_list
 +  * wxis_search
 +  * wxis_index
 +  * wxis_edit
 +  * wxis_write
 +  * wxis_delete
 +  * wxis_control
 +
 +Dichas funciones son muy simples, sólo contienen una línea:
 +        ​
 +  return wxis_document_post(wxis_url("​TASK.xis",​ $param));
 +
 +
 +  * para cada posible valor TASK del parámetro task, se ejecuta esta única línea:
 +    ​
 +  print(wxis_TASK($_REQUEST["​wxis_parameters"​]));​
 +        ​
 +donde wxis_TASK es una de las 7 funciones de arriba. La excepción es ''​task=write'',​ que contiene un parámetro extra:
 +      ​
 +  print(wxis_write($_REQUEST["​wxis_parameters"​],​ $_REQUEST["​wxis_write_content"​]));​
 +
 +
 +Alternativamente,​ se define una clase DB_ISIS (archivo ''​db_isis.php''​). No aparece usada en los ejemplos.
 +
 +Esta clase tiene las siguientes funciones (métodos):
 +    ​
 +    * ''​getParameterList($list)'':​ devuelve un string XML, "<​parameters>​\n<​key>​value</​key>​\n...\n</​parameters>​\n"​
 +    ​
 +Más las 7 funciones, una por tarea:
 +
 +    * doList
 +    * search
 +    * index
 +    * edit
 +    * write
 +    * delete
 +    * control
 +
 +Cada una de estas funciones hace lo siguiente:
 +
 +  return wxis_TASK($this->​getParameterList($param));​
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +==== wxis-modules => JSON => Python ====
 +
 +Lo que sigue son notas acerca de la adaptación de los wxis-modules originales para que devuelvan JSON, y el desarrollo de un módulo de Python para comunicarse con esos scripts ''​.xis''​.
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +
 +=== TO-DO ===
 +
 +
 +== Licencia ==
 +
 +Definir la licencia a usar.
 +
 +
 +== JSON ==
 +
 +  * Hacer que WXIS devuelva [[http://​www.json.org/​|JSON]],​ para lo cual necesitamos:​
 +    * [HECHO] Encerrar nombres y valores en comillas dobles.
 +    * [HECHO CON REPLACE] Escapar comillas dobles y barra invertida en los valores (mediante replace() o gizmo?). ATENCION: Esto no es solamente para los valores tomados de la base, sino también para los metadatos (e.g. los nombres de bases y archivos pueden contener barras invertidas)
 +    * [PENDIENTE] Omitir la coma final (usar iocc dentro de un grupo repetible [lista de campos], e Isis_Total(?​) dentro de un <​loop>​ [lista de registros y de términos])
 +
 +  * [PARECE ANDAR BIEN] Verificar si Python consume correctamente el JSON devuelto por WXIS (sin necesidad de un json parser). Probar los caracteres escapados.
 +
 +  * Renombrar **py-wxis-modules** como **json-wxis-modules** o **wxis-json-modules** o **wxis-modules-json**
 +
 +  * Si queremos procesar el JSON devuelto por wxis desde otra aplicación (p.ej. en un browser mediante JavaScript),​ pero no queremos permitir acceso directo a wxis desde un browser, tendríamos que poder enviar el JSON crudo vía Python.
 +
 +== Grabación ==
 +
 +  * [FUNCIONA BIEN!] ¿Cómo recibir los datos para grabar? <​del>​¿JSON?</​del>​ Estudiar lo que sucede en el write.xis original (insertar un display ALL antes del <​write>​). Una posibilidad para incorporar los datos es mediante un proc con H
 +
 +    $ mx tmp "​proc='​H100 10 0123456789'"​
 +    mfn=     1
 +    100  «0123456789»
 +
 +pero ojo, pues:
 +
 +    $ mx tmp "​proc='​H100 11 0123456789'"​
 +    fatal: '
 +
 +    $ mx tmp "​proc='​H100 9 0123456789'"​
 +    fatal: 9
 +
 +Modificamos la definición del método ''​write()'':​
 +
 +<code python>
 +  def write(self, content=()):​
 +    content = ','​.join([
 +      "H%s %s %s" % (f[0], str(len(f[1])),​ f[1])
 +      for f in content
 +    ])
 +</​code>​
 +
 +y así podemos llamarlo:
 +
 +<code python>
 +    content = (
 +      ('​100',​ '​abcdefghij'​),​
 +      ('​200',​ '​ABC'​)
 +    )
 +    db.write(mfn='​291',​ content=content)
 +</​code>​
 +
 +En ''​write.xis''​ podemos tener entonces:
 +
 +<code xml>
 +  <field action="​cgi"​ tag="​32199">​content</​field>​
 +  <​proc><​pft>​v32199</​pft></​proc>​
 +</​code>​
 +
 +== Errores, excepciones ==
 +
 +  * Definir **excepciones generales**:​ **ConnectionError** (cuando no se puede conectar con wxis), **WxisResponseError** (cuando la respuesta de wxis incluye un mensaje de error fatal o de ejecución, y por lo tanto no permite crear un diccionario,​ pero también cuando el error está generado desde un ''​.xis'',​ e.g. falta parámetro obligatorio). Definir además **excepciones específicas** para cada método donde se pueda tener **Isis_Status** diferente de cero: edit(), delete(), write(). Quizás la única excepción en estos casos es **LockedRecordError**.
 +
 +<code python>
 +  class Error(Exception):​
 +    pass
 +
 +  class LockedRecordError(Error):​
 +    def __init__(self,​ resp, message):
 +      self.resp = resp
 +      self.message = message
 +
 +  def getStatus(resp):​
 +    return resp['​metadata'​]['​Isis_Status'​]
 +
 +  def edit(self, **params):
 +    resp = self.__doTask('​edit',​ params)
 +    if getStatus(resp) <> '​0':​
 +      raise LockedRecordError(resp,​ '​Registro bloqueado'​)
 +    else:
 +      return resp
 +
 +  try:
 +    db.edit(mfn=256,​ lockid='​xx'​)
 +  except LockedRecordError:​
 +    print 
 +</​code>​
 +
 +Además, incluir siempre un ''​except:''​ como última instancia para capturar errores no previstos.
 +
 +Ver lista de códigos de error de wxis: 
 +  * http://​ibama2.ibama.gov.br/​cnia2/​cisis/​mensagens%20de%20erro%20do%20wxis-mx.pdf
 +  * http://​www.elysio.com.br/​documentacao/​manual_phl81.pdf
 +  * http://​www.google.com.ar/​search?​q=%22de+erro+do+CISIS%22&​filter=0
 +
 +Un tipo especial de error es cuando la expresión de búsqueda no está bien armada. Para eso existe un código llamado Isis_ErrorInfo:​
 +
 +<​code>​
 +<!--
 +    Intento de descubrir qué es Isis_ErrorInfo
 +    2008-03-27
 +    Lo que detecto es que si uso una expresión de búsqueda mal formada,
 +    el campo 1009 está vacío, mientras que si la expresión es correcta
 +    el campo 1009 tiene valor '​0'​. Por lo tanto, luego de una búsqueda
 +    sin resultados, podemos verificar el valor de Isis_ErrorInfo,​ y en
 +    caso de no ser '​0',​ incluir el error en la respuesta de wxis, para
 +    luego generar una excepción en Python.
 +    ​
 +    Ejemplos:
 +        expresion ​         Isis_ErrorInfo
 +        --------------------------------------
 +        test               0
 +        test/​( ​            <​vacio>​
 +        test/​(4 ​           4
 +        test/​(0 ​           0  <= Entonces no nos sirve tanto!
 +        test and           0
 +        test *             <​vacio>​
 +        test +             <​vacio>​
 +        +test              +
 +-->
 +
 +<​IsisScript>​
 +
 +<field action="​cgi"​ tag="​1">​q</​field>​
 +
 +<do task="​search">​
 +    <parm name="​db">​G:​\httpd_\bases\bibima\bibima</​parm>​
 +    <parm name="​expression"><​pft>​v1</​pft></​parm>​
 +    <parm name="​count">​2</​parm>​
 +    <​define>​1001 Isis_Current</​define>​
 +    <​define>​1002 Isis_Total</​define>​
 +    <​define>​1009 Isis_ErrorInfo</​define>​
 +    <​loop>​
 +        <​display><​pft>​mfn/</​pft></​display>​
 +    </​loop>​
 +    <​display><​pft>'​Isis_ErrorInfo:​ "',​ v1009,'"'</​pft></​display>​
 +</do>
 +
 +</​IsisScript>​
 +</​code>​
 +
 +== Testeo ==
 +
 +Podemos incluir un conjunto de tests, que sirvan a la vez como ejemplos de uso y como verificación del buen funcionamiento del módulo. ​
 +
 +== Documentación ==
 +
 +  * Documentar cada ''​.xis''​ y cada método del módulo: qué parámetros reciben, cuáles parámetros son obligatorios,​ cuáles son los defaults, y qué respuestas producen.
 +
 +  * Documentar los **requisitos** para utilizar estos wxis-modules:​ wxis (versión), servidor web, permisos sobre los archivos y directorios. Versión de Python requerida.
 +
 +  * Documentar cómo se instala/usa todo esto.
 +
 +  * Documentar la **configuración**.
 +
 +== Archivos auxiliares ==
 +
 +  * Incluir una **base demo**, útil para testeo. Podemos crear la base a partir de un archivo de texto.
 +
 +  * Archivos: actab, uctab, stw, fst
 +
 +== Performance ==
 +
 +  * Revisar el código interior de los loops, en particular ''​display-record.xis''​. Escape de caracteres para JSON: comparar el uso de **replace()** (campo por campo, o a todos los campos de una vez), vs. el uso de un **gizmo**.
 +
 +== Otros ==
 +
 +  # An instance of class Isis may have some associated attributes, like:
 +  # fst, actab, uctab, stw, which may then be passed to wxis in every invocation.
 +  # This set of attributes may be available as a dictionary:
 +  # >>>​ db.fst = '/​home/​user/​isis/​some.fst'​
 +  # >>>​ db.getParams()
 +  # {'​name':​ '/​home/​user/​isis/​testdb',​ '​fst':​ '/​home/​user/​isis/​some.fst',​ '​actab':​ '',​ '​uctab':​ '',​ '​stw':​ ''​}
 +
 +
 +  # The creation of a new masterfile is not associated with an existing db, so
 +  # it should be a class/​static method, not an instance method.
 +
 +  * [HECHO] Quitar los ''​@staticmethod'',​ y llevar esos métodos fuera de la clase, a nivel del módulo.
 +
 +  * Revisar la documentación de WXIS, en busca de algo importante que se haya escapado.
 +
 +  * [HECHO] **extract** (usado en el opac para limpiar el query).
 +
 +    # Method to extract keys from a string, using wxis's builtin mechanism, and
 +    # specifying custom fst, stw, actab and uctab parameters.
 +    def extract(self,​ **params):
 +        return self.__doTask('​extract',​ params)
 +
 +    extract.xis
 +        <do>
 +            <parm name="​count">​1</​parm>​
 +            ​
 +            <field action="​cgi"​ tag="​3002">​technique</​field>​
 +            <field action="​replace"​ tag="​3002">"​4"​n3002</​field>​ <!-- default: 4 -->
 +            <parm name="​fst"><​pft>'​1 ', v3002,'​ v3001'</​pft></​parm>​ <!-- aplica la técnica al contenido de v3001 y almacena el resultado en v1 -->
 +            ​
 +            <field action="​cgi"​ tag="">​stw</​field>​
 +            <parm name="​stw"></​parm>​
 +            ​
 +            <field action="​cgi"​ tag="">​actab</​field>​
 +            <parm name="​actab"></​parm>​
 +            ​
 +            <field action="​cgi"​ tag="">​uctab</​field>​
 +            <parm name="​uctab"></​parm>​
 +            ​
 +            <field action="​cgi"​ tag="​3001">​data</​field>​
 +            ​
 +            <​loop>​
 +                <field action="​import"​ tag="​list">​3001</​field>​
 +                <​extract>​this</​extract>​
 +                <​!--field action="​export"​ tag="​list">​1</​field-->​
 +                <​display><​pft>​
 +                    '​{"​keys":​['​
 +                        (
 +                            '"',​ replace(replace(v1,​ '​\',​ '​\\'​),​ '"',​ '​\"'​),​ '"'​
 +                            if iocc < nocc(v1) then ','​ fi
 +                        )
 +                    '​]}'​
 +                </​pft></​display>​
 +            </​loop>​
 +        </do>
 +
 +
 +  * [HECHO, pasando un parámetro extra a search.xis] Sería bueno tener una función **count** que devuelva el número de resultados de una búsqueda, sin devolver los registros.
 +
 +  * ¿Informar a Bireme sobre este uso/​modificación de wxis-modules?​
 +
 +  * ¿Tiene relevancia la **[[http://​www.python.org/​dev/​peps/​pep-0249/​|Python Database API Specification v2.0]]**, que fue diseñada pensando en bases relacionales?​
 +
 +  * Parámetros **actab** y **uctab**: quitar los valores //​hardcoded//​ en ''​common.xis''​. Agregar parámetro **stw**. Usar archivo de configuración. La **fst** también debe poder ser compartida entre varias bases (o al menos, su nombre no tiene por qué coincidir con el de la base), agregarla al archivo de config, y modificar los scripts que usan fst. En realidad, **los .xis no deben incluir información acerca de estos parámetros** (ni hardcoded, ni en archivos de config). Los .xis sólo deben utilizar los parámetros tal como les son pasados por las funciones/​métodos que los invocan. En todo caso, es cada aplicación específica que llama a los .xis quien debe decidir qué actab, uctab, stw, fst deben utilizarse.
 +
 +  * [HECHO] Redefinir los métodos de la clase Isis para que puedan ser llamados con **keyword arguments**,​ e.g.
 +
 +      db.search({'​query':'​marsden',​ '​count':​5}) ​ =>  db.search(query='​marsden',​ count=5)
 +
 +      >>>​ def test(**params):​
 +      ...   print params
 +      ...
 +      >>>​ test(db='​test',​ count=10)
 +      {'​count':​ 10, '​db':​ '​test'​}
 +
 +o bien
 +
 +      >>>​ def test(db, **params):
 +      ...   print db, params
 +      ...
 +      >>>​ test('​testdb',​ count=10)
 +      testdb {'​count':​ 10}
 +
 +
 +=== Módulo isis (xispy) ===
 +
 +Nombre sugerido por Rubén Mansilla: **apysis** (API en Python para bases Isis).
 +
 +**Esto es para agregar al ejemplo en isis.py**
 +
 +<code python>
 +def remove_sf_marks(field):​
 +    """​
 +    Input: '​00^aDon Quijote de la Mancha /^cpor Miguel de Cervantes.'​
 +    Output: 'Don Quijote de la Mancha / por Miguel de Cervantes.'​
 +    """​
 +    return re.sub('​\^\w',​ ' ', field['​value'​][4:​]
 +    ​
 +
 +titles = [ unicode(remove_sf_marks(field),​ '​latin1'​) for rec in res['​records'​] for field in rec['​fields'​] if field['​tag'​] == '​245'​ ]
 +formatted = [ '​(%s) ​ %s' % (n, t) for (n, t) in zip(range(1,​ len(titles)),​ titles) ]
 +print '​\n'​.join(formatted)
 +</​code>​
 +
 +
 +==== Manejando registros en Python [ver también: pymarc] ====
 +
 +¿Y ahora cómo obtenemos datos de un registro? He aquí una representación de un registro Isis en Python:
 +
 +<code python>
 +{'​fields':​ [{'​tag':​ '​001',​ '​value':​ '​003003'​},​
 +            {'​tag':​ '​905',​ '​value':​ '​c'​},​
 +            {'​tag':​ '​906',​ '​value':​ '​a'​},​
 +            {'​tag':​ '​907',​ '​value':​ '​m'​},​
 +            {'​tag':​ '​908',​ '​value':​ '#'​},​
 +            {'​tag':​ '​909',​ '​value':​ '#'​},​
 +            {'​tag':​ '​917',​ '​value':​ '​5'​},​
 +            {'​tag':​ '​918',​ '​value':​ '​a'​},​
 +            {'​tag':​ '​919',​ '​value':​ '#'​},​
 +            {'​tag':​ '​008',​
 +             '​value':​ '​840710t19791963riu######​b####​000#​0#​eng##'​},​
 +            {'​tag':​ '​245',​
 +             '​value':​ '​10^aHarmonic analysis of functions of several complex variables in the classical domains /^cby L. K. Hua ; [translated from the Russian by Leo Ebner and Adam Kor\xe1nyi].'​},​
 +            {'​tag':​ '​250',​ '​value':​ '##​^a[Rev. ed.].'​},​
 +            {'​tag':​ '​260',​
 +             '​value':​ '##​^aProvidence,​ R.I. :^bAmerican Mathematical Society,​^c1979,​ c1963.'​},​
 +            {'​tag':​ '​300',​ '​value':​ '##​^aiv,​ 164 p. ;^c24 cm.'},
 +            {'​tag':​ '​440',​
 +             '​value':​ '#​0^aTranslations of mathematical monographs ;^vv. 6'},
 +            {'​tag':​ '​500',​
 +             '​value':​ '##​^aTraducci\xf3n de: [To fu pien shu han shu lun chung ti tien hsing y\xfc ti t`iao ho f\xean hsi].'​},​
 +            {'​tag':​ '​500',​
 +             '​value':​ '##​^aVersi\xf3n original en chino publicada en 1958; versi\xf3n en ruso publicada en 1959.'​},​
 +            {'​tag':​ '​500',​
 +             '​value':​ '##​^a"​Third printing, revised, 1979". Incluye tres ap\xe9ndices.'​},​
 +            {'​tag':​ '​504',​ '​value':​ '##​^aBibliograf\xeda:​ p. 183-186.'​},​
 +            {'​tag':​ '​510',​
 +             '​value':​ '​4#​^aMR,​^c23 #A3277^3(de la ed. en ruso)'​},​
 +            {'​tag':​ '​100',​ '​value':​ '​1#​^aHua,​ Lo-keng,​^d1910-'​},​
 +            {'​tag':​ '​240',​
 +             '​value':​ '​10^aTo fu pien shu han shu lun chung ti tien hsing y\xfc ti t`iao ho fen hsi.^lIngl\xe9s'​},​
 +            {'​tag':​ '​650',​ '​value':​ '#​0^aHarmonic analysis.'​},​
 +            {'​tag':​ '​650',​
 +             '​value':​ '#​0^aFunctions of several complex variables.'​},​
 +            {'​tag':​ '​084',​ '​value':​ '##​^a32Mxx (22E30 31-02 43-02)^2MR'​},​
 +            {'​tag':​ '​010',​ '​value':​ '##​^a###​63016769#'​},​
 +            {'​tag':​ '​040',​ '​value':​ '##​^aDLC/​ICU^cICU^dDLC'​},​
 +            {'​tag':​ '​041',​ '​value':​ '​1#​^aeng^hrus^hchi'​},​
 +            {'​tag':​ '​991',​ '​value':​ '​FG'​},​
 +            {'​tag':​ '​005',​ '​value':​ '​20051018144818.0'​},​
 +            {'​tag':​ '​859',​ '​value':​ '##​^f20051018^hA-5622^pA-5622^uFG'​}],​
 + '​mfn':​ '​3000'​}
 +</​code>​
 +
 +Cómo obtener todas las ocurrencias de un campo:
 +
 +<code python>
 +  def get(rec, tag):
 +      return [field['​value'​] for field in rec['​fields'​] if field['​tag'​] == tag]
 +</​code>​
 +
 +
 +  >>>​ print get(rec, '​504'​)[0]
 +  ##​^aBibliograf�a:​ p. 183-186.
 +
 +Para mostrar datos en Unicode:
 +
 +  >>> ​ print unicode(get(rec,​ '​504'​)[0],​ '​windows-1252'​)
 +  ##​^aBibliografía:​ p. 183-186.
 +
 +
 +Probablemente sea mejor usar **pymarc**, tratando de inventar lo menos posible. Pero aún no lo probé.
 +
 +
 +
 +Para ver:
 +
 +
 +  r = IsisRecord()
 +  ​
 +  r[245]
 +  r[008]
 +
 +
 +  Expresión ​        ​Devuelve
 +  ----------------------------------------------------------------
 +  r['​245'​] ​         ​
 +  r['​245'​]['​a'​]
 +  r['​245a'​]
 +    ​
 +  r.245
 +  r.245.a
 +  r.245a
 +
 +
 +
 +==== Nota sobre errores en wxis ====
 +
 +<​code>​
 +>>>​ print db.edit({'​mfn':​ 2, '​lockid':​ '​FG'​})
 +   ...
 +      <​update>​
 +        <​write>​Lock
 +WXIS|fatal error|unavoidable|recread/​xropn/​w|
 +</​code>​
 +
 +Causa del error: el servidor web no tiene permiso para escribir la base.
 +
 +Moraleja:
 +    * ajustar permisos
 +    * generar mensaje de error amigable
 +
 +
 +==== pymarc ====
 +
 +
 +wxis  <=> xispy <=> myapp
 +
 +Cada isisscript es totalmente ciego; recibe un conjunto de parámetros y se limita a devolver
 +los datos pedidos, sin añadir ni modificar nada, y sin asumir ningún default, salvos los que
 +ya trae incorporados wxis.
 +
 +Lo único más o menos arbitrario es la estructura JSON con que se devuelven los datos.
 +
 +xispy tiene la responsabilidad de:
 +
 +    - pasar ciegamente a wxis los parámetros recibidos desde myapp
 +    - devolver a myapp un objeto que contenga los datos entregados por wxis
 +    - generar excepciones cuando corresponda,​ en base a la respuesta recibida desde wxis
 +
 +Una vez que myapp obtiene los datos, tenemos que ver qué hacemos con ellos. Supongamos que
 +recibimos un registro MARC. Una opción es tomar estos datos como input para una
 +instancia de la clase pymarc.Record:​
 +
 +  >>>​ resp = db.list(from=347,​ count=1) ​ # we need a shortcut: db.list(mfn=347)
 +  >>>​ from pymarc import Record
 +  >>>​ record = Record()
 +  >>>​ record.from_json(resp['​record'​])
 +
 +A partir de ahí ya se puede manipular el registro usando los métodos que proporciona
 +pymarc. Si se desea grabar el registro en la base, habrá que recurrir a un método que
 +genere una lista de campos: [(tag, value), ...]
 +
 +Por lo tanto, parece que necesitamos añadir dos custom methods a pymarc.Record:​
 +
 +    def from_json(self): ​ # or from_dict(self)
 +    ​
 +    def to_list(self)
 +    ​
 +¿Tal vez lo mejor sea trabajar en forma simétrica? from_list() y to_list()
 +
 +Atención: en alguna parte tiene que estar la responsabilidad sobre cómo tratar los datos
 +del leader (MARC vs Isis)
 +
 +==== Sobre cómo llamar a WXIS ====
 +
 +¿Necesito un servidor HTTP para llamar localmente al wxis?
 +
 +No lo sé en el caso de enviar datos para grabar, pero en el resto de los casos debería ser posible evitarlo, llamando directamente a wxis como un comando (local).
 +
 +Pero hay dos problemas a resolver:
 +
 +  * codificación de caracteres en los parámetros pasados en la línea de comandos
 +  * cómo pasar un bloque de texto, p.ej. un registro para ser grabado
 +
 +La mayor ventaja de llamar a wxis vía HTTP es que nos independizamos de la localización del servidor donde se aloja la base de datos. La desventaja es el //​overhead//​ de una petición a un servidor web, aunque éste sea local. Más aun, si sólo quisiéramos una aplicación que acceda a bases Isis pero sin que involucre la Web, entonces tendríamos que montar un servidor web sólo para poder usar wxis!
 +
 +Así podemos llamar a wxis como comando desde Python:
 +
 +<​code>​
 +def call_wxis_command(params):​
 +    import subprocess
 +    args = ''​
 +    for k,v in params.iteritems():​
 +        args += ' "'​ + k + '​='​ + v + '"'​
 +    command = '/​home/​fernando/​www/​cgi-bin/​wxis54 ' + args
 +    print command
 +    p = subprocess.Popen(command,​ shell=True, stdout=subprocess.PIPE) ​
 +    out = p.communicate()[0]
 +    return out 
 +</​code>​
 +
 +=== CDSOAI: IsisScripts on the fly ===
 +
 +Un enfoque alternativo es el de [[http://​ncsi-net.ncsi.iisc.ernet.in/​pmwiki/?​n=Main.Tools|CDSOAI]],​ donde desde un servlet Java se invoca a wxis en "modo comando",​ y por cada llamada a wxis se genera //​on-the-fly//​ un IsisScript que contiene todos los parámetros //​hardcoded//​. Esos IsisScripts se generan en base a una plantilla, donde se sustituyen los valores de los parámetros correspondientes a la llamada actual. De esta manera, los archivos ''​.xis''​ en lugar de las líneas
 +
 +  <field action="​cgi"​ tag="​...">​parametro</​field>​
 +
 +tendrían
 +
 +  <field action="​add"​ tag="​...">​${parametro}</​field>​
 +
 +o alguna otra sintaxis apropiada al lenguaje (e.g. Python) desde el cual se harán las sustituciones. El script resultante de dicha sustitución debería guardarse en un archivo (temporal), para que wxis pueda leerlo, y ese archivo luego debería ser eliminado.
 +
 +Una desventaja de este enfoque es que ya no podemos llamar a los IsisScripts en forma directa, pasándoles los parámetros (vía CGI o línea de comandos); necesariamente tenemos que pasar a través de otro lenguaje.
 +
 +
 +=== Test para wxis en línea de comando ===
 +
 +Pruebas hechas en Linux con Gnome Terminal configurada para usar encoding Windows-1252
 +
 +  * wxis: CISIS Interface v5.2b/​GC/​M/​32767/​10/​30/​I - XML IsisScript WWWISIS 7.1
 +  * mx: CISIS Interface v5.2b/​GC/​W/​M/​32767/​10/​30/​I - Utility MX
 +
 +
 +**test.xis**:​
 +
 +<code xml>
 +<​IsisScript>​
 +<field action="​cgi"​ tag="​1">​query</​field>​
 +<​display><​pft>'​query:​ ', v1/</​pft></​display>​
 +<do task="​search">​
 +   <​parm name="​db">/​home/​fer/​test</​parm>​
 +   <​parm name="​expression"><​pft>​v1</​pft></​parm>​
 +   <​loop>​
 +      <​display><​pft>'​v1:​ ', v1/</​pft></​display>​
 +   </​loop>​
 +</do>
 +</​IsisScript>​
 +</​code>​
 +
 +<​code>​
 +$ mx tmp "​proc='​a1#​anís#'"​ create=test count=1 now
 +$ mx test "fst=1 4 v1" actab=ac-ansi.tab ​ uctab=uc-ansi.tab fullinv=test
 +$ ifkeys test
 +     ​1|ANIS
 +$ wxis IsisScript=test.xis "​query=anis"​
 +query: anis
 +v1: anís
 +$ wxis IsisScript=test.xis "​query=anís"​
 +query: anís
 +v1: anís
 +</​code>​
 +
 +Es el comportamiento esperado. Nótese que no se necesita indicar a wxis las tablas ANSI.
 +
 +¿Pero qué pasa si esto se hace en un ambiente UTF-8?
 +
 +
 +=== Otro enfoque: parámetro ''​in''​ ===
 +
 +
 +Recordando que wxis es un software sub-documentado,​ y teniendo en cuenta su íntima relación con mx, se me ocurre probar esto:
 +
 +1. Crear un archivo ''​wxis.par'':​
 +
 +<​code>​
 +IsisScript=test.xis
 +query=anis
 +</​code>​
 +
 +2. Ejecutar wxis pasándole un parámetro ''​in'':​
 +
 +  wxis in=wxis.par
 +
 +o bien, eliminando en wxis.par la línea de IsisScript:
 +
 +  wxis IsisScript=test.xis in=wxis.par
 +
 +
 +==== Cómo hacer una API para bases isis ====
 +
 +Podemos tomar como referencia:
 +
 +  * Malete + PHP
 +  * Python: SQL Database Interfaces (e.g. ver Programming Python, Cap. 19)
 +
 +==== pyIsis (Degiorgi) ====
 +
 +http://​www.codigophp.com/​pyisis/​pyisis.txt
 +
 +==== Django ====
 +
 +Sobre uso de bases no relacionales:​ [[http://​groups.google.com/​group/​django-users/​browse_thread/​thread/​9a426f8fa0a7bc90/​708a3278356d025b|Using Django with a non-relational back-end data store?]]
 +
 +    Anything in Django that inherits from django.db.models.Model is fairly
 +    tightly tied to Django'​s database backend: so SQL databases, etc.
 +    ​
 +    If you want to get your data from another location, you can do that and
 +    then just use Django'​s views and templates to present your data. Things
 +    like generic views will not work without a bit of work on your part
 +    (since they require something that works exactly like a model'​s query
 +    interface), but they are just an aid in any case -- you do not lose any
 +    functionality if you do not use generic views.
 +    ​
 +    Before diving too deeply into putting a custom backend into the existing
 +    model infrastructure,​ you should probably have a think about whether you
 +    really need to (think about whether your are customising the right place
 +    in the hierarchy). If you already have a way of accessing data and
 +    getting it into Python objects, then you can probably live without
 +    Django'​s ORM layer. After all, the views are just Python code, so they
 +    can work with anything you like. The templates access everything using
 +    attributes or dictionary keys or methods, so you pass them objects or
 +    dictionaries and they do not care whether it's from Django'​s ORM or not.
 +    ​
 +    Although the various layers in Django (models, views, templates) all
 +    work well together, they are sufficiently orthogonal that they don't
 +    rely on each other to operate, so you can happily use your own "​model"​
 +    layer and that might be easier than trying to extend Django'​s model
 +    layer to talk to your own backend. ​
 +
 +
 +{{tag>​desarrollo isis python}}
  
cisis_y_python.txt · Last modified: 14/05/2009 00:00 (external edit)