Reading time: < 1 minute
En un .txt tenia unes notes que havia pres sobre entorns de programació, escencialment la conferència de la que són les notes parlava sobre PHP i diferents formens de fer el desplegament dels projectes. Aquesta informació l'he passat a una pàgina del wiki amb la idea d'anar-la actualitzant per altres llenguatges especialment amb la idea de afegir-hi notes de python.
Així doncs l'enllaç del wiki és a: Notes about programming environments and deployment. Tal com podeu deduir amb el títol les notes són amb anglès, sento no recordar la conferència per afegir-hi la presentació.
A continuació enganxo el contingut de la wiki de forma dinàmica, així quan actualitzi la wiki s’actualitzarà l’article:
Reading time: < 1 minute
Un WebHook és una HTTP callback, o sigui, un HTTP POST que es dona quan algo passa (un event); o sigui, que podem definir WebHook com a sistema de notificació d’events per HTTP POST.
El valor més important dels WebHooks és la possibilitat de poder rebre events quan aquest passen no a través dels ineficaços mecanismes de polling.
Si voleu més informació sobre aquest tema: WebHooks site.
Feia massa temps que no jugava amb DBUS i les he passat una mica negres aquesta tarda intentant recordar com funcionava tot plegat. La qüestió de base és molt senzilla, com que el codi parla per si mateix. Simplement adjuntaré els dos codis.
Receptor de senyals DBUS, rep la senyal amb format ‘string’ i la mostra:
#!/usr/bin/env python#--encoding:UTF-8--"""entra en un loop esperant senyals emeses a:dbus_interface=cat.oriolrius.provaobject_path="/cat/oriolrius/prova/senyal"ambnomde senyal:'estat'quanesreplasenyallamostrem"""import gobjectimportdbusimportdbus.mainloop.glibdefmostra(m):printmdbus.mainloop.glib.DBusGMainLoop(set_as_default=True)bus = dbus.SessionBus()bus.add_signal_receiver(mostra,path="/cat/oriolrius/prova/senyal",dbus_interface="cat.oriolrius.prova",signal_name = "estat" )loop = gobject.MainLoop()loop.run()
Emisor de senyals DBUS, envia una senyal de tipus ‘string’ amb el contingut ‘hola’:
#!/usr/bin/env python#--encoding:UTF-8--"""Emet una senyal a dbus, al bus 'session' amb destí:dbus_interface=cat.oriolrius.provaobject_path="/cat/oriolrius/prova/senyal"ambnomde senyal:'estat'"""import gobjectimportdbusfromdbus.serviceimportsignal,Objectimportdbus.mainloop.glibclassEmetSenyal(Object):def__init__(self,conn,object_path='/'):Object.__init__(self,conn,object_path) @signal('cat.oriolrius.prova')defestat(self,m):globalloopprint("senyal emesa: %s" % m)gobject.timeout_add(2000,loop.quit)dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)loop = gobject.MainLoop()bus = dbus.SessionBus()o = EmetSenyal(bus,object_path='/cat/oriolrius/prova/senyal')o.estat('hola')loop.run()
Usant el ‘dbus-monitor’ es pot veure la traça del missatge:
A vegades quan estem fent un programa ens interessa que el nostre codi pugui ser extés sense haver de tocar la seva estructra, fins hi tot el que ens pot interessar és que aquest codi sigui extés en algunes ocasions i en d’altres no. Un altre requeriment que podem tenir també seria que qui l’extengui no siguem nosaltres. Sovint tot això i molt més s’acostuma a fer amb el que anomenem Plug-ins, per cert, sempre m’ha fet molta gràcia la traducció al català de la paraula: ‘afegitons’.
Doncs bé, com que jo de programació no hi entenc gaire li vaig demanar al Pau que m’ajudés a entendre els models de plug-ins que implementaven alguns programes fets amb Python, ja que m’ineressava integrar aquesta funcionalitat en una serie de codis que estic desenvolupament. En aquest post intentaré explicar com funciona el paradigma dels plug-ins que usa Trac.
Primer de tot cal tenir en compte que Trac usa un patró de desplegament d’objectes anomenat Singleton, o sigui que totes les instàncies d’un objecte es refereixes a la mateixa instància. De fet, no sé dir fins a quin punt és necessari que el codi segueixi aquest patró per usar el sistema de plug-ins; tot i que jo diria que almenys les parts del codi que vulguin ser exteses pel model de plug-ins de Trac l’han de seguir.
Els plug-ins de Trac tenen les següents característiques:
Un Plug-in és un component que extent la funcionalitat d’un altre component
Un Plug-in pot extendre un altre Plug-in
Per tal d’incorporar la filosofia que té Trac per suportar Plug-ins al nostre codi cal importar el component ‘trac.core’, d’aquest component usarem el següent:
trac.core.Interface (classe) s’usa per definir quin és el contracte que hauran de seguir els plug-ins.
trac.core.ExtensionPoint (funció) quan volem que un component sigui extés usarà aquesta funció per recuperar les implementacions del contracte. Deifineix els punts de hook que té el nostre codi.
trac.core.implements (funció) quan un component usa aquesta funció és per implementar un contracte, o sigui, que els plug-ins que es construeixin l’han d’usar.
Abans de seguir explicaré que s’enten per contracte. Un contracte és en escència una classe de tipus interficie (python no té aquest model com a tal) que defineix quins mètodes (o altres classes) poden ser extesos dins el component original. O sigui, que cal no només definir quin és el contracte que s’ofereix sinó també documentar-lo el millor possible, explicant quines són les entrades i sortides que s’esperen de cada un dels mètodes/classes.
Perquè tot plegat s’entengui millor el Pau em va posar el següent exemple:
Imaginem que tenim una classe del tipus DNI que implementa una base de dades de DNIs, on té un metode que ens permet entrar DNIs a la base de dades:
el que fa és carregar els plug-ins que extenen la funcionalitat del codi original. A més cal que ens fixem que el paràmetre que usa la funció és la classe que defineix el contracte sobre el que es fan els plug-ins:
Com es pot veure el contracte només defineix un mètode: ‘check’ que ha de tenir un paràmetre d’entrada i espera un valor boleà de sortida.
Cal fixar-se en que la classe DNI cada vegada que afegeixi un element a la base de dades (en aquest cas una simple llista), cridarà a tots els plug-ins que compleixin el contracte per l’ordre en que s’han instanciat (s’han importat al codi original) mitjançant el següent codi.
fordni_checkinself.dni_checks:ifdni_check.check(dni) is False:print"El dni %s sembla no ser correcte"% ( dni )return
Un exemple de plug-in sobre el codi anterior i que compleix el contracte especificat podria ser aquest:
Es pot veure com la classe és una instància de ‘trac.core.Component’ (model Singleton) i implementa la interfice ‘dni.IDNIInput’. A nivell funcional el que es fa és ben simple, comprovem que sigui una cadena de texte i que tingui una mida de 9 caràcters, si això es dona retorna un ‘True’ o sinó un ‘False’.
Un exemple de com quedaria el codi principal seria:
importtrac.coreclassIDNIInput(trac.core.Interface):defcheck(nom):""" Es cridada cada cop que s'entra un nou dni"""classDNI(trac.core.Component):dni_checks = trac.core.ExtensionPoint(IDNIInput)def__init__(self):self._dnis = []defadd(self,dni):assertnotdniinself._dnis,"DNI ja existeix"fordni_checkinself.dni_checks:ifdni_check.check(dni) isFalse:print"El dni %s sembla no ser correcte" % ( dni )returnself._dnis.append(dni)defllista(self):printself._dnis
A aquestes altures ja s’han carregat dos plug-ins que treballen un després de l’altre i s’ha pogut apreciar la simplicitat i potència del model. Obviament es poden trobar coses a faltar com per exemple algún element que defineixi el llistat de plug-ins disponibles i que permeti alterar l’ordre en que aquests s’executen però això ja s’hauria de desenvolupar a part.
Espero haver-ho descrit de forma entenedora i sent el més fidel possible a les explicacions del Pau, al que he d’agrair-li l’esforç i dedicació per explicar-me aquest model de Plug-ins de Python.
Sovint faig molts scripts de sistema usant python com a llenguatge de programació, doncs bé, sobretot quan aquests scripts s’han de llençar usant el ‘crontab’ va molt bé tenir un bon ‘log’ per saber com van les coses. Així doncs, la setmana passada vaig decidir posar-me a fons amb el tema ‘logging’ de python i ara la forma de fer ‘logs’ que uso per defecte en els meus scripts és:
Després d’importar la llibreria de ‘logging’ es recuperar el fitxer de configuració on estan definits els paràmetres de ‘logging’. Un cop carregat, descomento una o les dues línies que hi ha a continuació en funció de si vull logs per pantalla o només contra un fitxer. La última línia només indica fins a quin nivell de depuració he de fer mostrar logs.
A més vaig trobar un petit script, que carrega una GUI per generar les configuracions dels fitxers de configuració dels ‘logs’, realment útil i simple d’usar:
Aquesta GUI l’he trobada al paquet: logging-0.4.9.6.tar.gz concretament al directori ‘tests’. Si no voleu buscar tant també podeu descarregar-lo directament: logconf.py.
La idea que es persegueix al usar múltiples esquemes en un sol projecte és la de poder usar múltiples bases de dades en un sol projecte. La idea sembla molt senzilla, però a través de la documentació oficial del projecte Symfony la veritat és que no he sabut trobar la solució. En l’explicació per portar a terme aquesta funcionalitat hem referiré tota l’estona als fitxers .yml i no als .xml equivalents, però suposo que la idea és totalment exportable.
Es tracta de deixar d’usar el fitxer schemal.yml i passar a usar diferents fitxers anomentats nomarbitrari_schema.yml, si encomptes d’un nom voleu usar un número tampoc hi ha problemes per fer-ho, per exemple, 1_schema.yml i successius.
Pel que fa al contingut d’aquests fitxers és el de sempre, tot i que jo ús recomanaria usar petites ajudes com aquesta:
Aquest nombasededades l’usarem després per especificar les dades de connexió al fitxer databases.yml, però potser el més interessant és fixar-se en el paràmetre package que ens permet que tot el model de dades quedi guardat dins de lib/model/nombasedades. O sigui, que tot queda molt més ben organitzat i fàcil d’accedir.
Cal que vigileu si dues bases de dades tenen dues taules que s’anomenen igual, ja que si això passa llavors hi haurà un conflicte en els models de dades que es generen, perquè aquestes tindran el mateix nom. Això és fàcilment solucionable simplement a l’schema heu de dir-li que el nom de la classe que representa les dades d’aquella taula de la base de dades és un nom diferent al de la taula, o sigui, el nom escollit per defecte.
Pel que fa al fitxer databases.yml el seu contingut és el de sempre, simplement cal que penseu que cal descriure la connexió de cada una de les bases de dades que heu anat declarant als diferents fitxers d’schema.yml, un exemple:
El mes de març vaig escriure un article titulat gairebé igual que aquest, en aquell article explicava com s’ho feia el PHP5 per rebre i retornar tipus complexes (p.e. objectes) com a paràmetres de crides SOAP. Doncs bé havia trobat una cosa que em tenia una mica mosca i és que quan enviava una objecte del client cap al servidor o alrevés en perdia el tipus. Doncs bé aquesta tarda he descobert com aconseguir fer això sense perdren el tipus. Ja sabeu que quan se’m posa una cosa al cap…
El tema no és senzill i el generador de WSDL del ZendStudio és una mica tiquis-miquis fent aquestes coses així doncs s’ha de fer tot molt maco perquè ho entengui el generador WSDL. De fet, la gràcia esta en definir el tipus complexe dins del fitxer WSDL, per això el generador necessita que li donem tota la informació com ell vol sinó no és capaç de generar bé el fitxer.
En l’exemple que us poso a continuació he creat la classe persona amb tres atributs dos de públics i un de privat. El privat al contrari del que comentava en l’article anterior no perd el seu valor. Així doncs, en principi tenim l’objecte 100% replicat amb el mètode que ara us explicaré.
La classe persona:
<?php
/**
* classe persona
*
*/
class persona {
/**
* propietat nom
*
* @var string $nom
* @var string $cognom
* @var string $dni
*/
public $nom;
private $cognom;
public $dni;
/**
* setter function
*
* @param string $nom
*/
function set($nom) {
$this-?>nom=$nom;
}
/**
* setter cognom
*
* @param string $cognom
*/
function setCognom($cognom)
{
$this-?>cognom=$cognom;
}
/**
* getter function
*
* @return string valor a tornar
*/
function get()
{
return $this-?>nom;
}
/**
* getter cognom
*
* @return string valor a tornar
*/
function getCognom()
{
return $this-?>cognom;
}
}
?>
Fixeu-vos que comentem el codi segons la sintaxis del phpDoc això es fa perquè és d’aquests comentaris d’on el generador WSDL extreurà els tipus de les variables per poder-les exportar sense perdre informació. La propietat dni no té ni getter ni setter perquè hi accedirem directament. Per la resta de coses és una classe completament normal i senzilla.
El servidor és força simple:
<?php
require_once("persona.class.php");
/**
* servidor soap
*
*/
class soapservice {
/**
* funcio soap publicada
*
* @param persona $per
* @return persona
*/
function peticion($per) {
$per->dni="38147000x";
return $per;
}
}
$server = new SoapServer("server.wsdl",array("classmap"=>array("persona"=>"persona")));
$server->setClass("soapservice");
$server->handle();
?>
Comentar que la classe soapservice rep un objecte de tipus persona afegeix un valor a la propietat dni i retorna el mateix objecte. Aquí també hem de documentar el codi segons la sintaxis del phpDoc així el model WSDL ens dirà explicitament el tipus dels objectes que rep i torna el mètode peticion.
Pel que fa a la creació de l’objecte server del tipus SoapServer fixeu-vos que mapegem amb l’opció classmap l’objecte de tipus persona que es passa com a paràmetre amb la classe local també anomenada persona. Gràcies a aquest mapeig quan rebem l’objecte per al soapservice aquest ja és del tipus persona.
Ara toca veure el codi del client, aquest codi també té un mapeig igual que el servidor de SOAP, però en aquest cas serveix per capturar la sortida del servidor i com que l’objecte que es torna a la sortida també és del tipus persona el classmap és igual que el del servidor.
<?php
require_once("persona.class.php");
// creem objecte tipus persona
$myPersona=new persona;
$myPersona->set("el_nom");
$myPersona->setCognom("el_cognom");
$client = new SoapClient("server.wsdl",array("classmap"=>array("persona"=>"persona")));
$localPersona=$client->peticion($myPersona);
var_dump($client->__getFunctions());
print_r($localPersona);
echo $localPersona->getCognom();
?>
Una de les coses interssants que fem és preguntar quines funcions s’estan publicant al servidor SOAP. Aquesta informació esta descrita al WSDL i la funció __getFunctions() ens la formateja per humans. Així doncs podem veure que hi ha disponible la funció peticion que té com a paràmetre un objecte del tipus persona i que retorna un altre objecte de tipus.
Després mostrem (print_r) l’objecte localPersona que ens ha tornat la crida al mètode peticion ($localPersona=$client->peticion($myPersona);):
persona Object
(
[nom] => el_nom
)
Finalment fem una prova de concepte i obtenim el valor de la propietat nom a través del getter, o sigui, echo $localPersona->getCognom();:
el_nom
Com podeu veure ha quedat ben demostrat que és relativament senzill enviar i rebre tipus complexes a través dels serveis SOAP. Tota la gràcia esta en el fitxer WSDL, a continuació us enganxo el que m’ha generat el Zend Studio per fer aquestes proves fixeu-vos com al prinicipi de tot el primer que fa és declarar el tipus complexe de dades que ha de traspassar, en el nostre cas la classe persona.
Treballant en un projecte que estem programant en PHP5 em va sortir aquest dubte, sobretot després de llegir les limitacions dels servidors SOAP que implementa PHP5. El tema finalment té una resposta positiva: SI, PERO…. A continuació us poso un exemple de com funciona aquest tema amb el PHP5 i quines limitacions té.
Definim el servidor SOAP que com podeu veure és senzillissim, l’únic que fa el servidor és re-enviar el mateix objecte que ha rebut.
<php
class SOAPservice {
function peticion($per) {
return $per;
}
}
$server = new SoapServer("server.wsdl");
$server->setClass("SOAPservice");
$server->handle();
?>
A continuació podeu veure el codi del client, és un codi molt senzill de només dues línies. Com podem veure primer definim la classe persona i abans de cridar el client SOAP instanciem la classe, després usem aquest objecte com a paràmetre a la crida SOAP. Capturem l’objecte que ens retorna el servidor SOAP i a continuació mostrem el seu contingut amb l’ordre print_r.
<?php
/*
definim classe persona
*/
class persona {
public $nom;
function setNom($nom) {
$this->nom=$nom;
}
function setCognom($cognom) {
$this->cognom=$cognom;
}
function getNom() {
return $this->nom;
}
function getCognom() {
return $this->cognom;
}
}
/*
creem objecte tipus persona
*/
$myPersona=new persona;
$myPersona->setNom("el_nom");
$myPersona->setCognom("el_cognom");
/*
fem una crida SOAP amb un objecte com a parametre
*/
$client = new SoapClient("server.wsdl");
$myObj=$client->peticion($myPersona);
/*
mostrem objecte generic que retorna la crida SOAP
*/
print_r($myObj);
?>
Si mirem la sortida que ens dona quan cridem el client podem veure que l’objecte que torna no és del tipus persona sinó d’un tipus intern del PHP5 anomenat stdClass. Aquest objecte només disposa dels atributs públics. No podem accedir als atributs privats o protegits i hem perdut tots els mètodes siguin del tipus que siguin.