Bag dinamiche e Resolver

Introduzione

Una bag oltre a valori scalari, vettoriali ed altre bag può contenere un oggetto dinamico che si chiama resolver ovvero un qualsiasi oggetto che erediti dalla classe BagResolver. Un resolver è un oggetto dinamico che restituisce il risultato di una chiamata a funzione come se fosse un valore realmente contenuto nella bag.

Quando una bag contiene o è costituita da elementi dinamici viene anche definita Bag dinamica. Possiamo avere Bag dinamiche che contengono

  • chiamate a query su un database
  • chiamate a webservice
  • chiamate a funzioni che calcolano valori in tempo reale
  • chiamate al sistema operativo

O qualunque altra cosa vi possa venire in mente quando definite un resolver

BagResolver

L’idea del resolver è quella di nascondere le chiamate esplicite, definendo invece un oggetto che le esegue quando si accede ad un path della bag e che ne restituisce il risultato come se fosse un valore statico, in modo del tutto trasparente. Un resolver fa l’override della primitiva __call__ ed implementa il metodo load() e quando si accede ad un elemento resolver viene invocato. Un resolver però può avere anche una cache, utile specialmente se la chiamata è lenta o onerosa. Se si imposta il parametro cacheTime viene salvata localmente all’indirizzo del resolvere il risultato dell’ultima chiamata e questo viene restituito a tutti gli accessi successivi finché non scade il tempo di cache. Il valore di default del cacheTime è 0. Un resolver deve implementare il metodo load()

Esempio 1: TimeResolver

Un esempio molto semplice di resolvere è il TimeResolver

from datetime import datetime
from gnr.core.gnrbag import Bag, BagResolver

class TimeResolver(BagResolver):
    def __call__(self):
        return datetime.now()

Ora proviamo ad usarlo

>>> mybag = Bag()
>>> mybag['now'] = TimeResolver()
>>> print mybag['now']
2010-11-18 11:47:13.237443

Se impostiamo un cacheTime pari a 100. Il valore verrà aggiornato solo ogni 100ms.

>>> ct=100
>>> mybag['now']=TimeResolver(cacheTime=ct)
>>> print mybag['now']
2010-11-18 11:49:34.257631

Esempio 2: UserInfoResolver

In questo resolver il risultato è una Bag contenente alcune informazioni prese da dalle librerie os e socket ( hostname, IP, PID, user).

from gnr.core.gnrbag import Bag, BagResolver
import socket, os

class UserInfoResolver(BagResolver):

    def load(self):
      result = Bag()
      try:
          result['hostname']=socket.gethostname()
          result['ip']=socket.gethostbyname(result['hostname'])
      except:
          result['hostname']='localhost'
          result['ip']='unknown'

      result['pid']=os.getpid()
      result['user']=os.getenv('USER')
      result['ID']=result['ip']+'-'+str(result['pid'])+'-'+result['user']
      return result

Vediamo il resolver all’opera.

>>> mybag = Bag()
>>> mybag['info'] = UserInfoResolver()
>>> info = mybag['info']
>>> template = "This is the process %s.\nYou are user %s, from the host %s at the address %s"
>>> print template %(mybag['info.pid'], mybag['info.user'], mybag['info.hostname'], mybag['info.ip'])

Esempio 3: RssFeedResolver

Questo resolver riceve l’URL di un RSS feed e, dal momento che si popola facilmente una bag a partire da un XML è molto semplice gestire il risultato.

class RssFeedResolver(BagResolver):
  def init(self, feed):
    self.feed=feed

  def load(self):

    feed= Bag(self.feed)['rss.channel']
    result= Bag()
    result['title']= feed.pop('title')
    result['description']= feed.pop('description')
    result['link']= feed.pop('link')
    result['language']= feed.pop('language')
    result['copyright']= feed.pop('copyright')
    dig= feed.digest('#v.title, #v.description, #v.pubDate, #v.link')
    news=Bag()

    for title, description, pubDate, link in dig:
      news.setItem(title.replace('.', '\.').replace(' ','_'),description,link=link, date=pubDate, title=title)
      result['news']=news

    return result

Here is how the resolver works:

>>> mybag['feeds.washingtonpost']= RssFeedResolver('http://www.washingtonpost.com/wp-dyn/rss/world/index.xml')
??? There's an unknown error:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/niso/sviluppo/genro/gnrpy/gnr/core/gnrbag.py", line 2357, in __init__
    parname = self.classArgs[j]
IndexError: list index out of range

The resulting Bag is structured as shown below:

item value
title washingtonpost.com - World News and Analysis From The Washington Post
description World news headlines from the Washington Post,including international news and opinion from Africa, North/South America,Asia,Europe and Middle East. Features include world weather, news in Spanish, interactive maps, daily Yomiuri and Iraq coverage.
link http://www.washingtonpost.com/wp-dyn/content/world/index.html?nav=rss_world
language EN-US
copyright None
news Bag of News

Each new item is a BagNode structured as follows:

label value attributes
In_Russia,_A_Secretive_Force_Widens_ MOSCOW - On Nov. 15, the Russian Interior Ministry and Gazprom, the state-controlled energy giant, announced… link, date, title

Scorciatoria, il BagCbResolver

In realtà se il risultato che deve essere ritornato è una semplice chiamata a funzione si può evitare di definire una classe Resolver apposta. E si può usare una istanza del più generico BagCbResolver che è un BagResolver che chiama una funzione callback passata come parametro.

>>> from gnr.core.gnrbag import Bag, BagCbResolver
>>> from datetime import datetime
>>> mybag = Bag()
>>> mybag['now'] = BagCbResolver(datetime.now)
>>> print mybag['now']
2010-11-18 14:23:40.070095

Questa scorciatoia funziona con tutte le funzioni callback.

def sayHello():
return “Hello World!”
>>> mybag['hello'] = BagCbResolver(sayHello)
>>> print mybag['hello']
Hello World!

In alternativa si può usare il metodo setCallBackItem

>>> mybag.setCallBackItem('hello', sayHello)
>>> print mybag['hello']
Hello World!