viernes, 21 de junio de 2024

Python (VI) NO VA BIEN!!! Robocorp (I) Install rcc. Correr el primer ejemplo en el browser

 1. Instalar rcc

En https://github.com/robocorp/rcc?tab=readme-ov-file#installing-rcc-from-the-command-line dice literalmente:

Windows

  1. Open the command prompt
  2. Download: curl -o rcc.exe https://downloads.robocorp.com/rcc/releases/latest/windows64/rcc.exe
  3. Add to system path: Open Start -> Edit the system environment variables
  4. Test: rcc

macOS

Brew cask from Robocorp tap

  1. Update brew: brew update
  2. Install: brew install robocorp/tools/rcc
  3. Test: rcc

Upgrading: brew upgrade rcc

Linux

  1. Open the terminal
  2. Download: curl -o rcc https://downloads.robocorp.com/rcc/releases/latest/linux64/rcc
  3. Make the downloaded file executable: chmod a+x rcc
  4. Add to path: sudo mv rcc /usr/local/bin/
  5. Test: rcc

===================

2. Ejecutar el módulo de ejemplo del principiante

En https://robocorp.com/docs/courses/beginners-course-python/11-creating-pdf hay un fichero tasks.py que copiamos aquí.

from robocorp.tasks import task
from robocorp import browser

from RPA.HTTP import HTTP
from RPA.Excel.Files import Files
from RPA.PDF import PDF

@task
def robot_spare_bin_python():
    """Insert the sales data for the week and export it as a PDF"""
    browser.configure(
        slowmo=100,
    )
    open_the_intranet_website()
    log_in()
    download_excel_file()
    fill_form_with_excel_data()
    collect_results()
    export_as_pdf()
    log_out()

def open_the_intranet_website():
    """Navigates to the given URL"""
    browser.goto("https://robotsparebinindustries.com/")

def log_in():
    """Fills in the login form and clicks the 'Log in' button"""
    page = browser.page()
    page.fill("#username", "maria")
    page.fill("#password", "thoushallnotpass")
    page.click("button:text('Log in')")

def fill_and_submit_sales_form(sales_rep):
    """Fills in the sales data and click the 'Submit' button"""
    page = browser.page()

    page.fill("#firstname", sales_rep["First Name"])
    page.fill("#lastname", sales_rep["Last Name"])
    page.select_option("#salestarget", str(sales_rep["Sales Target"]))
    page.fill("#salesresult", str(sales_rep["Sales"]))
    page.click("text=Submit")

def download_excel_file():
    """Downloads excel file from the given URL"""
    http = HTTP()
    http.download(url="https://robotsparebinindustries.com/SalesData.xlsx", overwrite=True)

def fill_form_with_excel_data():
    """Read data from excel and fill in the sales form"""
    excel = Files()
    excel.open_workbook("SalesData.xlsx")
    worksheet = excel.read_worksheet_as_table("data", header=True)
    excel.close_workbook()

    for row in worksheet:
        fill_and_submit_sales_form(row)

def collect_results():
    """Take a screenshot of the page"""
    page = browser.page()
    page.screenshot(path="output/sales_summary.png")

def export_as_pdf():
    """Export the data to a pdf file"""
    page = browser.page()
    sales_results_html = page.locator("#sales-results").inner_html()

    pdf = PDF()
    pdf.html_to_pdf(sales_results_html, "output/sales_results.pdf")

def log_out():
    """Presses the 'Log out' button"""
    page = browser.page()
    page.click("text=Log out")

Para ello creamos una carpeta por ejemplo ~/MyPython/prova06_rcc

Copiamos el fichero anterior (tasks.py) en dicha carpeta

Copiamos el siguiente fichero conda.yaml que sale si hemos completado el tutorial indicado en dicha página

# For more details on the format and content:
# https://github.com/robocorp/rcc/blob/master/docs/recipes.md#what-is-in-condayaml
# Tip: Adding a link to the release notes of the packages helps maintenance and security.

channels:
- conda-forge

dependencies:
- python=3.10.12 # https://pyreadiness.org/3.10
- pip=23.2.1 # https://pip.pypa.io/en/stable/news
- robocorp-truststore=0.8.0 # https://pypi.org/project/robocorp-truststore/
- pip:
- rpaframework==28.0.0 # https://rpaframework.org/releasenotes.html
- robocorp==1.4.0 # https://pypi.org/project/robocorp
- robocorp-browser==2.2.1 # https://pypi.org/project/robocorp-browser

Opcionalmente podemos copiar los ficheros LICENSE y README.md

Copiamos el fichero robot.yaml

# For more details on the format and content:
# https://github.com/robocorp/rcc/blob/master/docs/recipes.md#what-is-in-robotyaml

tasks:
Run Task:
shell: python -m robocorp.tasks run tasks.py

environmentConfigs:
- environment_windows_amd64_freeze.yaml
- environment_linux_amd64_freeze.yaml
- environment_darwin_amd64_freeze.yaml
- conda.yaml

artifactsDir: output

PATH:
- .
PYTHONPATH:
- .
ignoreFiles:
- .gitignore

Nos situamos en dichar carpeta y ejecutamos el programa mediante "rcc run"

# Nos situamos y ejecutamos rcc

cd ~/MyPython/prova06_rcc
rcc run

Y ejecuta todo el proceso




jueves, 20 de junio de 2024

LDAP (I)

1. Obtener los parámetros del servidor LDAP

Ejecutar en Windows:

nslookup -type=srv _ldap._tcp.DOMAIN_NAME

En Linux

host -t srv _ldap._tcp.DOMAIN_NAME


Y nos devuelve els servidor y la dirección IP, que para el caso de windows:

Servidor:  MUNICIPIO-DC-04.edificio.municipio
Address:   XXX.XXX.XXX.XXX

_ldap._tcp.edificio.municipio           SRV service location:
priority = 0 weight = 100 port = 389 svr hostname = municipio-dc-03.edificio.municipio _ldap._tcp.edificio.municipio SRV service location:
priority = 0 weight = 100 port = 389 svr hostname = municipio-dc-04.edificio.municipio
municipio-dc-03.edificio.municipio internet address = XXX.XXX.XXX.XXX municipio-dc-04.edificio.municipio internet address = XXX.XXX.XXX.XXX

Y para linux


_ldap._tcp.edificio.municipio has SRV record 0 100 389 municipio-dc-03.edificio.municipio.
_ldap._tcp.edificio.municipio has SRV record 0 100 389 municipio-dc-03.edificio.municipio.

Y para saber si está activado el TSL

openssl s_client -connect municipio-dc-03.edificio.municipio:389

Y nos devuelve para el caso que  no esté activado el TSL

CONNECTED(00000003)
write:errno=104
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 338 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---

Para ver un usuario como esta en LDAP hay que entrar en el servidor de LDAP y ejecutar este comando

dsquery user -name "Ximo Dante" 

Siendo el name el nombre del usuario y no el nombre de identificación al iniciar una sesión 
Y nos devuelve


"CN=Ximo Dante,OU=DEPARTAMENTO,OU=XX USUARIOS,DC=edificio,DC=poblacion"


martes, 18 de junio de 2024

Python (V) . Importar módulos de otras carpetas. Entornos virtuales python: Crear, activar, desactivar, listar librerias instaladas. requirememts.txt

 0. Introducción


Para poder hacer referencia a módulos que están en otra carpeta, mirar a ver si dicha referencia está en sys.path

para ver lo que hay en el sys.path 

import sys
print(sys.path)

Para añadir una ruta al syspath 

sys.path.append('/home/ximo/miruta')

Para ver como obtenemos las rutas del fichero hasta la raiz del disco:

from pathlib import Path

for path in Path(__file__).parents:
    print(str(path))

Y obtenemos 

/home/ximo/MyPython/prova03_ref_library/lib02
/home/ximo/MyPython/prova03_ref_library
/home/ximo/MyPython
/home/ximo
/home
/


1. Solución 1: Añadir la carpeta raiz al sys.path. Si funciona por línea de comandos.

Supongamos que la librería raiz es root y esta tiene dos hijas lib01 y lib02

Supongamos que lib01 tiene dos hijas lib01_01 y lib01_02 y cada hija tiene un fichero python

Supongamos que lib02 tiene una hija lib02_01

prova03_ref_library
    lib01
        lib01_01
            proga.py
        lib01_02
            progb.py
    lib02
        lib02_01
            progc.py

desde progc.py queremos llamar a los otros progs, para ello se propone buscar la ruta de la carpeta padre del proyecto (a la carpeta prova03_ref_library)  y a partir de ahí hacer los imports como lib01e.carpeta_hijo.modulo. Por ejemplo lib01.lib01_01.proga

Veamos la ruta absuluta de la carpeta que contien a progc.py

/home/ximo/MyPython/prova03_ref_library/lib02/lb02_01

Observamos que hay 2 saltos a carpetas padres para pasar de la carpeta lib02_01 a la prov03_ref_library por tanto la ruta del padre se obtiene con 

Path(__file__).parents[2]

Por tanto si añadimos la expresión anterior al sys.path, ya podemos referenciar los módulos y carpetas que cuelgan de ella.

# progc.py

from pathlib import Path
import sys

path_root2 = Path(__file__).parents[2]
sys.path.append(str(path_root2))

import lib01.lib01_01.proga as proga
import lib01.lib01_02.progb as progb

# O también
#from lib01.lib01_01 import proga as proga
#from lib01.lib01_02 import progb as progb

Ahora no tenemos ningún problema para hacer estas importaciones

OJO: A veces falla si desde otro módulo importamos un módulo que importa otro. Si importamos desde la misma carpeta hay que hacer el "try-except"

# ------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
from pathlib import Path
# ------Metemos tres niveles desde la caprta actual (o) hasta el abuelo (2)
for i in range(3):sys.path.append(str(Path(__file__).parents[i])) from basicutils import xmutils, xmexcel, xmpolish, xmsql, xmodoo try: # if in the same folder then needs a try and import directly from castillautils import xmcastilla except Exception as error: import xmcastilla # ------Fin imprescindible

2. Solución 2: Definir la carpeta raiz en el launch.json. NO FUNCIONA POR LINEA DE COMANDOS

Supongamos el ejemplo anterior

El nuevo launch.json será

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": {"PYTHONPATH":"${workspaceRoot}"}, // modify PYTHONPATH
}
]
}

Y el fichero se simplificaría

import lib01.lib01_01.proga as proga
import lib01.lib01_02.progb as progb

# O también
#from lib01.lib01_01 import proga as proga
#from lib01.lib01_02 import progb as progb

3. Usar entorno virtual python en la configuración

Se puede crear un entorno virtual de python de varias maneras. Una es de la forma tracicional de situarse mendiante una ventana de comandos dentro de la carpeta del proyecto y ejecutar

#Opcion 1: Crearlo en una ruta relativa
python3 -m venv my-venv
#ACTIVARLO
source my-venv/bin/activate

#ACTIVARLO en WINDOWS
my-venv\Scripts\activate.bat

#---------------SI HAY PROBLEMAS DE POR COPIAR UN PROYECTO ANTERIOR
#---------------SE RECOMIENDA TRABAJAR EN RUTAS ABSOLUTAS 
python3 -m venv ~/MyPython/control-presencia/my-venv
#ACTIVARLO
source ~/MyPython/control-presencia/my-venv/bin/activate

o por contra desde vscode darle a Crtl-Shit-p y seleccionar Python: create environment

Una vez creado se puede crear una configuración (launch.json) dándole al botón triangular y dándole al vínculo "create a launch.json"


Y se copia este fichero, donde se especifica una ruta apython. Ojo en windows hay que indicar el nombre de ejecutable (${workspaceRoot}/.venv/bin/python.exe

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"python": "${workspaceRoot}/my-venv/bin/python", //Linux
"python": "${workspaceRoot}/my-venv/Scripts/python.exe", //Windows!!!
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": {"PYTHONPATH":"${workspaceRoot}"}, // modify PYTHONPATH
}
]
}

 Para desactivar el virtual environment

deactivate

Para ver que entorno actual tenemos

echo $VIRTUAL_ENV

4. Ver las librerias instaladas en un entorno virtual. "requirements.txt"

Se puede listar sin detalles, con detalles o incluso crear el requirements.txt

#Primeramente hay qie activar el entorno virtual
venv/bin/activate.sh

#listar sin detalles
pip list

#listar con detalles
pip freeze


#crear requirements.txt
peep freeze > requirements.txt







Python (IV). Crear librerías. Ejecutar las pruebas. Guardarlas en pypi

Ver también CodeCamp pero con cuidado pues no hace falta meter los ficheros __init__.py en cada carpeta!!! O ver tambien Medium 

Para crear una librería hay que realizar los siguientes pasos básicos:

1. Crear la carpeta padre del proyecto y crear un entorno virtual en ella

Creamos por ejemplo la carpeta mymodule, y dentro de ella creamos el entorno virtual virtualenv

#Creamos la carpeta padre y nos situamo dentro de ella
mkdir mymodule
cd mymodule

#Si no tenemos instalado para crear el entorno virtual lo instalamos
pip install virtualenv 

# Creamos el entormo virtual y lo activamos
python -m venv myenv
myenv\Scripts\activate.bat

2. Instal·lar en dicho entorno virtual las librerias wheel, setuptools y twine


#Nos situamos dentro de la carpeta padre e instalamos
pip install setuptools wheel twine


3. Creamos la estructura del proyecto: las subcarpetas ximo01, ximo02 y test

#Creamos las subcarpetas ximo01, ximo02 y test 
mkdir ximo01
mkdir ximo02
mkdir test
cd mymodule

#Si no tenemos instalado para crear el entorno virtual lo instalamos
mymodule/
│
├── mi_paquete/
│   ├── __init__.py
│   ├── subcarpeta1/
│   │   ├── __init__.py
│   │   ├── modulo1.py
│   │   └── modulo2.py
│   ├── subcarpeta2/
│   │   ├── __init__.py
│   │   ├── modulo3.py
│   │   └── modulo4.py
│   └── subcarpeta3/
│       ├── __init__.py
│       ├── modulo5.py
│       └── modulo6.py

4. Crear entorno virtual y activarlo


#Si no tenemos instalado para crear el entorno virtual lo instalamos
pip install virtualenv 

# Creamos el entormo virtual y lo activamos
python -m venv myenv
# lo activamos (windows)
myenv\Scripts\activate.bat
# lo activamos (linux)
source myvenv/bin/activate


3. Crear una carpeta dentro de ximolib01parent con el nombre que queramos que tenga la librería por jemplo ximollib01 

4. Entrar en la carpeta ximolib01parent y crear un entorno virtual de python y activarlo

python3 -m venv myvenv

source myvenv/bin/activate


Ojo: para que el entorno virtual se guarde en a configuración de vscode ver el siguiente post


5. Instalar en dicho entorno las librerías: wheel, setuptools y twine

pip install wheel setuptools twine

6. En la carpeta padre ximolib01parent crear estos ficheros

- setup.py (vacío)

- README.md (inicilamentre vacío, pero se puede dar información de la librería)

7. Situarse en ximolib01 (ximolib01parent/ximolib01) y crear estos ficheros:

 - _init__.py (inicilamente vacío)

- Uno o varios ficheros python (.py) de las funcionalidades a crear

 8. Situarse en ximolib01parent/tests y crear estos ficheros:

 - _init__.py (inicilamente vacío)

- Uno o varios ficheros python (.py) que comiencen con test_ de las funcionalidades a testear

 Veamos la estructura del proyecto

ximolib01parent
   └>setup.py
   └>README.md
   └>myvenv
   │   └>bin
   └>ximolib01
   │   └>__init_.py
   │   └>myfunctions01.py
   │   └>myfunctions02.py
   │   └>myfunctionsNN.py
   └>tests
         └>__init_.py
         └>test_myfun01.py    
         └>test_myfun02.py    

9. Ejemplo

Supongamos que solmente tenemos esta estructura simple de proyecto

ximolib01parent
   └>setup.py
   └>myvenv
   │   └>bin
   └>ximolib01
   │   └>__init_.py
    |    └>ximolib0101
   │    |    └>__init_.py
   │    |    └>formulas.py
    |    └>tests
    |           └>__init_.py
    |           └>test_formulas.py    

Veamos el fichero formulas.py

import math

def area_circle(radius):
    return math.pi * radius ** 2

def area_square(side):
    return side ** 2

def area_rectangle(length, width):
    return length * width

Veamos el contenido del fichero test_formulas.py. Para ello debemos observar que para pasar de la carpeta tests a la carpetaximolib01parent  debemos hacer 2 salto2, por tanto en el pathlib incluimos 2 saltos

import unittest
from pathlib import Path

# ------Imprescindible para poder importar de otras carpetas (de basicutils)
import sys
saltos=2  # Solo hay un salto de la carpeta tests a la carpeta ximolib01parent
from pathlib import Path
for i in range(saltos):sys.path.append(str(Path(__file__).parents[i]))
from ximolib01 import formulas
# ------Fin imprescindible


class TestFigures(unittest.TestCase):
    
    def test_circle_area(self):
        self.assertEqual(circle.circle_area(1), 3.1415926,'wrong circle area') 

    def test_rectangle_area(self):
        self.assert rectangle.rectangle_area(1, 2) == 2

#Execute test
if __name__ == '__main__':
	unittest.main()

Observar en el test que importamos así 

import sys
saltos=2  # Solo hay un salto de la carpeta tests a la carpeta ximolib01parent
from pathlib import Path
for i in range(saltos):sys.path.append(str(Path(__file__).parents[i]))
from ximolib01 import formulas


EJECUCIÓN DE LOS TESTS

Podemos ejecutarlos de 3 maneras:

  1. Mediante el triángulo de vs code
  2. En el terminal de vscode( y comprobando que estamos en el entorno virtual) nos situamos dentro de la carpeta ximolib01 (que es el padre inminente de la carpeta tests) y ejecutamos: 
     python -m unittest tests/test_formula.py
    

  3. En el terminal de vscode( y comprobando que estamos en el entorno virtual) nos situamos dentro de la carpeta ximolib01 (que es el padre inminente de la carpeta tests) y ejecutamos:
     python -m unittest discover -s tests
    

En la tercera opción le pedimos al sistema que busquer TODOS los tests y los ejecute

10. Fichero ximolib01parent/setup.py


from setuptools import setup, find_packages

VERSION = '0.0.1'
DESCRIPTION = 'My first python library'
LONG_DESCRIPTION = 'This is my first library and I am very proud of it'

# Setting up
setup(
# the name must match the folder name 'ximolib01'
name="ximolib01",
version=VERSION,
author="Ximo Dante",
author_email="<ximodane@gmail.com>",
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
packages=find_packages(),
install_requires=['xmltodict'], # add any additional packages that
# needs to be installed along with your package. Eg: 'caer'
test_requires=[],
keywords=['python', 'wsdl'],
classifiers= [
"Development Status :: 3 - Alpha",
"Intended Audience :: Education",
"Programming Language :: Python :: 3",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Operating System :: Linux :: Ubuntu",
]
)


Los parámetros son:

install_requires indica la lista de librerías que se requieren previamente 

test_requires indica la lista de librerías que se requieren previamente solo para realizar los tests


11.Construir (build) la librería

Situarse en ximo01libparent y ejecutar en un shell


python setup.py bdist_wheel

y el fichero con extensión .whl (de wheel) se creará en la carpeta dist

12. Instalar la librería localmente


pip install /path/to/wheelfile.whl

13. Instalarla en https://pypi.org

13.1 Obtener una clave API de https://pypi.org/

Todo parece sencillo hasta que aparece un código QR que hay que leer con el authentictor de google del móvil y ya da un token enorme que hay que guardar

12. Ejecutar el builfd y la instalación

Ojo: utilizar setup está deprecado!!!!

python setup.py sdist bdist_wheel  # Ya hecho antes

twine upload dist/*

Y nos pide el token anterior


Y además nos da una url donde ver la librería

https://pypi.org/project/ximolib01/0.0.1/

13. Instalar la librería

Ya la tenemos disponible en el repositorio pypi para instalar

 pip install ximolib01





miércoles, 12 de junio de 2024

Enviar correos con GMAIL(3). Python y la nueva seguridad de Google

Ver https://developers.google.com/gmail/api/quickstart/python

Repositorio GIT de código google para utilizar esta API


1. Permitir la API (Enable the API)

ir a https://console.cloud.google.com/apis/enableflow?apiid=gmail.googleapis.com y seleccionar el proyecto o crear uno nuevo

Si clicamos donde se indica podemos crear proyectos o modificar uno existente.

Le damos a SIGUIENTE

Y aparece esta pantalla y le damos a HABILITAR para habilitar la API



2. Configurar la pantalla de consentimiento de OAuth

Entrar en https://console.cloud.google.com/iam-admin

Añadir un usuario habilitado y su correo electronico con el botón "+ ADD USERS" y después seleccionarlo en el filtro


3. Autorizar las credenciales para una apliocación de escritorio

Entrar en https://console.cloud.google.com/apis/credentials 

Clicar el boton de "+ CREAR CREDENCIALES" y seleccionar "ID de Cliente de OAuth"


Y aparece esta pantalla donde seleccionamos App de escritorio y le damos un nombre


Y le damos al btón de CREAR

Y aparece esta pantalla



y podemos decargar el JSON y le damos a ceptar

El ficheo JSON es

{
"installed":{
"client_id":"*********************************************.apps.googleusercontent.com",
"project_id":"********-***-******",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"******-********-*****************",
"redirect_uris":["http://localhost"]
}
}

Hay que guardar dicho fichero en el directorio de trabajo con el nombre de "credentials.json"

4. Instalar las librerías de cliente de Google

En nuestro caso nos vamos a la carpeta donde está el entorno de python:

En una ventana de comandos ejecutar:


#1.Cambiamos de directorio.

cd ~/MyOdoo/Control-Presencia #2.Instalamos los paquetes de Google indicando la ruta del ejecutable python en
#  el entorno virtual 
./bin/python -m pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib


5. Crear un programa Python para registrar las credenciales 

Ahora nos vamos al direcotrio de nuestro proyecto y creamos el fichero "quickstart.py".

que contendrá este código modificado por el tema de localización de ficheros dentro de la carpeta de trabajo.

Hay que tener cuidado con la variable SCOPES!!! como veremos mas adelante.

import os.path
import sys

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]


def main():
  """Shows basic usage of the Gmail API.
  Lists the user's Gmail labels.
  """
  creds = None
  # The file token.json stores the user's access and refresh tokens, and is
  # created automatically when the authorization flow completes for the first
  # time.
  LOCAL_PATH= os.path.dirname(os.path.abspath(sys.argv[0])) 
  tokenFile=LOCAL_PATH+"/"+"token.json"
  if os.path.exists(tokenFile):
    creds = Credentials.from_authorized_user_file(tokenFile, SCOPES)
  # If there are no (valid) credentials available, let the user log in.
  if not creds or not creds.valid:
    if creds and creds.expired and creds.refresh_token:
      creds.refresh(Request())
    else:
      credentialsFile=LOCAL_PATH+"/"+"credentials.json"
      flow = InstalledAppFlow.from_client_secrets_file(
          credentialsFile, SCOPES
      )
      creds = flow.run_local_server(port=0)
    # Save the credentials for the next run
    with open(tokenFile, "w") as token:
      token.write(creds.to_json())

  try:
    # Call the Gmail API
    service = build("gmail", "v1", credentials=creds)
    results = service.users().labels().list(userId="me").execute()
    labels = results.get("labels", [])

    if not labels:
      print("No labels found.")
      return
    print("Labels:")
    for label in labels:
      print(label["name"])

  except HttpError as error:
    # TODO(developer) - Handle errors from gmail API.
    print(f"An error occurred: {error}")


if __name__ == "__main__":
  main()


Y cuando lo ejecutmos por primera vez nos pedirá que aceptemos las condiciones etc.. y al final creará un fichero "token.json" en nuestra carpeta de trabajo que permitirá ejecutar el programa sin pedirlo mas veces.

6. Enviar un correo

Si no cambiamos la linea de SCOPES nos dará un error:

googleapi: Error 403: Request had insufficient authentication scopes.

Hay que tener en cuenta que en el programa anterior tenemos en la línea 11:

# If modifying these scopes, delete the file token.json.
SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]

que indica claramente que solo podemos leer correos. Si queremos enviar hay que utiliar otros SCOPES. 

Los scopes vienen dados en https://developers.google.com/gmail/api/auth/scopes 

Para nuestro caso queremos enviar un correo, por tanto podemos cambiar la línea por esta otra

# If modifying these scopes, delete the file token.json.
SCOPES = ["https://www.googleapis.com/auth/gmail.send"]

y tenemos que borrar el fichero "token.json"

Aquí hay un programa simple que verifica si un fichero es mas antiguo de 0,25 dias y si lo es envia un correo a una cuenta.

import os.path as path
import sys
import base64
import time
from email.message import EmailMessage

from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

# If modifying these scopes, delete the file token.json.
SCOPES = ["https://www.googleapis.com/auth/gmail.send"]


'''
Mail Utils
'''

#==============================================================================
# GENERAL
#==============================================================================
def getGMailCredentials()->object:
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    LOCAL_PATH= path.dirname(path.abspath(sys.argv[0])) 
    tokenFile=LOCAL_PATH+"/"+"token.json"
    if path.exists(tokenFile):
        creds = Credentials.from_authorized_user_file(tokenFile, SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            credentialsFile=LOCAL_PATH+"/"+"credentials.json"
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsFile, SCOPES
            )
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open(tokenFile, "w") as token:
            token.write(creds.to_json())

    return creds
        
def sendGMail(senderEmail:str='ximodante@gmail.com', message:str='Prueba', receiversEmail:list[str]=['receiver@gmail.com']):
    """Create and send an email message
    Print the returned  message id
    Returns: Message object, including message id

    Load pre-authorized user credentials from the environment.
    TODO(developer) - See https://developers.google.com/identity
    for guides on implementing OAuth2 for the application.
    """
    #creds, _ = google.auth.default()
    creds=getGMailCredentials()

    try:
        service = build("gmail", "v1", credentials=creds)
        eMessage = EmailMessage()

        eMessage.set_content("This is automated draft mail")

        eMessage["To"] = receiversEmail[0]
        eMessage["From"] = senderEmail
        eMessage["Subject"] = message

        # encoded message
        encoded_message = base64.urlsafe_b64encode(eMessage.as_bytes()).decode()

        create_message = {"raw": encoded_message}
    # pylint: disable=E1101
        send_message = (
            service.users()
            .messages()
            .send(userId="me", body=create_message)
            .execute()
        )
        print(f'Message Id: {send_message["id"]}')
    
    except HttpError as error:
        print(f"An error occurred: {error}")
        send_message = None
    return send_message


#if __name__ == "__main__":
#  gmail_send_message()
# [END gmail_send_message]

   

def myTestCron():
    try:
        senderEmail=sys.args[1]      
        message=sys.args[2]
        receiversEmail=[sys.args[3]]
        filePath=sys.args[4]

        
    except:
        senderEmail='ximodante@gmail.com'  
        message='Ha fallat la importació del padró de OESIA'
        receiversEmail=['receiver@gmail.com'] #a010
        filePath='/mnt/NASDADES_DOCS/A0-003_INFORMATICA/ASS/ASS2024/AS2024-0042_Padro_habitants_policia/PH_ROLAN_ODOO.xlsx'
        #filePath='Z:\\A0-003_INFORMATICA\\ASS\\ASS2024\\AS2024-0042_Padro_habitants_policia\\PH_ROLAN_ODOO.xlsx'

    if path.exists(filePath):
        
        if (time.time() - path.getmtime(filePath)) / (3600  *24) > 1: 
            print('File older than 1 day')
            sendGMail(senderEmail=senderEmail, message=('Fitxer > 1 dia ' + message), receiversEmail=receiversEmail)
    else:
        sendGMail(senderEmail, ('No existeix el fitxer ' + message), receiversEmail)
#------ main
myTestCron()