Los sockets en Python.

logo python

Es común, incluso puede ser habitual, que necesitemos crear una conexión entre dos programas o dispositivos al programar; pues bien, los sockets en Python y otros lenguajes, están pensados para eso. 

Los sockets son una estructura que permite la conexión entre dos aplicaciones, procesos o equipos, (IPC) estableciendo una relación cliente/servidor, que permite el intercambio de datos, incluso entre programas colocados en ordenadores diferentes.

Los sockets, pueden interpretarse como el punto final de una conexión, donde para establecerla será necesario como mínimo:

  • definir la dirección IP local y remota de los ordenadores que participan,
  • el puerto de conexión
  • y el protocolo de transporte de la información.  

La comunicación que se crea tendrá por tanto un carácter bidireccional.

La relación cliente/servidor, también significa que el comportamiento de los socket no es el mismo, en ambos casos, mientras en el cliente es el punto final de una conexión, en el servidor se comporta como una centralita telefónica, que usa sockets clientes y servidor

La fácil manipulación que ofrecen los sockets, los hace prácticamente imprescindibles, si la conexión es multiplataforma, aunque también son ampliamente usados en cualquier tipo de comunicación IPC (Inter-Process Communication), sin que sean los únicos, que existen.

Los tipos de sockets en Python:

Pueden ser de flujo (sock Stream), que emplean el protocolo  de transmisión TCP, o de datagrama (Sock_Dgram), que emplean UDP, para transmitir los datos.

El protocolo TCP (Transmission Control Protocol), difiere  en muchos elementos de UDP( User Datagram Protocol), sin embargo la forma más simplificada de explicar su diferencia, seria decir que los mensajes enviados por TCP llegan a su destino en el mismo orden en que se enviaron y los que se envían por UDP, pueden llegar en un orden diferente a como fueron enviado originalmente, además de que en el segundo no hay una forma de verificar la entrega y que es TCP, quien incorpora el concepto de puerto.

En Python, para expresar los datos de conexión para la familia de direcciones AF_INET, se emplea una lista con dos parámetros (host, puerto).

Donde host es una cadena que representa un nombre de host en notación de dominio de Internet como ‘darling.com ‘ o una dirección IPv4 como ‘100.50.200.50’, y el puerto es un entero.

Crear un socket en Python

Los sockets bajo el método TCP, son una forma común de comunicación en internet, por ejemplo, al interactuar con un navegador.

Cuando se da click sobre una dirección en el buscador, es como si escribiéramos lo siguiente en un socket cliente:

sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sck.connect(("www.mi_pagina", 80))

En este código primero creamos un objeto socket, que es del tipo cliente, al cual pasamos dos argumentos: el tipo de familia de direcciones (AF_INET), y el tipo de socket (sock-stream).

Clarificando un poco esto, aclaro que existen muchas familias de direcciones (F_UNIX, AF_INET6, AF_NETLINK, AF_TIPC, AF_CAN, AF_BLUETOOTH, AF_PACKET, AF_RDS), siendo AF_INET, la más común.
AF significa Address Family. AF_INET que bien pudo llamarse AF_INET4, se refiere a la familia de direcciones del tipo Protocolo de Internet v4 (IPv4), que emplean 32 bits, mientras AF_INET6 se refiere a familia de direccionamiento de IPv6, que utiliza 128 bits.

Volviendo al código, si bien este sería el script en el socket cliente; en el socket servidor ocurre algo más:

serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), 2222))
serversocket.listen(4)

O sea, creamos un objeto con las mismas propiedades del cliente, sin embargo empleamos bin(), para vincularlo a un hostname y un puerto; y finalmente ponerlo a escuchar.

Viendo el detalle; en el primer parámetro de bin(), estamos indicando a que dirección se conectará.

Usamos gethostname() en este caso, para que tome un host visible en la red, fuera de nuestro ordenador, pero podríamos emplear localhost, o 127.0.01, para que se visible solo en nuestra máquina.

Los puertos de socket, el segundo parámetro de bin(), suelen ser puertos con valores de 4 dígitos.

listen(), le indica a la librería del socket la cantidad de solicitudes máximas de conexión que deseamos poner en cola. Hemos escogido 4, que un número aceptable.

Aunque de forma general, esto es lo que ocurre al establecerse una comunicación entre sockets, que será mejor mostrarlo en un ejemplo más amplio, donde abarquemos todos los momentos.

En este ejemplo emplearemos sys(), para atrapar los errores desde el intérprete, que se produzcan en la salida en la salida del código.

SOCKET SERVIDOR

#importamos las librerias
import socket
import sys

# creamos el objeto socket 
objetoSocket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#introducimos en una variable los datos del servidor
datos_servidor = ('localhost', 10000)
# empleamos sys.stderr para imprimir
print >>sys.stderr, 'empezando a levantar %s puerto %s' % datos_servidor

# asociamos el socket con la dirección del servidor, empleando bind() 
objetoSocket_server.bind(datos_servidor)
# para que nuestro socket sea capaz de aceptar la conexión entrante desde el cliente
# y pueda escuchar el mensaje utilizaremos dos métodos: el método accept()
# que nos devuelve una conexión abierta entre el servidor y el cliente, 
# por la cual los datos, se leen y transmiten mediante recv() y sendall() 
# respectivamente y el metodo listen(), que nos indica las cantidad maxima de 
# conexiones aceptadas

objetoSocket_server.listen(1)
while True:
     print >>sys.stderr, 'Esperando para realizar conexión'
     connection, datos_cliente = objetoSocket_server.accept()

# manejamos la conexion
try:
     print >>sys.stderr, 'conectando desde', datos_cliente
     
     while True:
          data = connection.recv(19)
          print >>sys.stderr, 'recibido "%s"' % data
          if data:
               print >>sys.stderr, 'devolviendo mensaje al cliente'
               connection.sendall(data)
          else:
               print >>sys.stderr, 'no hay mas datos', datos_cliente
               break
finally:
     connection.close()

SOCKET CLIENTE

# importamos las librerias
import socket
import sys
#creamos el objeto socket
objetoSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM 

# creamos una variable con los datos del servidor.
datos_servidor = ('localhost', 10000)
# empleamos sys.stderr para imprimir
print >>sys.stderr, 'conectando a %s puerto %s' % datos_servidor

#conectamos
objetoSocket.connect(datos_servidor)
try:
     mensaje = 'Mensaje de prueba'
     print >>sys.stderr, 'enviando "%s"' % mensaje
     '''
     empleamos sendall()para enviar la información y recv(), para   recibirla,
     los parámetros del método sendall son los datos que se van a enviar, y el 
     parámetro de la funcion  recv()m es el número máximo de bytes a aceptar.
     '''
     objetoSocket.sendall(mensaje) # enviando el mensaje
     cantidad_recibida = 0
     cantidad_esperada = len(mensaje) # recibiendo la respuesta
 
     while cantidad_recibida < cantidad_esperada: data = objetoSocket.recv(19) cantidad_recibida += len(data) print >>sys.stderr, 'recibiendo "%s"' % data

    # cerramos  el socket al concluir con el método close().
    finally:
        print >>sys.stderr, 'cerrando socket'
        objetoSocket.close()

Como un último punto en este ejemplo, es bueno añadir que el socket cliente es normalmente usado, solo para un intercambio (o un pequeño número de comunicación secuencial), de modo que una vez terminado connect() es destruido

En esencia, esta es la estructura de una conexión IPC, empleando sockets.

El concepto de los sockets en Python

Resumiendo lo visto hasta aquí, la comunicación del tipo IPC, se ejecuta mejor empleando sockets, los cuales funcionan bajo la estructura cliente servidor.

El socket cliente  tiene como función básica conectarse y solicitar o envíar información, mientras  la labor del socket servidor es crear sockets clientes y escuchar el puerto.

Dicho de otro modo: una vez creado el socket servidor, este crea un par de sockets clientes, desde los que escucha la información.

La función del socket servidor , no es mandar ningún dato o recibirlo ; sino que produce sockets clientes y cada uno de estos es creado en respuesta a algún otro socket cliente que hace connect() al host y al puerto al que el socket servidor está vinculado.

Tan pronto como el servidor crea ese socket cliente, vuelve a escuchar por más conexiones.

Los dos sockets clientes, pueden conversar entre ellos, usando un puerto asignado dinámicamente y que será reciclado cuando la conversación termine.

Más sobre los sockets en Python

Existen alternativas para manipular la conexión de manera mas efectiva; ya que una vez creada  la conexión, el bucle que empleamos anteriormente, puede funcionar de diferentes modos:

  • Crear un hilo para manejar clientsocket,
  • crear un proceso nuevo para manejar clientsocket 
  • Reestructurar el codigo para usar sockets no bloqueantes y usar select para  multiplexar entre el «socket servidor» y cualquier clientsocket  activo.

IPC

Para ejecutar conexiones IPC rápidas entre dos procesos en una misma máquina, lo aconsejable es revisar la memoria compartida o las tuberías (PIPES).

Cuando se usan  sockets  con el tipo de redirección AF_INET, es una opción aconsejable vincular el servidor  con «localhost», ya que provee un atajo alrededor de algunas capas del código de red, que funciona en casi todas las plataformas.

Los verbos send y recv

Los verbos send y recv operan en los buffers de red y están enfocados a manejarlos; y debido a ello, no  están obligados a manejar obligatoriamente, todos los bytes que se les entrega, o se espera qué entreguen.

Esto crea la necesidad de llamarlos nuevamente hasta que el mensaje haya sido tratado por completo.

Como devuelven resultados cuando el buffer se llenado (send) o vaciado(recv); cuando recv() retorna 0 bytes es porque al otro lado se ha cerrado , o está en el proceso de cerrar la conexión y si ya hemos dicho que se recicla después del close(), entonces no recibirás  datos de esta conexión, nunca más.

Si la conexión no se ha interrumpido, nuestro recv() podría esperar eternamente, porque el socket no dirá, cuando no hay más nada por leer.

Por tanto, otra practica a tener en cuenta es saber qué hacer con el limite de la conexión, cerrarla, darle una longitud fija o delimitarla; pero, no hablaré en este articulo -que ya se ha hecho muy largo- de este tema.

Cerrar los sockets

Para Python cuando un socket es eliminado por el recolector de basura, automáticamente llama a close().

Esto elimina la posibilidad de que quede eternamente abierto, aunque confiar en esto es una mala práctica, porque puede no evitar que el socket del otro lado espere indefinidamente, pensando que su pareja solo esta siendo lenta y TCP es un protocolo confiable, que esperará un largo tiempo antes de rendirse con una conexión.

Por ello, lo recomendado es llamar a close(), siempre y cuando no estes usando hilos.

Sockets bloqueantes y no bloqueantes.

Todo lo que hemos visto hasta aquí, es el procedimiento  para un socket bloqueante

En Python,  se usa socket.setblocking(False) para que un socket no sea bloqueante.

Se diferencia en que send()recv()connect() y accept() pueden retornar sin haber hecho nada.

Debido a ello, se necesita leer  el retorno de un modo óptimo. Un buena solución es emplearse la función select()

Usando select.

ready_to_read, ready_to_write, in_error = \
               select.select(
                  potential_readers,
                  potential_writers,
                  potential_errs,
                  timeout)

select() se le pasan tres listas: la primera con todos los sockets que puede intentar leer; la segunda con todos los sockets que puede intentar escribir, y la tercera, qué usualmente se deja vacía, incluye a aquellos a los que se quiera verificar los errores.

Teniendo en cuenta su complejidad, es muy probable que necesites colocar un socket en más de una lista.

Aunque la llamada a select(), se comporta como bloqueante, acepta darle un tiempo de espera largo.

El retorno de la función devuelve igualmente tres listas, que contienen los sockets que son realmente legibles, escribibles y con error. Cada una de estas listas es un subconjunto (posiblemente vacío) de la lista correspondiente que se pasó a select().

Cuando un socket está en la lista retornada legible, eso significa que una llamada a recv() en este socket va a retornar algo, y lo mismo para la lista de escribibles, ese socket será capaz de mandar algo  y eso solo significa que el espacio de salida del buffer de red está disponible.

En caso de que se necesite revisar a un socket servidor, debe ir en la lista de potenciales legibles y si retorna como legible es casi seguro que una llamada a accept() va a funcionar.

Si se posee un nuevo socket para llamar a connect() y conectarlo con otro, debe ir en la lista de potenciales escribibles,  y si retorna como escribible, es que seguramente esta conectado.

select() puede ser útil incluso con sockets bloqueantes, ya que  el socket retorna como leíble cuando hay algo en el buffer, no obstnte vale aclarar que esto no sirve de ayuda con el problema de determinar si el  socket al otro lado culminó la conexión, o solo está ocupado.

Y hasta aquí, espero modestamente, que esto sirva de ayuda a alguien.

Tres cosas necesitan las ovejas para andar en manada, el miedo al perro, la voz de amo y que la de atrás empuje, por miedo a quedarse sola.

Y.

Crear un mapa con Folium en Python

logo python

En este articulo, mostraré como crear un mapa con Folium en Python, por diferentes vías.

Folium es una librería fantástica, que nos permite crear mapas de un modo simple y ágil y que puede ser una magnifica alternativa a basemap.

La forma básica de crear un mapa en Folium es :

import folium
from folium import plugins
import ipywidgets
import pandas as pd
import numpy as np

folium.Map()#aqui llamamos al mapa simple 

Otra forma simple, es crear el mapa, acotando elemento como el zoom, el área,  la escala  y tamaño del mapa y una dirección especifica. Esta ultima mediante su latitud y longitud.

folium.Map(location=[39.473861, -0.360830], zoom_start=80, width=500, height= 600, control_scale=True)

Otros artículos sobre Python

Esta muestra además la escala y se puede controlar el tamaño del mapa

Controlar el tamaño del mapa

También podemos controlar el tamaño del mapa es empleando  el modulo figure() de la librería branca().

from branca.element import Figure

#folium.Map()
map_1= folium.Map(location=[39.473861, -0.360830], zoom_start=80, control_scale=True)
map_1
fig = Figure(width=1000, height=500)
fig.add_child(map_1)
fig

Guardar el mapa

Para guardar el mapa en folium, basta con darle un ruta al  método save()

Tipos de mapas con widgets

Con folium es fácil añadir widgets a los mapas, que los hacen muy útiles y amplian mucho las posibiidades de visualización.

En el ejemplo mostramos diferentes vistas de un mapa, empleando los widgets:

#seleccion de los widgets
select_widgets=ipywidgets.Select( 
    options=['Open Street Map', 'Terrain', 'Watercolor', 'Positron', 'Dark Mater'],
    value = 'Open Street Map',
    description ='Tipos de mapa:',
    disable= False)
def selectMap(maptype):
    if maptype == "Open Street Map":
        display(folium.Map(location=[39.473861, -0.360830], zoom_start=80, control_scale=True))
    if maptype == "Terrain":
        display(folium.Map(location=[-12.055815, -77.0567980], zoom_start=50, control_scale=True))
    if maptype == "Watercolor":
        display(folium.Map(location=[20.0521337, -75.860336], zoom_start=10, tiles='Stamen Watercolor', control_scale=True))
    if maptype == "Positron":
        display(folium.Map(location=[40.410610, -3.490617], zoom_start=10, tiles='CartoDB Positron', control_scale=True))
    if maptype == "Dark Mater":
        display(folium.Map(location=[40.410610, -3.490617], zoom_start=10, tiles='CartoDB Dark_Matter', control_scale=True))

#interactuando entre el widget y la funcion
ipywidgets.interact(selectMap, maptype=select_widgets)

Hay mucho mas que sacar de Folium, como mapas de calor, así que te invito a investigar esta útil librería y sacarle jugo a sus enormes posibilidades.

Espero que esto sirva de ayuda a alguien. Muchas gracias.

Pocos saben lo mucho que debemos saber, para darnos cuenta de lo poco que sabemos

R. Tagore

Error EOFError en Python

python error

El error EOFError, es un tipo de error que suele aparecer con cierta regularidad en Python y que podemos manipular con excepciones.

Se genera cuando una de las funciones integradas input () o raw_input () alcanza una condición de fin de archivo (EOF) sin leer ningún dato.

Esto ocurre cuando le hemos pedido al usuario una entrada pero no hemos proporcionado ninguna entrada en el cuadro de entrada.

Este error, a veces se experimenta al usar IDE en línea.

Podemos solucionar este problema usando try y except, para manipular la excepción.

Podemos solucionar este problema usando try y except, para manipular la excepción.

Un código como este, arrojará un error del  tipo EOFerror

Podemos manejarlo de la siguiente forma:

n = int(input())
print(n * 10)

Podemos manejarlo de la siguiente forma:

try:
    n = int(input())
    print(n * 10)
    
except EOFError as e:
    print(e)

Lee sobre otros errores de Python en este blog

Hay que sembrar un árbol, un ansia, un sueño, un hijo, porque la vida es eso , sembrar, sembrar, sembrar

J. A. Buesa

NameError: name exit is not defined

python error

En este post veremos el NameError: name exit is not defined, en  Python que cómo indica es un error de nombre, y que podemos solucionar con cierta facilidad.

Ver más errores en Python en este blog

La solución a esto es importar sys y luego importar exit.

Seria algo así:

from sys
import exit
exit()

También puede intentar esta vía

import sys
sys.exit()

Y esto es todo, debería funcionar.

Espero sinceramente que este post, sirva de ayuda  a alguien y muchas gracias.

Entrena duro y en silencio, que el éxito sea tu grito

D.

KeyError en Python

python error

En este post analizaremos cómo se origina  el KeyError, en Python y como surge esta excepción.

El KeyError se genera, cuando se accede a una clave (key) no válida dentro de un diccionario (dict).

Veamos primero la jerarquía general de  las Exceptions Class  de Python.

Resumiendo

Todas las excepciones de Python heredan de la clase BaseException o se extienden desde una clase heredada en la misma.

La jerarquía de excepciones completa de este error es:

BaseException

¿Cuándo debería usarlo?

Dado que IndexError trata con listas y KeyError trata con dictados, deberíamos explorar brevemente la diferencia entre estas dos estructuras de datos comunes en Python. Las listas de Python son similares a las matrices en la mayoría de los otros lenguajes de programación. Es una colección ordenada de objetos que se asignan en un índice numérico incremental para identificar cada elemento.

Las listas se usan comúnmente como pilas, lo que permite el comportamiento de «primero en entrar, último en salir».

Los dictados, por otro lado, se conocen como matrices asociativas en la mayoría de los otros lenguajes. Un dictado también es una colección de objetos, pero no está ordenado, y en lugar de usar índices numéricos, un dictado usa tipos de datos inmutables como claves.

Ahora te será fácil entender que siempre que veas una referencia a los pares clave: valor en Python, esto es una indicación de que la colección que contiene esos pares es un dict o diccionario.

La comprensión del error es muy simple, si creamos un diccionario e intentamos acceder a una clave que no existe, se generará un KeyError

Manejar la excepción Index Error: Index Out of Range

python error

Hablemos de cómo manejar una excepción Index Error: Index Out of Range en Python

Una solución para manejar este error de modo inesperado y prevenir que sea lanzado, es emplear un bloque try-except para encontrarlo y controlarlo.

La idea es que si ocurre una excepción durante la ejecución de nuestro codigo, la excepción quedará atrapada en el bloque except.

Ejemplo

Import sys
Try:
list =(‘a’,2,3,5,6,’b’,8)

print(lista[8])
except IndexError as e:
print(e)
print(sys.exc_info())

Una vez en este punto podrás manejar el error y lo que nos imprime.

…. no recuerdo ya el lugar de donde vengo y pueda que no exista el sitio adonde voy..

J. Sabina

Resultado incorrecto al dividir números grandes en Python3.

python error

Si te ha sucedido ya, te explico porque obtienes un resultado incorrecto al dividir números grandes en Python 3.

 Es cierto que Python 3 da errores en el resultado cuando divides dos números muy largos, empleando el símbolo /.

Esto se debe a que en Python 3.x  el símbolo / significa división de punto flotante y con números muy grandes, puede dar pequeños errores de redondeo.

Lo correcto es usar // para  la división de enteros.

Por ejemplo:

d = a // (b*c)

Espero haber ayudado a alguien.

Muchas gracias

El que siempre soñó, tiene derecho a ganar

I.Delgado

Ver el tiempo de carga de nuestra página Prestashop

logo prestashop

Entre las muchas cosas que se pueden hacer utilizando el modo DEBUG, podemos ver el tiempo de carga de nuestra página Prestashop.

Puedes ver acá como activar o desactivar el método DEBUG.

Para ver en el Backend el tiempo de carga, debemos activar la opción ‘_PS_DEBUG_PROFILING_’

Esta  muestra en detalle el rendimiento de nuestra tienda.

Para acceder a ellos debemos seguir la ruta: directorio/config/defines.inc.php”, siendo directorio, aquel donde se ha instalado tienda.

Una vez dentro cambiamos la linea

define(‘_PS_DEBUG_PROFILING_’, false);

por

define(‘_PS_DEBUG_PROFILING_’, true);

Una vez guardado, actualizamos la pagina y ahora podemos ver los tiempos de carga, los tiempos en que se ejecutan las consultas y más información de valor sobre el comportamiento de la web.

Subir montañas, hermana hombres….

J. Martí

Solucionar “Trying to get property of non-object» en Prestashop

error prestashop

Hola, es muy probable que estes recibiendo el error “Trying to get property of non-object» en tu página Prestashop.

Si tienes un ecommerce con actualización automática de los productos desde la web del  proveedor, este error puede surgir en cualquier momento.

Veamos este error esta diciendo que estas tratando de «tomar una propiedad desde algo que no es un objeto» y eso debe ser revisado.

Recibes este error porque tienes activado el modo debug.

Si tu página se ve y funciona bien, es muy probable que puedas obviar este warning y otros de su tipo.

Por eso, si no quieres sumirte en el código, basta con desactivar el modo DEBUG, de tu pagina para no recibir más el error.

MODO DEBUG ¿que es PRESTASHOP?

El Modo DEBUG en PrestaShop,  es una  variable con la cual se puede activar o desactivar el modo de depuración.

Mediante ella, podemos visualizar todos los  mensajes de depuración y avisos PHP, que están diseñados para que  el desarrollador pueda detectar  errores, que de otro modo no veria.

En este link puedes escribirme si necesitas algún tipo de colaboración Contacto

Desactivar el modo DEBUG

Para desactivar el modo DEBUG, debemos acceder por FTP a nuestra tienda y modificar el archivo “defines.inc.php”.

Su ubicación es “directorio/config/defines.inc.php”.

En este caso directorio es el directorio raíz donde hemos instalado PrestaShop.

La variable “_PS_MODE_DEV_,  es la que que activa el modo DEBUG,  y posee dos valores “TRUE= activado” y “FALSE= desactivado”

Una vez dentro  solo deberemos cambiar la linea:

define(‘_PS_MODE_DEV_’, true);

y cambiar el valor de True a False:

define(‘_PS_MODE_DEV_’, false);

El modo DEBUG, dejara de estar activo y los errores dejaran de llegar.

Sabiendo esto ya puedes activar o desactivar el modo DEBUG a tu voluntad para evaluar errores.

Fue tu amor, más difícil y más fallo, que un contrabando de gallos, pasando al amanecer.

W. Chirino

Index Error: Index Out of Range en Python

python error

El error en Python, Index Error: Index Out of Range, extiende de la clase LookupError, que se hereda de la BaseException, que viene predefinida en el lenguaje.

Dentro de la clase LookupError, se enumeran dos errores:

BaseException
  LookupError
    IndexError
      KeyError

¿Qué significa el error de Python, Index Error: Index Out of Range?

Como Python admite la indexación de elementos de lista, esta preparado para acceder a elementos iterables y operar con ellos, realizando acciones como imprimir o recorrer elementos.

El problema viene cuando se menciona un índice en su código que está fuera del rango de la lista, entonces  Python arrojará un IndexError que le indicará que el índice de la lista está fuera del rango.

Por ejemplo cuando intentamos acceder a un elemento usando un índice que está fuera del rango de esa lista, recibirá un error de  Index Error: Index Out of Range.

En en el siguiente caso, imprimir la posición (lista [8]) se refiere a una posición que no existe que no existe, ya que la primera posición de cualquier arreglo es 0, por tanto si la lista tiene 7 registros, la posición más alta es [6]

lista = (‘a’,2,3,5,6,’b’,8)
print(lista[8])

Si estas recibiendo este error basta con que te asegures que estas pidiendo una posición dentro del rango correcto.

Puedes ver mas como manejar este error aquí

…., si avanzo sígueme, si me detengo empújame, si retrocedo mátame…….

F. País
Translate »