Showing posts with label Python. Show all posts
Showing posts with label Python. Show all posts

Wednesday, May 18, 2022

¿Qué es Nylas, por qué me uní y porque debería importarles?



Me uní a Nylas más o menos hace 4 meses como su primer Senior Developer Advocate. Puede parecer un tiempo muy corto, pero en el mundo de los startups, el tiempo se mueve de forma diferente.

Aviso: Este post está escrito en Español, pensado en desarrolladores de habla hispana, pero no significa que Nylas ofrezca contenido, soporte o servicios en Español. Todo servicio es ofrecido únicamente en Inglés.

¿Qué es Nylas?


En palabras sencillas, Nylas es un grupo de APIs que nos permiten conectarnos de manera sencilla con cualquier proveedor de correo, calendario y contactos sin tener que pasar por ningún tipo de proceso largo y tedioso.

En otras palabras, hacemos el trabajo pesado para que ustedes puedan enfocarse en tu negocio.


Si hablamos de Email, ofrecemos “Un API de Correo para cualquier proveedor”. Esto significa que nuestro API de Correo es universal y puedes conectarte con varios proveedores sin tener la necesidad de escribir código específico para cada uno de ellos. Además, proveemos capacidades de tiempo real, sincronización bi-direccional, y full CRUD (Crear, Leer, Actualizar, Borrar). Si quieres saber más, solo tienes que ir a nuestra página Universal Email API.


Cuando se trata de Calendarios, ofrecemos las mismas capacidades que en nuestro API de Correo API, pero también sincronización de conferencias, metadata de eventos, y webhooks programables. Si quieres saber más, solo tienes que ir a nuestra página Universal Calendar API.


Si hablamos de Contactos, podemos crear, actualizar o eliminar contactos en cualquier proveedor compatible. Si quieres saber más, solo tienes que ir a nuestra página Universal Contacts API.


Con APIs para Correos, Calendarios, y Contactos, solo estamos comenzando. Miren que más ofrecemos por encima de esos APIs para proveer a los desarrolladores aún más poder.

Con Neural API, explorar el mundo de AI y ML es muy sencillo, con modelos listos para utilizar que pueden ser utilizados para limpiar conversaciones, extraer firmas, realizar OCR, y ejecutar análisis de sentimientos. Si quieres saber más, solo tienes que ir a nuestra página Neural API.With Neural API, exploring the world of AI and ML is easy, with ready-made models that can be used to clean conversations, extract signatures, perform OCR, and run sentiment analysis. If you want to learn more, just go to our Neural API page.


Nylas Streams es nuestra solución ETL que requiere muy poco o nada de código para transformar y consumir datos de comunicaciones para E-Commerce, Ventas, Fintech y Customer Success. Si quieres saber más, solo tienes que ir a nuestra página Nylas Streams.


Scheduler, un organizador completo con UI configurable. Si quieres saber más, solo tienes que ir a nuestra página Scheduler.


Components, widgets UI/UX completamente flexibles y listos para utilizar inmediatamente. Si quieres saber más, solo tienes que ir a nuestra página Components.


¿Por qué me uní?


Lo primero que llamó mi atención fue el hecho de que Nylas provee APIs que hacen que el manejo de comunicaciones sea mucho más sencillo. Manejar Correo, Calendarios y Contactos brinda mucho espacio para poder crear aplicaciones impresionantes.

Lo segundo que me atrajo de Nylas fue la cultura de la compañía. Todos se ven comprometidos los unos a los otros, puesto que vi mucho apoyo mutuo cuando la cuenta de Nylas posteaba algo en las redes sociales, y también mis entrevistas fueron más una conversación entre amigos que una entrevista de trabajo.

Lo tercero y probablemente más importante para mí, fue poder regresar a Developer Advocacy, que es algo que realmente me apasiona. Poder compartir con la comunidad de desarrolladores es algo que me hace sentir bien y que me hace una mejor persona y un mejor desarrollador.

Obviamente, estas 3 cosas no van a ser suficientes para convencerlos, así que he creado un par de pequeños ejemplos.

Para esto voy a utilizar uno de mis lenguajes de programación favoritos– R. Y aunque no soy un profesional de R ni tampoco un experto, es algo que me encanta, así que tenganme paciencia, deben hacer una manera mucho mejor de hacer esto.

Lo que vamos a hacer, es simplemente leer los primeros tres mensajes de nuestro inbox e imprimir los títulos.


El resultado va a ser:

A Nylas email 
Invitation: RRSP Webinar @ Tue Feb 8, 2022 1pm - 2pm (EST) (alvaro.t@nylas.com) 
Start here: Welcome to Clockwise


Obviamente, aunque R es un lenguaje asombroso utilizado por Estatistas y Data Scientists alrededor del mundo, no es un lenguaje utilizado por las masas.

Es por esto que Nylas provee SDKs para Python, Ruby, Node, y Java.

Veamos como podemos leer nuestro inbox utilizando el SDK de Python. Si quieres saber como instalar el SDK, puedes leer nuestra documentación Python Documentation.


El resultado va a ser:

A Nylas email 
Invitation: RRSP Webinar @ Tue Feb 8, 2022 1pm - 2pm (EST) (alvaro.t@nylas.com) 
Start here: Welcome to Clockwise

Como podemos ver, utilizar el SDK es mucho más sencillo, porque no tenemos que navegar la respuesta JSON ni tampoco averiguar que número de elemento tenemos que imprimir. Además, si algo cambia internamente podemos estar tranquilos y saber que el SDK va a ser actualizado para reflejar cualquier cambio mientras que llamar directamente al API va a requerir trabajo manual extra. Y lo mismo sucede con el Calendario, Contactos y el resto de nuestras propuestas.

¿Por qué debería importarles?


Bueno, para empezar, soy su amigable Developer Advocate así que ya saben que voy a mantenerlos informados con información constante e interesante acerca de las APIs de Nylas. Además, crearse una cuenta en Nylas es sencillo, no se necesita tarjeta de crédito y obtenemos 14 días para probar el producto. Listos para comenzar? Solo deben ir a https://dashboard.nylas.com/register y seguir las instrucciones.

Además, estoy trabajando en una serie de posts para ayudarlos a dar sus primeros pasos, hacer sus primeras llamadas a las APIs y en general, ayudarlos a obtener lo mejor de su experiencia con Nylas.

Pueden comenzar leyendo mi post How to Send Emails with the Nylas Python SDK

¿Piensas que Nylas no es para ti? Bueno, entonces recomiéndanos con tus amigos. Puedes conocer a alguien que se puede beneficiar de tener una manera rápida, sencilla y conveniente de acceder a APIs universales de comunicación.

Friday, November 06, 2015

SCN Vanity App

Este post fué posteado originalmente en SCN Vanity App.


Este blog va para mi amigo y colega Aaron Williams quien me dió la idea -;)

Para este, vamos a utilizar Python y algunas librerías interesántes...

Libraries
pip install pandas #For Analisis de Datos y Data Frames
pip install mechanize #Headless Web Browser
pip install beatifulsoup4 #Para Web Scrapping

Por supuesto...también vamos a utilizar Expresiones Regulares...pero eso ya está incluído en Python -:)

Así que, la idea básica es que debemos logearnos al SCN usando nuestro Nombre de Usuario y Password y luego leer la primera página de nuestra carpeta de "contentido" solamente para blogs...luego podemos continuar leyendo las páginas siguientes utilizándo un parámetro que va a cargar los siguientes 20 blogs...

Ahora...y antes de digan algo -:P Esto funciona (por lo menos para mi) solo para las 10 primeras páginas...porque luego de eso el HTML parece ser generado automáticamente...así que no hay ningún lado de donde obtener la data -:( o quizás es porque mi blogs vienen desde hace mucho tiempo atrás...el primer blog que escribí en el SCN lo escribí el 17 de Febrero del 2006 Tasting the mix of PHP and SAP...

En fin...veamos el código fuente -:D


SCN_Vanity_App.py
#coding= utf8

USR = 'YourUser'
PWD = 'YourPassword'

import sys
import re
import mechanize
from BeautifulSoup import BeautifulSoup
import pandas as pd

reload(sys)
sys.setdefaultencoding("iso-8859-1")

cookies = mechanize.CookieJar()
br = mechanize.Browser()
br.set_cookiejar(cookies)
br.set_handle_robots(False)

res = br.open("http://scn.sap.com/login.jspa")

br.select_form(nr=0)
br["j_username"] = USR
br["j_password"] = PWD
br.submit()

br.select_form(nr=0)
res = br.submit()

result = res.read()

author = re.search("username: \'.*",result)
author = re.sub('username: \'|\'|\,','',author.group(0))
displayname = re.search("displayName: \'.*",result)
displayname = re.sub('displayName: \'|\'|\,','',displayname.group(0))

j = 0
df = pd.DataFrame()

while(1==1):
 try:
  link = "http://scn.sap.com/people/%s/content?filterID="\
         "contentstatus[published]~objecttype~objecttype[blogpost]" %(author)
  
  if(j>0):
   link = "http://scn.sap.com/people/%s/content?filterID="\
   "contentstatus[published]~objecttype~objecttype[blogpost]&start=%j" %(author,str(j))
  
  j += 20
   
  res = br.open(link)

  Titles = []
  Likes = []
  Bookmarks = []
  Comments = []
  Views = []

  soup = BeautifulSoup(res.read()) 
  list_items = [list_item for list_item in soup.findAll('td',{'class':'j-td-title'})]
  if(len(list_items) == 0):
   break;
  for i in range(0, len(list_items)):
   title = re.search('[^<>]+(?=<)',str(list_items[i]))
   Titles.append(title.group(0))

  list_items = [list_item for list_item in soup.findAll('a',{'class':'j-meta-number'})]
  for i in range(0, len(list_items), 2):
   like = re.sub('<[^>]+>|in.*','',str(list_items[i]))
   bookmark = re.sub('<[^>]+>|in.*','',str(list_items[i+1]))
   Likes.append(int(like))
   Bookmarks.append(int(bookmark))

  list_items = [list_item for list_item in soup.findAll('span',{'title':'Replies'})]
  for i in range(0, len(list_items)):
   comment = re.sub('<[^>]+>|in.*','',str(list_items[i]))
   Comments.append(int(comment))

  list_items = [list_item for list_item in soup.findAll('span',{'title':'Views'})]
  for i in range(0, len(list_items)):
   views = re.sub('<[^>]+>|in.*','',str(list_items[i]))
   Views.append(int(views))

  for i in range(0, len(Titles)):
   df = df.append({'Title': Titles[i], 'Likes': Likes[i], 'Bookmarks': Bookmarks[i], 
                   'Comments': Comments[i], 'Views': Views[i]}, ignore_index=True)
 
 except:
  break

print("Welcome " + displayname + "\n")
sum_row = df[["Views"]].sum()
print("Total number of Views" + " ==> " + str(sum_row.values[0]))
sum_row = df[["Comments"]].sum()
print("Total number of Comments" + " ==> " + str(sum_row.values[0]))
sum_row = df[["Bookmarks"]].sum()
print("Total number of Bookmarks" + " ==> " + str(sum_row.values[0]))
sum_row = df[["Likes"]].sum()
print("Total number of Likes" + " ==> " + str(sum_row.values[0]))

print("\nTop 3 Blogs with most Views")
print("---------------------------")
df = df.sort_values(by=['Views'],ascending=[False])
for i in range(0, 3):
 print(df.iloc[[i]]['Title'].values[0] + " ==> " + str(df.iloc[[i]]['Views'].values[0]))
print("\nTop 3 Blogs with most Comments")
print("---------------------------")
df = df.sort_values(by=['Comments'],ascending=[False])
for i in range(0, 3):
 print(df.iloc[[i]]['Title'].values[0] + " ==> " + str(df.iloc[[i]]['Comments'].values[0]))
print("\nTop 3 Blogs with most Bookmarks")
print("---------------------------")
df = df.sort_values(by=['Bookmarks'],ascending=[False])
for i in range(0, 3):
 print(df.iloc[[i]]['Title'].values[0] + " ==> " + str(df.iloc[[i]]['Bookmarks'].values[0]))
print("\nTop 3 Blogs with most Bookmarks")
print("---------------------------")
df = df.sort_values(by=['Likes'],ascending=[False])
for i in range(0, 3):
 print(df.iloc[[i]]['Title'].values[0] + " ==> " + str(df.iloc[[i]]['Likes'].values[0]))


Si ejecutamos este código, vamos a obtener un bonito reporte como este -;)


Por supuesto...sería bueno tener una mejor interface de usuario...pero ese no mi fuerte -:(  Así que...si alguien quiere tomar este proyecto y mejorárlo...realmente lo agradecería -;)

Saludos,

Blag.
Development Culture.

Tuesday, May 13, 2014

Mi primer post sobre Julia

Así que...que es Julia? Tan solo otro bonito lenguaje de programación -;)

De acuerdo con sus creadores...

Julia is a high-level, high-performance dynamic programming language for technical computing, with syntax that is familiar to users of other technical computing environments.

Julia es un lenguage dinámico de alto nivel y alta performance para computación técnica, con sintaxis que es familiar para usuarios de otros entornos de computación técnica.



Recién comencé a aprenderlo hace algunos días...y debo decir que realmente me gusta...tiene una sintaxis parecida a Python así que me sentí comodo desde el principio...

Por supuesto...es prácticamente un nuevo lenguaje, así que hay cosas que se están agregando y corrigiendo mientras hablamos...pero la comunidad está creciendo y me alegra estar entre sus primeros entusiastas -:)

Lo primero que hice después de leer la documentación y ver algunos videos fué simplemente pasar una de mis antiguas aplicaciones hecha en Python a Julia...la aplicación fué "LCD Numbers" la cual simplemente pide un número y lo retorna impreso con formato LCD...

Este es el código en Python...

LCD_Numbers.py
global line1, line2, line3

line1 = ""
line2 = ""
line3 = ""

zero = {1: ' _  ', 2: '| | ', 3: '|_| '}
one = {1: '  ', 2: '| ', 3: '| '}
two = {1: ' _  ', 2: ' _| ', 3: '|_  '}
three = {1: '_  ', 2: '_| ', 3: '_| '}
four = {1: '    ', 2: '|_| ', 3: '  | '}
five = {1: ' _  ', 2: '|_  ', 3: ' _| '}
six = {1: ' _  ', 2: '|_  ', 3: '|_| '}
seven = {1: '_   ', 2: ' |  ', 3: ' |  '}
eight = {1: ' _  ', 2: '|_| ', 3: '|_| '}
nine = {1: ' _  ', 2: '|_| ', 3: ' _| '}

num_lines = {0: zero, 1: one, 2: two, 3: three, 4: four,
             5: five, 6: six, 7: seven, 8: eight, 9: nine}

def Lines(number):
    global line1, line2, line3
    line1 += number.get(1, 0)
    line2 += number.get(2, 0)
    line3 += number.get(3, 0)

number = str(input("\nEnter a number: "))
length = len(number)
for i in range(0, length):
    Lines(num_lines.get(int(number[i:i+1]), 0))

print ("\n")
print line1
print line2
print line3
print ("\n") 
Y este es en cambio...su version en Julia...

LCD_Numbers.jl
zero = [1=> " _  ", 2=> "| | ", 3=> "|_| "]
one = [1=> "  ", 2=> "| ", 3=> "| "]
two = [1=> " _  ", 2=> " _| ", 3=> "|_  "]
three = [1=> "_  ", 2=> "_| ", 3=> "_| "]
four = [1=> "    ", 2=> "|_| ", 3=> "  | "]
five = [1=> " _  ", 2=> "|_  ", 3=> " _| "]
six = [1=> " _  ", 2=> "|_  ", 3=> "|_| "]
seven = [1=> "_   ", 2=> " |  ", 3=> " |  "]
eight = [1=> " _  ", 2=> "|_| ", 3=> "|_| "]
nine = [1=> " _  ", 2=> "|_| ", 3=> " _| "]

num_lines = [0=> zero, 1=> one, 2=> two, 3=> three, 4=> four,
             5=> five, 6=> six, 7=> seven, 8=> eight, 9=> nine]

line = ""; line1 = ""; line2 = ""; line3 = ""

function Lines(number, line1, line2, line3)
    line1 *= number[1]
    line2 *= number[2]
    line3 *= number[3]
    line1, line2, line3
end

println("Enter a number: "); number = chomp(readline(STDIN))
len = length(number)
for i in [1:len]
    line = Lines(num_lines[parseint(string(number[i]))],line1,line2,line3)
    line1 = line[1]; line2 = line[2]; line3 = line[3]
end

println(line1)
println(line2)
println(line3 * "\n")

Como pueden ver...el código se ve bastante familiar...pero por supuesto...me deshice de todas esas horribles variables globales...y utilicé algunas de las impresionantes capacidades de Julia, como retorno múltiple de valores y definición de variables en una sola línea... si quieren ver el output...aquí está...


Por supuesto...este es solo un test...las cosas se van a poner interesantes cuando pase un poco de código en R hacia Julia y haga algunas comparaciones de velocidad -;)

Saludos,

Blag.
Development Culture.

Thursday, October 03, 2013

SAP HANA - Hablemos de Filas y Columnas

Como ya sabemos todos...SAP HANA puede manejar la creacion de tablas de dos maneras...Filas y Columnas. Las tablas de Filas se supone que son mejores para tablas que se actualizan constantemente y las tablas de Columnas son para tablas que no se actualizan constantemente...las tablas de Columnas se supone que son mucho mas rapidas cuando se trata de leer informacion.

Como nunca he hecho una prueba de esto...cree dos scripts en Python para generar 1 millon de registros para dos tablas...DOC_HEADER y DOC_DETAILS. Estas dos tablas comparten los campos DOCUMENT_ID, YEAR y AREA pero como la informacion es creada de forma aleatoria, la posibilidad de que ambas tablas compartan la misma informacion es desconocida...

Para propositos de este ejemplo cree ambas tablas DOC_HEADER y DOC_DETAILS utilizando los dos tipos Filas y Columnas y cree otros script en Python para medir el tiempo de lectura...

Column_Row_SAPHANA_Test.py
import dbapi
import time

conn = dbapi.connect('*****', 30015, 'SYSTEM', '****')
cur = conn.cursor()

query = '''SELECT H."DOCUMENT_ID", H."YEAR", H."AREA", SUM("AMOUNT") AS AMOUNT
           FROM "BLAG"."DOC_HEADER_ROW" AS H INNER JOIN "BLAG"."DOC_DETAIL_ROW" AS D
           ON H."DOCUMENT_ID" = D."DOCUMENT_ID"
           AND H."YEAR" = D."YEAR"
           AND H."AREA" = D."AREA"
           GROUP BY H."DOCUMENT_ID", H."YEAR", H."AREA"'''

start = time.clock()
cur.execute(query)
ret = cur.fetchall()
end = time.clock()

counter = 0

for row in ret:
    counter += 1
time_taken = end - start
print("Row Store - %s records in %s seconds") %(counter, time_taken)

query = '''SELECT H."DOCUMENT_ID", H."YEAR", H."AREA", SUM("AMOUNT") AS AMOUNT
           FROM "BLAG"."DOC_HEADER_COLUMN" AS H INNER JOIN "BLAG"."DOC_DETAIL_COLUMN" AS D
           ON H."DOCUMENT_ID" = D."DOCUMENT_ID"
           AND H."YEAR" = D."YEAR"
           AND H."AREA" = D."AREA"
           GROUP BY H."DOCUMENT_ID", H."YEAR", H."AREA"'''

start = time.clock()
cur.execute(query)
ret = cur.fetchall()
end = time.clock()

counter = 0

for row in ret:
    counter += 1
time_taken = end - start
print("Column Store - %s records in %s seconds") %(counter, time_taken)

Las dos consultas hacen exactamente lo mismo...hacen un INNER JOIN entre las dos tablas...utilizando almacenamiento en Filas para el primero y almacenamiento en Columnas para la segunda...

Ejecutamos estos 3 veces y veamos que pasa -;)

Benchmark
Row Store - 7669 records in 2.45972177882 seconds
Column Store - 7669 records in 1.59409638594 seconds

Row Store - 7669 records in 2.32650395252 seconds
Column Store - 7669 records in 1.68382533783 seconds

Row Store - 7669 records in 2.44930342075 seconds
Column Store - 7669 records in 1.52396673192 seconds

Como podemos ver...tenemos una ganancia constante de aproximadamente 38% mas de velocidad cuando utilizamos almacenamiento en Columnas...

Saludos,

Blag.
Developer Experience.

Monday, December 17, 2012

MySQL, PostgreSQL y SAP HANA, una comparacion amistosa

UPDATE! Gracias a un comentario de Alan Suffolk arregle mi script de Python un poco, tan solo moviendo el end = time.clock() y time_taken = end - start despues de la ejecucion de cur.execute(query) porque es ahi exactamente donde el query de SAP HANA termina...pueden ver el nuevo tiempo de proceso en la imagen abajo...

Mi buen amigo Pakdi Decnud me dio una excelente idea mientras estabamos almorzando el mismo dia del evento SAP CodeJam Montreal.

Pakdi me dijo..."Porque no haces una comparacion entre SAP HANA y digamos...MongoDB"...yo pense por supuesto, que era una excelente idea...asi que ayer me puse a investigar MongoDB...asi que seguramente se preguntaran..."Porque entonces estas hablando de MySQL y PostreSQL?"

Respuesta sencilla...y aqui esta lo que pienso...
  • Realmente no entiendo MongoDB...todo el No-SQL es marciano para mi...
  • MongoDB esta basado en "Documentos", lo cual significa que creas colecciones de documentos , no bases de datos o tablas...
  • MongoDB no soporta Inner Joins y las agregaciones necesitan un framework que es mas raro que el mismo MongoDB...
  • MongoDB no esta pensado para aplicaciones empresariales
Es por eso que decidi dar un pequeno giro y tomar dos de las Bases de Datos mas usadas por start-ups y desarrolladores...MySQL y PostgreSQL.

Para este blog, queria tener bastante informacion...asi que como siempre, tome a mi amado Python y cree un script para generar 1 millon de registros para dos tablas. Un script por tabla.

La estructura de las tablas se muestra a continuacion...

DOC_HEADER
Field NameData TypeLength
DOCUMENT_IDVARCHAR8
YEARVARCHAR4
AREAVARCHAR2
NAMEVARCHAR20
LAST_NAMEVARCHAR20

DOC_DETAIL
Field NameData TypeLength
DOCUMENT_IDVARCHAR8
YEARVARCHAR4
AREAVARCHAR2
AMOUNTVARCHAR

Y este es el script para generar 1 millon de registros en un archivo .CSV

Doc_Header_Generator.py
import random
import csv
 
names = ["Anne", "Gigi", "Juergen", "Ingo", "Inga", "Alvaro", "Mario",
"Julien", "Mike", "Michael", "Karin", "Rui", "John", "Rocky", "Sebastian",
"Kai-Yin", "Hester", "Katrin", "Uwe", "Vitaliy"]
last_names = ["Hardy", "Read", "Schmerder", "Sauerzapf", "Bereza", "Tejada",
"Herger", "Vayssiere", "Flynn", "Byczkowski", "Schattka",
"Nogueira", "Mayerhofer", "Ongkowidjojo", "Wieczorek", "Gau", "Hilbrecht",
"Staehr", "Kylau", "Rudnytskiy"]
area = ["001", "002", "003", "004", "005", "006", "007", "008", "009", "010"]
year = ["2000", "2001", "2002", "2003", "2004", "2005", "2006", "2007",
"2008", "2009", "2010", "2011", "2012"]
 
def Generate_File(pSchema, pNumber):
    iNumber = 0
    c = csv.writer(open("Doc_Header.csv", "wb"))
 
    while iNumber < pNumber:
        queries = []
        r_doc_id = random.randrange(1, 999999)
        r_names = random.randrange(0, 20)
        r_lastnames = random.randrange(0, 20)
        r_areas = random.randrange(0, 10)
        r_years = random.randrange(0, 13)
        iNumber += 1
        queries.append(r_doc_id)
        queries.append(year[r_years])
        queries.append(str(area[r_areas]))
        queries.append(names[r_names])
        queries.append(last_names[r_lastnames])
        c.writerow(queries)
 
num_files = input("How many records?: \n")
Generate_File(num_files)

Doc_Detail_Generator.py
import random
import csv
 
area = ["001", "002", "003", "004", "005", "006", "007", "008", "009", "010"]
year = ["2000", "2001", "2002", "2003", "2004", "2005", "2006", "2007",
"2008", "2009", "2010", "2011", "2012"]
 
def Generate_File(pSchema, pNumber):
    iNumber = 0
    c = csv.writer(open("Doc_Detail.csv", "wb"))
 
    while iNumber < pNumber:
        queries = []
        r_doc_id = random.randrange(1, 999999)
        r_areas = random.randrange(0, 10)
        r_years = random.randrange(0, 13)
        r_amount = random.randrange(0, 10000, 1)
        iNumber += 1
        queries.append(r_doc_id)
        queries.append(year[r_years])
        queries.append(str(area[r_areas]))
        queries.append(r_amount)
        c.writerow(queries)
 
num_files = input("How many records?: \n")
Generate_File(num_files)

Con los dos archivos listos, lo cargue en MySQL, PostgreSQL y SAP HANA.

Para medir la velocidad, cree tres scripts de Python utilizando...si...otra vez Bottle...

La idea basicamente es hacer join a las dos tablas, seleccionar el Document_Id, Year, Area y la suma del Amount.

Empezemos con el script de MySQL...

MySQL_Bottle_Documents.py
from bottle import get, run
import mysql.connector
import time


@get('/show_query')
def show_form():
    counter = 0
    start = time.clock()
    conn = mysql.connector.Connect(host='localhost', user='root',
                        password='root', database='P075400')
    cur = conn.cursor()

    query = '''SELECT A.DOCUMENT_ID, A.YEAR, A.AREA, SUM(AMOUNT)
                  FROM DOC_HEADER AS A INNER JOIN DOC_DETAIL AS B
                  WHERE A.DOCUMENT_ID = B.DOCUMENT_ID
                      AND A.YEAR = B.YEAR
                      AND A.AREA = B.AREA
                  GROUP BY DOCUMENT_ID, YEAR, AREA'''
    cur.execute(query)

    output = "<DIV ALIGN='CENTER'><TABLE BORDER='1'>"
    output += "<TR BGCOLOR='#B9C9FE'>"
    output += "<TH>Document</TH><TH>Year</TH>"
    output += "<TH>Area</TH><TH>Amount</TH>"
    output += "</TR>"
    for row in cur:
        counter += 1
        document_id = str(row[0])
        year = str(row[1])
        area = str(row[2])
        amount = str(row[3])
        output += "<TR BGCOLOR='#E8EDFF'>"
        output += '''<TD>%s</TD><TD>%s</TD>
                         <TD>%s</TD><TD>%s</TD>''' % 
                  (document_id, year, area, amount)
        output += "</TR>"
    output += "</TABLE>"
    end = time.clock()
    time_taken = end - start
    output += "<H1>SAP HANA - %s records in %s seconds</H1></DIV>"\
    % (counter, time_taken)
    return output

run(host='localhost', port=8080)

Deje que el script corriera...y despues de mas de una hora...simplemente me aburri y termine el proceso...

Asi que, continue con PostgreSQL...

PostgreSQL_Bottle_Documents.py
from bottle import get, run
import psycopg2
import time


@get('/show_query')
def show_form():
    counter = 0
    start = time.clock()
    conn = psycopg2.connect("dbname=P075400 user=postgres password=root")
    cur = conn.cursor()

    query = '''SELECT "DOC_HEADER"."DOCUMENT_ID", "DOC_HEADER"."YEAR",
                "DOC_HEADER"."AREA", SUM("DOC_DETAIL"."AMOUNT") FROM
                public."DOC_HEADER", public."DOC_DETAIL" WHERE
                "DOC_HEADER"."DOCUMENT_ID" = "DOC_DETAIL"."DOCUMENT_ID"
                AND "DOC_HEADER"."YEAR" = "DOC_DETAIL"."YEAR"
                AND "DOC_HEADER"."AREA" = "DOC_DETAIL"."AREA"
                GROUP BY "DOC_HEADER"."DOCUMENT_ID", "DOC_HEADER"."YEAR",
                "DOC_HEADER"."AREA"'''
    cur.execute(query)

    output = "<DIV ALIGN='CENTER'><TABLE BORDER='1'>"
    output += "<TR BGCOLOR='#B9C9FE'>"
    output += "<TH>Document</TH><TH>Year</TH>"
    output += "<TH>Area</TH><TH>Amount</TH>"
    output += "</TR>"
    for row in cur:
        counter += 1
        document_id = str(row[0])
        year = str(row[1])
        area = str(row[2])
        amount = str(row[3])
        output += "<TR BGCOLOR='#E8EDFF'>"
        output += '''<TD>%s</TD><TD>%s</TD>
                         <TD>%s</TD><TD>%s</TD>''' % 
                  (document_id, year, area, amount)
        output += "</TR>"
    output += "</TABLE>"
    end = time.clock()
    time_taken = end - start
    output += "<H1>PostgreSQL - %s records in %s seconds</H1></DIV>"\
    % (counter, time_taken)
    return output

run(host='localhost', port=8080)

Esta vez...tuve suerte...


Con 2 millones de registros, PostgreSQL logro agregar el campo Amount y generar 7669 registros en 36 segundos...nada mal...

Para SAP HANA, decidi tomar total ventaja de los Calculation Views, asi que cree lo siguiente...



Hice un join a las dos tablas, use una proyeccion, aplique la agregacion y especifique el resultado...luego escribi este script en Python...

SAPHANA_Bottle_Documents.py
from bottle import get, run
import dbapi
import time


@get('/show_query')
def show_form():
    counter = 0
    start = time.clock()
    conn = dbapi.connect('hanasvr-02', 30015, 'P075400', '5pA5kb6i')
    cur = conn.cursor()

    try:
        ret = cur.execute("drop type test_out")
    except dbapi.Error:
        pass

    try:
        ret = cur.execute("drop procedure pyTest")
    except dbapi.Error:
        pass

    queries = ['''create type test_out as table (DOCUMENT_ID NVARCHAR(8),
               YEAR VARCHAR(4), AREA VARCHAR(2), AMOUNT BIGINT)''',
               '''create procedure pyTest (OUT out_test TEST_OUT)\
               language sqlscript reads sql data with result view ProcView as\
               begin\
               out_test = CE_CALC_VIEW("_SYS_BIC"."blag/CV_DOCUMENTS",
               ["DOCUMENT_ID","YEAR","AREA","AMOUNT"]);\
               end''']

    for query in queries:
        cur.execute(query)
        conn.commit()

    query = '''select DOCUMENT_ID, YEAR, AREA, AMOUNT from ProcView'''

    cur.execute(query)
    ret = cur.fetchall()

    output = "<DIV ALIGN='CENTER'><TABLE BORDER='1'>"
    output += "<TR BGCOLOR='#B9C9FE'>"
    output += "<TH>Document</TH><TH>Year</TH>"
    output += "<TH>Area</TH><TH>Amount</TH>"
    output += "</TR>"
    for row in ret:
        counter += 1
        document_id = str(row["DOCUMENT_ID"])
        year = str(row["YEAR"])
        area = str(row["AREA"])
        amount = str(row["AMOUNT"])
        output += "<TR BGCOLOR='#E8EDFF'>"
        output += '''<TD>%s</TD><TD>%s</TD>
                     <TD>%s</TD><TD>%s</TD>''' % 
                     (document_id, year, area, amount)
        output += "</TR>"
    output += "</TABLE>"
    end = time.clock()
    time_taken = end - start
    output += "<H1>SAP HANA - %s records in %s seconds</H1></DIV>"\
    % (counter, time_taken)
    return output

run(host='localhost', port=8080)

Luego de la ejecucion...no podia estar mas feliz...aqui esta el resultado...



SAP HANA manejo los mismos 2 millones de registros...genero los mismo 7669 registros agregados en solo 18 segundos...eso es 50% mas rapido que PostgreSQL y...bueno...solo digamos que...bastante mas rapido que MySQL...

Ahora...diganme que SAP HANA no es la mejor y mas rapida Base de Datos del mercado...Los reto -:)

Luego de hacer un ligero cambio en mi script de Python para SAP HANA...el nuevo tiempo de procesamiento, sin generar la tabla en Bottle es...


Saludos,

Blag.

Wednesday, November 28, 2012

SQLAlchemy y SAP HANA

Este Lunes 26 de Noviembre participe en el Montréal-Python 33: Qualified Quasar un realmente interesante meetup organizado por mis amigos del Montreal Python Community. No fui solo como asistente, pero tambien como speaker y hable sobre "Python y SAP" (Voy a escribir un blog apenas los videos esten listos)...durante mi presentacion, alguien me pregunto sobre SQLAlchemy y SAP HANA.

Hasta ese dia, nunca habia utilizado SQLAlchemy y realmente no sabia si algo asi existia para SAP HANA, pero algo...llamenlo premonicion...me dijo que alguien estaba trabajando en eso...asi que le respondi..."No lo se, pero estoy casi seguro de que alguien esta trabajando en eso...yo les aviso apenas tenga alguna noticia".

El Martes, comence a revisar algo de SQLAlchemy pero no lo logre captar que es lo grandioso que tiene...y de pronto, Srdjan Boskovic me dijo Martin Stein habia estado trabajando en...adivinen que? SQLAlchemy para SAP HANA...me pregunto si podia jugar un poco con eso y quizas escribir un blog...ya se podran imaginar lo feliz y emocionado que estaba...una nueva tecnologia para aprender...horas de romperme la cabeza tratando de entenderlo...levantarme temprano y acostarme tarde solo para escribir un blog en el SCN...estaba el paraiso sin duda alguna...pero...ya estaba ocupado con varias cosas, asi que les prometi a ambos que le daria una mirada al dia siguiente...

Hoy dia, me levante muy temprano...mas temprano de lo normal y comence a analizar el codigo y los ejemplos...lei un par de tutoriales y luego comenze a trabajar por primera vez con SQLAlchemy...con un poco de gran ayuda de Martin...finalmente termine mi aplicacion...puedo decir que me tomo cerca de 15 horas sin pausa...pero cuando amas tu trabajo...simplemente no puedes parar...

En fin...todavia esta en "Beta"...no disponible por el momento, asi que...les voy a mostrar como usarlo como se ve una aplicacion utilizandolo...por supuesto...regrese a mi micro framework favorito de Python...el asombroso Bottle.

Asi que, para este proyecto necesitamos las librerias de Bottle, PyODBC y SQLAlchemy_HANA. Tambien utilice una conexion ODBC con mi servidor SAP HANA en AWS.

Veamos el codigo fuente...

Band_Model.py
from sqlalchemy import Column, Integer, String
from meta import Base


class Singer(Base):
    __tablename__ = 'SINGERS'

    singer_id = Column(Integer, primary_key=True)
    first_name = Column(String(20))
    last_name = Column(String(20))
    band = Column(String(20))

    def __init__(self, singer_id, first_name, last_name, band):
        self.singer_id = singer_id
        self.first_name = first_name
        self.last_name = last_name
        self.band = band

    def __repr__(self):
        return "<Single('{first_name}', '{last_name}'\
                ,'{band}')>".format(**self.__dict__)

Como no tenia ningun idea en la cual basar mi aplicacion, decidi crear algo para organizar a algunos de mis cantantes favoritos de Punk Rock...el primer script Band_Model.py se encargara de crear la tabla SINGERS en SAP HANA.

Band_App.py
from bottle import get, post, request, run, redirect
from meta import Base, engine, Session
from sqlalchemy import *
from sqlalchemy.orm.exc import NoResultFound
from Band_Model import Singer

def connect():
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

@get('/show')
def show_form():
    output = "<div align='center'>"
    output += "<h1>SQLAlchemy on SAP HANA</h1>"
    output += "<a href='/add_singer'>Add Singer</a>"
    output += "<table border=1>"
    singers = Session.query(Singer).all()
    output += "<tr><th>Id</th><th>First Name</th>"\
              "<th>Last Name</th><th>Band</th>"\
              "<th>Update</th><th>Delete</th></tr>"
    for singer in singers:
        update = "/update_singer?singer_id=" + str(singer.singer_id)
        delete = "/delete_singer?singer_id=" + str(singer.singer_id)
        output += "<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td>"\
                  "<td><a href='%s'>Update</a></td><td>"\
                  "<a href='%s'>Delete</a></td></tr>"\
        % (singer.singer_id, singer.first_name, singer.last_name,
           singer.band, update, delete)
    output += "</table></div>"
    return output

@get('/add_singer')
def add_form():
    query = ""
    singer_id = 0
    try:
        query = Session.query(Singer.singer_id).\
                    order_by(desc(Singer.singer_id)).first()
        singer_id = int(query[0]) + 1
    except NoResultFound:
        singer_id = 1
    except TypeError:
        singer_id = 1
    output = "<DIV ALIGN='CENTER'><TABLE>"
    output += "<FORM METHOD='POST'><TR><TD>"
    output += "<INPUT TYPE='HIDDEN' NAME='Singer_Id'"\
              "value='%s'></TD></TR>" % (singer_id)
    output += "<TR><TD>First Name</TD><TD>"
    output += "<INPUT TYPE='TEXT' NAME='First_Name'></TD></TR>"
    output += "<TR><TD>Last Name</TD><TD>"
    output += "<INPUT TYPE='TEXT' NAME='Last_Name'></TD></TR>"
    output += "<TR><TD>Band</TD>"
    output += "<TD><INPUT TYPE='TEXT' NAME='Band'></TD></TR>"
    output += "<TR><TD COLSPAN='2' ALIGN='CENTER'>"
    output += "<INPUT TYPE='SUBMIT' value='Add Singer' NAME='Add_Singer'>"
    output += "<INPUT TYPE='RESET' value='Clear'></TD></TR>"
    output += "</FORM><TABLE></DIV>"
    return output

@post('/add_singer')
def create():
    Singer_Id = request.forms.get('Singer_Id')
    Singer_Id = int(Singer_Id)
    First_Name = request.forms.get('First_Name')
    Last_Name = request.forms.get('Last_Name')
    Band = request.forms.get('Band')
    singer = Singer(Singer_Id, First_Name, Last_Name, Band)
    Session.add(singer)
    Session.commit()
    redirect("/show")

@get('/update_singer')
def update_form():
    query = ""
    singer_id = 0
    singer_id = int(request.query.singer_id)
    query = Session.query(Singer.first_name, Singer.last_name, Singer.band).\
                          filter_by(singer_id=singer_id).first()
    first_name = query[0]
    last_name = query[1]
    band = query[2]
    output = "<DIV ALIGN='CENTER'><TABLE>"
    output += "<FORM METHOD='POST'><TR><TD>"
    output += "<INPUT TYPE='HIDDEN' NAME='Singer_Id'"\
              "value='%s'></TD></TR>" % singer_id
    output += "<TR><TD>First Name</TD><TD>"
    output += "<INPUT TYPE='TEXT' NAME='First_Name'"\
              "value='%s'></TD></TR>" % first_name
    output += "<TR><TD>Last Name</TD><TD>"
    output += "<INPUT TYPE='TEXT' NAME='Last_Name'"\
              "value='%s'></TD></TR>" % last_name
    output += "<TR><TD>Band</TD>"
    output += "<TD><INPUT TYPE='TEXT' NAME='Band'"\
              "value='%s'></TD></TR>" % band
    output += "<TR><TD COLSPAN='2' ALIGN='CENTER'>"
    output += "<INPUT TYPE='SUBMIT' value='Update Singer'"\
              "NAME='Update_Singer'>"
    output += "<INPUT TYPE='RESET' value='Clear'></TD></TR>"
    output += "</FORM><TABLE></DIV>"
    return output

@post('/update_singer')
def update():
    Singer_Id = request.forms.get('Singer_Id')
    Singer_Id = int(Singer_Id)
    First_Name = request.forms.get('First_Name')
    Last_Name = request.forms.get('Last_Name')
    Band = request.forms.get('Band')
    Session.query(Singer).filter_by(singer_id=Singer_Id).\
    update({'first_name': First_Name, 'last_name': Last_Name,
           'band': Band})
    Session.commit()
    redirect("/show")

@get('/delete_singer')
def delete():
    singer_id = 0
    singer_id = int(request.query.singer_id)
    Session.query(Singer).filter_by(singer_id=singer_id).delete()
    Session.commit()
    redirect("/show")

connect()
run(host='localhost', port=8080)


El script Band_App.py se encargara de crear la aplicacion Bottle, agregar nuevos cantantes, modificar los existentes y tambien borrarlos. Como pueden ver...no hay ni un solo SELECT, puesto que SQLAlchemy maneja las tablas como clases y provee metodos para manejar todo. Ahora finalmente entiendo toda la emocion acerca de SQLAlquemy...es realmente impresionante...y facil de usar...luego de que has gastado  15 horas trabajando con el...

Veamos las imagenes...


Por supuesto, la primera vez que ejecutamos la aplicacion, la tabla va a estar vacia...acabamos de crearla...



Podemos insertar nuestro primer registro...o quizas mas para que sea mejor...


Oh...parece que Mr. Ness no tiene una banda...no hay problema...podemos actualizar su registro...


Podemos revisar si todo esta OK o no...


Si, ahora todo se ve bien...sin embargo...tenemos que demostrar que en enlace para borrar tambien funciona...asi que...lastima por los muchachos...


Bueno...eso es todo...me diverti mucho trabajando en esto...y es otra prueba de que SAP HANA es realmente versatil y puede ser usado en cualquier lugar. Hasta la proxima -:)

Saludos,

Blag.

Monday, November 19, 2012

Llamando a Python desde ERP (con PyRFC)

Si leyeron mi blog anterior Volviendo a visitar Python y SAP (con PyRFC)  seguramente recordaran que dije que con PyRFC no solo podemos extraer o ejecutar servicios del lado del ERP, pero tambien podemos utiliar PyRFC como un servidor que puede ser llamado desde el ERP.

En otras palabras, podemos crear una funcion en Python que sera servida por PyRFC y luego sera llamada por un modulo de funciones en el ERP.

En este blog vamos a construir un comparador de XML, el cual va a leer dos archivos XML y nos va a devolver lo que se ha agregado y lo que se ha eliminado.

Ya deberian tener el PyRFC instalado, asi que sigamos adelante con los pasos que debemos seguir para hacer que esto funcione...

Primero, necesitamos obtener la informacion del servidor Gateway, asi que vayamos a la transaccion SMGW y escojamos Goto --> Parameters --> Display. Al final de la tabla, encontran la informacion que necesitamos.


Luego, necesitamos crear una conexion TCP/IP, para que podamos llamar a nuestro servidor PyRFC. Vamos a la transaccion SM59 y creamos lo siguiente.


Ahora, necesitamos crear un archivo que contendra la informacion de nuestra conexion, tanto para el ERP como para nuestro servidor Gateway.



En mis pruebas, utilice el gwhost que viene de la SMGW pero por alguna razon no funciono correctamente...Utilice entonces el valor de ashost...asi que traten con el primero y si no funciona, hagan lo mismo que yo.

Ahora, logeense al ERP y creen una estructura simple llamada ZXML_RESPONSE.


Creen un Table Type utilizando la estructura y llamenlo ZXML_RESPONSE_TT.

Ahora, vamos a la transaccion SE37 y creamos un modulo de funciones llamado ZXML_DIFF. Este FM estara vacio puesto que todo el trabajo se hace del lado de Python.


Por ahora...hemos terminado con el lado del ERP...continuemos al lado de Python...

Necesitamos dos librerias para hacer que esto funciones ListComparator and ElementTree, pueden instalar ambas utilizando easy_install.

El codigo es simple, Python va a recibir dos rutas, una para cada archivo XML, va a leer su contenido, comparar las diferencias y devolvernos lo que ha sido agregado y lo que ha sido eliminado.



PyRFC_XML_Diff.py
from sapnwrfc2 import Server, Connection
from ConfigParser import ConfigParser
from elementtree import ElementTree as ET
from listcomparator.comparator import Comparator

config = ConfigParser()
config.read('sapnwrfc.cfg')


def xml_diff(request_context, XML_1="", XML_2="", ROOT="",
                    ADDITIONS=[], DELETIONS=[]):
    add_list = {}
    del_list = {}
    length = 0
    lower_root = ROOT.encode('utf-8')
    root_old = ET.parse(XML_1).getroot()
    root_new = ET.parse(XML_2).getroot()
    objects_old = root_old.findall(lower_root.lower())
    objects_new = root_new.findall(lower_root.lower())
    objects_old = [ET.tostring(o) for o in objects_old]
    objects_new = [ET.tostring(o) for o in objects_new]
    my_comp = Comparator(objects_old, objects_new)
    my_comp.check()

    for e in my_comp.additions:
        line = e.split("\n")
        length = len(line)
        for i in range(0, length):
            add_list = {}
            add_list.update({"LINE": line[i]})
            ADDITIONS.append(add_list)

    for e in my_comp.deletions:
        line = e.split("\n")
        length = len(line)
        for i in range(0, length):
            del_list = {}
            del_list.update({"LINE": line[i]})
            DELETIONS.append(del_list)

    return {
        'ADDITIONS': ADDITIONS,
        'DELETIONS': DELETIONS
    }

params_connection = config._sections['connection']
conn = Connection(**params_connection)
func_xml_diff = conn.get_function_description("ZXML_DIFF")

params_gateway = config._sections['gateway']
server = Server(**params_gateway)
server.install_function(func_xml_diff, xml_diff)
print "--- Server registration and serving ---"
server.serve(100)

Ahora que ya tenemos todo listo del lado del Servidor de Python...vamos a definir un par de archivos XML para probarlo.




Podemos ver claramente que ambos archivos XML son diferentes...y que el individuo Blag ha recibido un raro aumento en su cantidad...vamos a analizar esto...

Regresemos a la transaccion SE37 y ejecutemos la funcion. Es muy importante llenar el parametro RFC Target sys con el nombre que usamos para nuestra conexion RFC.


El parametro ROOT le dira a nuestra funcion cual es el nodo padre de nuestro archivo XML.

Ahora, ejecutamos la aplicacion PyRFC_XML_Diff.py y veremos esto, que nos va a indicar que nuestro servidor esta listo y funcionando.



Ahora, ejecutamos la funcion y recibiremos nuestra respuesta de Python.


Ahora, podemos analizar las tablas ADDITIONS y DELETIONS...


Podemos ver que algo paso con la cuenta 0001, que no fue agregada, pero uno de sus valores fue modificado. Tambien, en el segundo archivo XML, la cuenta de Kiara fue agregada.


Asi que ahora, todo cae por su propio peso...la cuenta de Blag solia tener una cantidad de 100 y ahora tiene una cantidad de 10,000.

Asi que como pueden ver, solo utilizamos una caparazon en el ERP para llamar a toda la funcionalidad desde Python.

Saludos,

Blag.

Tuesday, November 13, 2012

SAP CodeJam Montreal

Gracias a una iniciativa de Krista Elkin, Jonathan Druker y mia ( con un gran apoyo de Craig Cmehil y Helena Losada ), SAP CodeJam Montreal se va a realizar el Jueves, 13 de Diciembre del 2012 de 3 to 9 pm en las oficinas de SAP Labs Montreal.


Esta es su oportunidad de aprender mas sobre SAP HANA (Tables, Views, Atrtibute Views, Calculation Views, SQLScript y mucho mas), interactuar con personas de diferentes empresas y background y mas importante, divertirte en este evento de 6 horas.

Este es un evento para desarrolladores, asi que deben tener conocimientos de programacion. Pero no se preocupen, no es necesario tener conocimiento previo de SAP HANA.

Pronto, actualizare este blog con el enlace para que puedan registrarse. Y recuerden, tenemos espacios limitados asi que asegurense de inscribirse pronto y rapido. (Ya esta actualizado!)

Luego de que se registren, les enviaremos un email con los detalles sobre lo que tienen que traer para poder disfrutar al maximo este grandioso evemto.


Y de paso, no se olviden de hacer "Like" en la  Pagina de Facebook de CodeJam.

SAP Labs Montreal
111 Rue Duke
Suite 9000
Montreal, Quebec
H3C 2M1
Canada

Nos vemos alli!

Saludos,

Blag.

Saturday, November 03, 2012

Volviendo a visitar Python y SAP (con PyRFC)

Hace unos cuantos dias Srdjan Boskovic escribio un blog llamado Python / ABAP Stack. Esto por supuesto, me trajo memorias puesto que trabaje bastante con sapnwrfc de Piers Harding.

Luego de algunos mails con Srdjan y Jonas Kunze estaba listo y preparado para comenzar a trabajar.

Seguro se preguntaran...porque otro conector para Python y SAP? Simple, mientras que el conector de Pier's es impresionante, carece de algunas caracteristicas que SAP necesitaba para sus desarrollos externos, asi que ellos se tomaron el trabajo de hacer desde cero exactamente lo que necesitaban...una de las maravillas de ser programador...puedes tomar una idea...y convetirla en una aplicacion alucinante o en este caso, un conector RFC...

A manera de introducir el PyRFC, el mejor ejemplo es siempre mostrar algo hecho con las tablas de vuelos...que siempre estan disponibles en cualquier instalacion de ERP y como ya lo habia usado antes en SAP HANA and Python? Yes Sir!, Bottle parecia ser una buena opcion...porque ya no quieren ver mas pantallas de linea de comandos, no?

Primero les mostrare el codigo y luego les comentare sobre algunos de los cambios mas significativos que he podido notar al utilizar PyRFC (Y dicho sea de paso...luche conmigo mismo para hacer el ejemplo aun mejor y tener algo de manejo de errores, que siempre es bueno, inclusive para humildes blogs como este).


Bottle_PyRFC.py
from bottle import get, post, request, run, redirect
from sapnwrfc2 import Connection, ABAPApplicationError, LogonError
from ConfigParser import ConfigParser
 
conn = ""
 
@get('/login')
def login_form():
    return '''<DIV ALIGN='CENTER'><BR><BR><BR><BR>
                <H1>Python (Bottle) & SAP - using PyRFC</H1>
                <BR><TABLE BORDER='1' BORDERCOLOR='BLUE'
                     BGCOLOR='WHITE'>
                <FORM METHOD='POST'>
                <TR><TD>User</TD><TD>
                <INPUT TYPE='TEXT' NAME='User'></TD></TR>
                <TR><TD>Password</TD>
                <TD><INPUT TYPE='PASSWORD' NAME='Passwd'></TD></TR>
                <TR><TD COLSPAN='2' ALIGN='CENTER'>
                <INPUT TYPE='SUBMIT' value='Log In' NAME='LOG_IN'>
                <INPUT TYPE='RESET' value='Clear'></TD></TR>
                </FORM>
                <TABLE>
              </DIV>'''
 
@post('/login')
def login_submit():
    global conn
    try:
        user = request.forms.get('User')
        passwd = request.forms.get('Passwd')
        config = ConfigParser()
        config.read('sapnwrfc.cfg')
        params_connection = config._sections['connection']
        params_connection["user"] = user
        params_connection["passwd"] = passwd
        conn = Connection(**params_connection)
        redirect("/choose")
    except LogonError:
        redirect("/error")
 
@get('/choose')
def choose_table():
    return '''<CENTER>
                <FORM METHOD='POST'>
                <INPUT TYPE='TEXT' NAME='Table'><BR>
                <INPUT TYPE='SUBMIT' value='Show Table'
                 NAME='Show_Table'>
                </FORM>
              </CENTER>'''
 
@get('/error')
def error():
    output = "<div align='center'><h1>Invalid username or password</h1></div>"
    return output
 
@post('/choose')
def show_table():
    global conn
    fields = []
    fields_name = []
    counter = 0
    table = request.forms.get('Table')
    try:
        tables = conn.call("RFC_READ_TABLE", QUERY_TABLE=table, DELIMITER='|')
        data_fields = tables["DATA"]
        data_names = tables["FIELDS"]
        long_fields = len(data_fields)
        long_names = len(data_names)
 
        for line in range(0, long_fields):
            fields.append(data_fields[line]["WA"].strip())
        for line in range(0, long_names):
            fields_name.append(data_names[line]["FIELDNAME"].strip())
 
         output = "<div align='center'><h1>%s</h1></center>" % table
 
        output += "<table border='1'><tr>"
        for line in range(0, long_names):
            field_name = fields_name[line]
            output += "<th bgcolor='#B8D5F5'> %s </th>" % field_name
        output += "</tr>"
        for line in range(0, long_fields):
            counter += 1
            if(counter % 2 == 0):
                output += "<tr bgcolor='#DCE1E5'>"
            else:
                output += "<tr>"
            data_split = fields[line].split("|")
            for line in range(0, long_names):
                output += "<td> %s </td>" % data_split[line]
            output += "</tr>"
        output += "</table>"
 
     except ABAPApplicationError:
        output = "<div align='center'><h1>Table %s was not found</h1></div>" % table
        return output
 
     return output
    conn.close()
 
run(host='localhost', port=8080)


Asi que, para mi PyRFC tiene algunos beneficios resaltantes, como la opcion de atrapar ABAPApplicationError y LoginError (Asumo que la version de Piers tambien lo tenia, pero nunca me preocupe por buscarlo...que verguenza), ademas, la manera de llamar al Modulo de Funcioneses mas limpia, una simple funcion en Python que recive como parametros el nombre del FM y los parametros, quitandonos la necesidad de tener que declarar cada parametro como el atributo de un objeto. Tambien, es bastante veloz y puede ser usado en el lado del servidor...pero hablaremos de eso mas tarde...en otro blog...cuando realmente tenga la oportunidad de trabajar con eso...

Ejecutemos este programa y veamos como se ve...cuando lo ejecutamos desde Python debes ir a tu browser y pasar el siguiente enlace (puesto que estamos ejecutando una aplicacion en Bottle):

http://localhost:8080/login


Como siempre, fue muy divertido trabajar con Python, y por supuesto, al mezclarlo con PyRFC la diversion excedio mis expectativas puesto que ya nos mas de las 8:30 pm en una noche del Sabado...y estoy escribiendo un blog de lo que trabaje casi toda la tarde...la programacion es divertida...no lo olviden...

Saludos,

Blag.

Tuesday, October 30, 2012

Happy SAP HANA Friends

Esta es una presentacion que hice en el Community Theatre en el SAP TechEd Las Vegas 2012.


En esta presentacion, Blagbert ayuda a su amigo Nerdbert a crear su primer proyecto SAP HANA utilizando diferentes tecnologias como:

Python para alimentar SAP HANA con un poco de data aleatoria.
R para consolidar la data.
PowerBuilder para consumir la data y presentarla en un modo grafico.

Si no estuviste en el TechEd en Las Vegas, esta es tu oportunidad de ver mi presentacion.
Si estuviste en el TechEd en Las Vegas, pero no estuviste en mi presentacion...bueno...que verguenza! Fue una presentacion muy buena -:)

Sientanse libre de comentar -;)

Saludos,

Blag.

Monday, August 13, 2012

Rapido caso de uso con SAP HANA y R


AVISO: No soy un experto en SAP HANA o un experto en R expert, ni siquiera un experto en Python. Solo soy un joven con muchas ideas a quien le encanta escribir blogs.

El otro dia estaba pensando acerca de hacerlo algo interesante con SAP HANA y R, porque la gente parece no estar interesada en R aun, y eso es una verdadera pena...porque...R es alucinante...SAP HANA es mas alucinante...asi que juntarlos a los dos es...creo que ya tienen la idea...

Lo primero que se me vino a la cabeza fue una encuesta...nombre, apellido, pais, eded, sexo lenguage de programacion favorito...por supuesto...necesito mucho registros...mas de los que puedo generar a mano...asi que...un script de Python vino al rescate...luego pense...porque no tomo los nombres y apellidos de mis companheros de equipo, paises de origen, invento algunas edaded y otras cosas mas para hacer relleno. El script basicamente va a generar registros aleatorios para insertarlos en SAP HANA.

Vote_Generator.py
import os
import sys
import random
 
names = ["Anne", "Gigi", "Juergen", "Ingo", "Inga", "Alvaro", "Mario", "Julien",
"Mike", "Michael", "Karin", "Rui", "John", "Rocky", "Sebastian", "Kai-Yin",
"Hester", "Katrin", "Uwe", "Vitaliy"]
last_names = ["Hardy", "Read", "Schmerder", "Sauerzapf", "Bereza", "Tejada",
"Herger", "Vayssiere", "Flynn", "Byczkowski", "Schattka",
"Nogueira", "Mayerhofer", "Ongkowidjojo", "Wieczorek", "Gau", "Hilbrecht",
"Staehr", "Kylau", "Rudnytskiy"]
age = [24, 34, 40, 38, 28, 36, 35, 42, 30, 37]
sex = ["M", "F"]
country = ["Germany", "France", "Polland", "Peru", "Russia", "USA", "China",
"Philippines", "Portugal", "Spain"]
vote = ["ABAP", "Node", "Ruby", "Python", "R", "PHP", "ActionScript",
"Euphoria", "Java", "C++"]
 
def Generate_File(pSchema, pNumber):
    iNumber = 0
    pathname = os.path.dirname(sys.argv[0])
    pathname = os.path.abspath(pathname) + "\HANA_File.txt"
    myfile = open(pathname, "a")
    while iNumber < pNumber:
        r_names = random.randrange(0, 20)
        r_lastnames = random.randrange(0, 20)
        r_age = random.randrange(0, 10)
        r_sex = random.randrange(0, 2)
        r_country = random.randrange(0, 10)
        r_vote = random.randrange(0, 10)
        iNumber += 1
        myfile.write("insert into " + pSchema + " values('" +
        names[r_names] + "','" + last_names[r_lastnames] + "'," +
        str(age[r_age]) + ",'" + sex[r_sex] + "','" + country[r_country] +
        "','" + vote[r_vote] + "');\n")
    myfile.close()
    print 'The file ' + pathname + ' was written successfully'
 
schema = raw_input("Schema: \n")
num_files = input("How many records?: \n")
Generate_File(schema, num_files)

La execucion es bastante simple...pasamos el esquema y el numero de registros que queremos generar...para este blog...200,000 registros sonaron como un buen plan.


Como pueden ver...algunos nombre graciosos son generados aleatoriamente, pero despues de todo, somos un equipo asi que no creo que les importe mucho.

El siguiente paso era crear la tabla en SAP HANA y cargar los registros.


El objetivo de nuestro codigo de RHANA es tener informacion detallada sobre cuantos hombres o mujeres de cierta edad, de cierto pais votaron en la encuenta. Algo como CHINA -> 24 -->gt; F --> 890. (890 mujeres de 24 anhos de China votaron en la encuenta). Asi que desde luego, necesitamos crear una tabla para almacenar la informacion.


Con las piezas listas...estamos listos para codificar nuestro script...

HANA_R_Votes.sql
DROP TYPE T_VOTES_DETAIL;
 
CREATE TYPE T_VOTES_DETAIL AS TABLE (
COUNTRY VARCHAR(15),
AGE CHAR(2),
SEX CHAR(1),
FREQUENCY INTEGER
);
 
DROP PROCEDURE CALCULATE_VOTES;
DROP PROCEDURE GET_VOTES;
 
CREATE PROCEDURE CALCULATE_VOTES(IN votes VOTES, out votes_detail T_VOTES_DETAIL)
LANGUAGE RLANG AS
BEGIN
Country<-votes$COUNTRY
Age<-votes$AGE
Sex<-votes$SEX
Votes<-table(Country, Age, Sex)
Stats<-data.frame(Votes)
Stats<-subset(Stats,(Stats$Freq>0))
Stats<-Stats[order(Stats$Country,Stats$Age),]
votes_detail<-data.frame(COUNTRY=Stats$Country,AGE=Stats$Age,SEX=Stats$Sex,FREQUENCY=Stats$Freq)
END;
 
CREATE PROCEDURE GET_VOTES()
LANGUAGE SQLSCRIPT AS
BEGIN
tab_votes = SELECT * FROM VOTES;
CALL CALCULATE_VOTES(:tab_votes, T_VOTES_DETAIL);
INSERT INTO "VOTES_DETAIL" SELECT * FROM :T_VOTES_DETAIL;
END;
 
CALL GET_VOTES();
SELECT * FROM VOTES_DETAIL

Cuando todo esta listo, llamamos al procedimiento GET_VOTES() el cual va a leer los 200,000K registros de SAP HANA, enviarlos a nuestro servidor R para procesar la data y de ahi enviarlos nuevamente a SAP HANA para llenar una tabla con la informacion que queremos generar.


Si...todo el proceso tomo menos de 2 segundos...para 200,000K registros...nada mal...
Ahora podemos seleccionar los datos de nuestra tabla condensada...


Ahora que tenemos los datos que queremos, podemos utilizar las capacidades de analisis de SAP HANA para...analizar nuestros datos...



Debo decirlo...esto fue muy sencillo y rapido...podra no ser el mejor escenario, pero estoy seguro de que les dara una mejor perspectiva de lo que se puede hacer con SAP HANA y R.

Saludos,

Blag.