jueves, 3 de octubre de 2024

Abrir varias sesiones de Visual Studio Code (VsCode)

Una vez hemos abierto el programa vscode tecleamos Shift-Control-N 

Remina (RDP). Enviar el código Crt-Alt-Supr

 En supersuser.com  nos indica las instrucciones para llamar a crt-Alt-Supr desde una session Remina en Windows:

  1. Ejecutar el comando osk.exe para mostrar un teclado en pantalla
  2. Ahora con el teclado real (mecánico) apretamos Crt y Alt
  3. Y al mismo tiempo con el teclado virtual de pantalla, con el ratón marcamos Supr


martes, 1 de octubre de 2024

Python(X): Obtener la ruta del fichero actual que se ejecuta

Según stackoverflow:

Ruta del fichero actual ejecutándose:

os.path.abspath(getsourcefile(lambda:0))

Ruta del directorio que contiene a dicho fichero

os.path.dirname(getsourcefile(lambda:0))



 

martes, 17 de septiembre de 2024

Fast HTML. @no_type_check decorator

 Para que no de problemas, hay que crear un entorno virtual que no comparta con nadie, pues si se aprovecha un entorno virtual existente, algunas librerías pueden dar problemas de compatibilidad.

A partir de quí, se siguen las instrucciones de  https://docs.fastht.ml/tutorials/quickstart_for_web_devs.html

Hay que instalar en el entorno virtual la librería

pip install python-fasthtml


0.Introducción

Crear una aplicación y servirla: Hace falta hacer el import de fasthtml, hacer un get, devolver código html y "servirla"

from fasthtml.common import FastHTML, serve

app = FastHTML()

@app.get("/")
def home():
    return "<h1>Hello, World</h1>"

serve()


Crear código html: FastHtml tiene funciones para crear "div", "p" etc, y además puede utilizar marcas del framework css "pico". Solo se muestra como construir el html

page = Html(
    Head(Title('Some page')),
    Body(Div('Some text, ', A('A link', href='https://example.com'), Img(src="https://placehold.co/200"), cls='myclass')))
print(to_xml(page))
return page


Routing: Se definen de 3 maneras:

  1. Como @app.route('/ruta01', methods=['get','post']) más la función que devuelve el html de esta ruta
  2. Como @app.get('/ruta02') más la función que devuelve el html
  3. Como @rt('/ruta03') mas una función que se llama igual que el método (get(), post(), delete() ...

@app.route("/", methods='get')
def home():
    return H1('Hello, World')

@app.get("/")
def my_function():
    return "Hello World from a GET request"

rt = app.route
@rt("/")
def post():
    return "Hello World from a POST request"

client.post("/").text


Styling: Para que todos los "headers" tengan el mismo estilo, se añade la opción "hdrs" cuando llamamos a FastHTML(). También podemos añadir otras librerías como Flexbox

from fasthtml.common import *
css = Style(':root {--pico-font-size:90%,--pico-font-family: Pacifico, cursive;}')
flexbotlink = Link(rel="stylesheet", href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css", type="text/css")
app = FastHTML(hdrs=(picolink, css, flexbotlink))


Testing: Usar scarlette.testclient para ver el html generado

from starlette.testclient import TestClient
client = TestClient(app)
r = client.get("/")
print(r.text)


1. htmx

OJO: Sin htmx devolvemos tuplas de html, pero con htmx se devolverán todos el código!!!!

Veamos este ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
app = FastHTML()

count = 0

@app.get("/")
def home():
    return Title("Count Demo"), Main(
        H1("Count Demo"),
        P(f"Count is set to {count}", id="count"),
        Button("Increment", hx_post="/increment", hx_target="#count", hx_swap="innerHTML")
    )

@app.post("/increment")
def increment():
    print("incrementing")
    global count
    count += 1
    return f"Count is set to {count}"

serve()

Línea 10: El botón hace una petición tipo post (hx_post) a "/increment", cuyo resultado se aplica al elemento (hx_target) con id="count" (que en este caso es el elemento "p" de la línea anterior (9). Pero con hx_swap="innerHTML", lo que hacemos es substituir el html interno del elemento por el contenido que se devuelve en la llamada por post

Línea 13 a18: Fúnción de llamada a post que devuelve el contenido a asignar al elemento "p" de la línea 10.


2. Todo app

Nos mostraria esta pantalla:



Veamos el código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#1. Import fasthtml
from fasthtml.common import * 
from typing import no_type_check # To avoid "Variable not allowed in a type expression"
#2. Define the todo element's render function def todoRender(todo): tid_=f'todo-{todo.id}' toggle = A( Mark('Toggle'), hx_get=f'/toggle/{todo.id}', target_id=tid_) delete = A( Mark('Delete'), hx_delete=f'/{todo.id}', hx_swap="outerHTML", target_id=tid_ ) return Li( toggle, ' ', delete, ' ', ' '+todo.title+ ' '+str(todo.id)+(' ✅' if todo.done else ''), id=tid_ )
#3. Create a FastHTML app
@no_type_check
app, rt, todos, Todo = fast_app( 'todos.db', live=True, render=todoRender, id= int, title=str, done=bool, pk='id' ) #4. Define the input element def mk_input(): return Input( placeholder="What needs to be done?", id='title', hx_swap_oob='true', ) #5. Main form @rt("/") def get(): frm= Form( Group( mk_input(), Button("Add") ), hx_post='/', target_id='todo-list', hx_swap='beforeend' ) return Titled( 'Todos', Card( Ul(*todos(), id='todo-list'), header=frm ) ) #6. Toggle element @rt("/toggle/{tid}") def get(tid:int): todo=todos[tid] todo.done = not todo.done return todos.update(todo) #7. Delete element @rt("/{tid}") def delete(tid:int): todos.delete(tid) #8. Add element @rt("/") def post(todo:Todo): return todos.insert(todo), mk_input() #9. Run the application on the server serve()

Línea 27: En vez de llamar a FastHTML() utilizamos fast_app() y devolvemos:

  • app, rt: para routing
  • todo: que es una "matriz" de elementos (no confundir con tupla, dictionary o list)
  • Todo: que es el tipo de elemento. OJO: VS se queja diciendo "Variable not allowed in a type expression", però al executar no dona cap problema. El motiu és que "pylance" no puede averiguar ese tipo pues se genera dinámicamente. 

Los parámetros de llamada son:

  • 'todos.db' que es el nombre de una base de datos sqlite que se guarda en la misma carpeta. Se podría poner 'data/todo.db' para que lo gardara en la subacarpeta data
  • live=True para que recargue el navegador la página cuando hay un cambio. Solo para desarrollo y no para producción
  • render=todoRender que es la función de renderizado de cada elemento "todo"
  • id,title,done,pk: son elementos a tener en cuenta a la hora de renderizar.

Línea 5: Función de renderizado, a la que se indican vínculos a acciones (get para toggle y delete para delete) que se aplican al target_id. Se crea una lista cuyos elementos tienen un "id" que utilian los vínculos para actuar sobre ellos.

Para obtener el símbolo  buscamoe en google "check button emoji"  que nos lleva a https://emojipedia.org/check-mark-button donde podemos copiar el elemento

Línea 68: Cuidado con el manejo de la matriz "todos". No podemos hacer:

todos[tid].done = not todos[tid].done

Linea 38: Cremos una función que la llamamos para construir cada vez el "input" del título de elemento todo, así aseguramos que borramos su contenido cuando hacemos una alta (botón add)

Línea 42hx_swap_oob='true' indica que se cambiara todo el elemento indicado en el id

Línea 55: hx_swap='beforeend' indica que se añade la respuestaal final

Línea 55: hx_swap='outerHTML' indica que cambia todo el elemento con la respuesta


3. Usando Flexbox

Vemos un ejemplo de como se utiliza:


#1. Import flexbox
#2. Create a parent Div with 3 childre:
#   the first has the 12 cells and the next 2 children with 6 columns each 
grid = Html(
    Link(rel="stylesheet", href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css", type="text/css"),
    Div(
        Div(Div("This takes up the full width", cls="box", style="background-color: #800000;"), cls="col-xs-12"),
        Div(Div("This takes up half", cls="box", style="background-color: #008000;"), cls="col-xs-6"),
Div(Div("This takes up half", cls="box", style="background-color: #0000B0;"), cls="col-xs-6"), cls="row", style="color: #fff;" ) ) show(grid)


4. Trabajando routing y request parameters: Sesión, path parameters, regex (reg_re_param fuction),  types and enums, @no_type_check, casting Path, integers with default value, boolean values, dates, dataclasses, web sockets, cookies..

El parámetro "session" del enrutamiento es un pequeño diccionario donde guardar datos


from fasthtml.common import *
from starlette.testclient import TestClient


app = FastHTML()
cli = TestClient(app)

#-------------------------------
# 0. Session
#-------------------------------
import uuid
@app.get("/")
def get(session):
    if 'session_id' not in session: session['session_id'] = str(uuid.uuid4())
    return H1(f"Session ID: {session['session_id']}")
print(cli.get('/').text)

#-------------------------------
# 1. Path parameters
#-------------------------------
@app.get('/user/{nm}')
def _(nm:str): 
	return f"Good day to you, {nm}!"

print(cli.get('/user/jph').text)

#-------------------------------
# 2. Regex
# Registering a new URL converter 
# with a specified name and regular expression pattern.
#-------------------------------
reg_re_param("imgext", "ico|gif|jpg|jpeg|webm")

# {fn} is the basename and {ext} is the extension
@app.get(r'/static/{path:path}{fn}.{ext:imgext}')
def get_img(fn:str, path:str, ext:str): 
	return f"Getting {fn}.{ext} from /{path}"

print(cli.get('/static/foo/jph.ico').text)

#-------------------------------
# 3. Using enum
#-------------------------------
# str_enum is a helper function that returns an enum type
ModelName = str_enum('ModelName', "alexnet", "resnet", "lenet")

from typing import no_type_check
@app.get("/models/{nm}")
@no_type_check # Avoid "Variable not allowed in type expression"
# Variable not allowed in type expression Pylancereport InvalidTypeForm
def model(nm:ModelName): return nm

print(cli.get('/models/alexnet').text)

#-------------------------------
# 4. Casting to a Path:
#-------------------------------
@app.get("/files/{path}")
def txt(path: Path): return path.with_suffix('.txt')

print(cli.get('/files/foo').text) 

#-------------------------------
# 5. An integer with a default value:
#-------------------------------
fake_db = [{"name": "Foo"}, {"name": "Bar"}]

@app.get("/items/")
def read_item(idx:int|None = 0): 
	return fake_db[idx]

print(cli.get('/items/?idx=1').text)

#-------------------------------
# 6. Boolean values (takes anything “truthy” or “falsy”):
#-------------------------------
@app.get("/booly/")
def booly(coming:bool=True): 
	return 'Coming' if coming else 'Not coming'

print(cli.get('/booly/?coming=true').text)
print(cli.get('/booly/?coming=no').text)

#-------------------------------
# 7.Getting dates:
#-------------------------------
@app.get("/datie/")
def datie(d:parsed_date): 
	return d

date_str = "17th of May, 2024, 2p"
print(cli.get(f'/datie/?d={date_str}').text)

#-------------------------------
# 8. Matching a dataclass:
# @dataclass decorator add a __init__ method internally
# @ see https://docs.python.org/3/library/dataclasses.html
# assdict converts a class to a dictionary
#-------------------------------
from dataclasses import dataclass, asdict

@dataclass
class Bodie:
    a:int;b:str

@app.route("/bodie/{nm}")
def post(nm:str, data:Bodie):
    res = asdict(data) # convert to dict
    res['nm'] = nm # add a key and value to the dict
    return res

print(cli.post('/bodie/me', data=dict(a=1, b='foo')).text)

#-------------------------------
# 9. Cookies. Set values
#-------------------------------
from datetime import datetime

@app.get("/setcookie")
def setc(req):
    now = datetime.now()
    res = Response(f'Set to {now}')
    res.set_cookie('now_cookie', str(now))
    return res

print(cli.get('/setcookie').text)

#-------------------------------
# 10. Cookies. Get values
#-------------------------------
@app.get("/getcookie")
def getc(now_cookie:parsed_date): return f'Cookie (now_cookie) was set at time {now_cookie.time()}'

print(cli.get('/getcookie').text)

#-------------------------------
# 11. User Agent
#-------------------------------
@app.get("/ua")
#async def ua(user_agent:str): return user_agent
async def ua(user_agentin:str): return user_agentin

print(cli.get('/ua', headers={'User-Agent':'FastHTML', 'User-Agentin':'Yo mismo'}).text)

#-------------------------------
#12. Scarlett Requests
#-------------------------------
@app.get("/form")
async def form(request:Request):
    form_data = await request.form()
    a = form_data.get('a')
    
@app.get("/redirect")
def redirect():
    return RedirectResponse(url="/")	

#-------------------------------
# 13. Static files
# For images, CSS, etc.
#-------------------------------
@app.get("/{fname:path}.{ext:static}")
def static(fname: str, ext: str):
  return FileResponse(f'{fname}.{ext}')

#-------------------------------
# 14. Web Sockets
#-------------------------------
from asyncio import sleep
app = FastHTML(ws_hdr=True)
rt = app.route

def mk_inp(): return Div('Introduir'),Input(id='msg')

@rt('/')
async def get(request):
    cts = Div(
        Div('Comencem',id='notifications'),
        Form(mk_inp(), id='form', ws_send=True),
        hx_ext='ws', ws_connect='/ws')
    return Titled('Websocket Test', cts)

#@app.ws('/ws')
#async def ws(msg:str, send):
#    await send(Div('Hello ' + msg, id="notifications"))
#    await sleep(2)
#    return Div('Goodbye ' + msg, id="notifications"), mk_inp()

# -----more websocket
async def on_connect(send):
    print('Connected!')
    await send(Div('Hello, you have connected', id="notifications"))

async def on_disconnect(ws):
    print('Disconnected!')

@app.ws('/ws', conn=on_connect, disconn=on_disconnect)
async def ws(msg:str, send):
    await send(Div('Hello ' + msg, id="notifications"))
    await sleep(2)
    return Div('Goodbye ' + msg, id="notifications"), mk_inp()

#-------------------------------

serve()






lunes, 16 de septiembre de 2024

Instalar Gitlab CE en debian

1. Instalación inicial 

Seguimos a https://www.linuxtechi.com/how-to-install-gitlab-on-debian/?utm_content=cmp-true


# 1) Update the System
$ sudo apt update

# 1) Install GitLab dependencies
$ sudo apt install wget ca-certificates curl apt-transport-https gnupg2 -y #3) Install Gitlab on Debian 12 $ curl -s https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash $ sudo apt install gitlab-ce -y

Comprobar que funciona y apuntarse la contraseña de "root". Para ello entrar en 

http://192.168.xxx.xxx 

y dar la contraseña de root y un correo para ello marcar el símbolo mostrado:


Seleccionar Edit profile:




Y ahí comprobamos el email y la contraseña.


2. Obtener un certificado SSL.

Parece ser que gitlab quiere una clave (.key) y un certificado (.crt) cuyo nombre sea el mismo que el dominio de la máquina, por ejemplo "gitlab.municipio.es"

2.1 Opción 1: Partimos de un certificado con extension p12

Para ello, si partimos de un certificado "wildcard" en formato p12 seguiremos las instrucciones de https://www.ssl.com/how-to/export-certificates-private-key-from-pkcs12-file-with-openssl/

Veamos como obtenemos la clave privada (.key) y el certificado (.crt) cuyo nombre sea del dominio


# 1) Get only private key with domain name
$ openssl pkcs12 -in crt_wildcard.p12 -out gitlab.municipio.es.key -nodes -nocerts

# 1) Get only certificate with domain name
$ openssl pkcs12 -in crt_wildcard.p12 -out gitlab.municipio.es.crt -nokeys


2.2 Opción 2: Nos creamos un certificado self-signed (autofirmado)

Para ello, si partimos de un certificado "wildcard" en formato p12 seguiremos las instrucciones de https://medium.com/@gengchao77977/configure-self-signed-ssl-certificate-on-gitlab-58cc8e8cf3fa


# 1) Create the root CA key
$ sudo openssl genrsa -out ca.key 4096

# 2) Create the root CA certificate
$ sudo openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 -out ca.crt -subj '/CN=gitlab.municipio.es' #3) Create a certificate signing request (CSR) $ sudo openssl req -new -nodes -out gitlab.municipio.es.csr -newkey rsa:4096 -keyout gitlab.municipio.es.key -subj '/CN=gitlab.municipio.es'
#4) Create a certificate configuration file. Create a file named "run.extfile" with this content basicConstraints=critical,CA:TRUE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = yourdomain.com #5) Create a Gitlab certificate $ sudo openssl x509 -req -in gitlab.municipio.es.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out gitlab.municipio.es.crt -days 365 -sha256 -extfile run.extfile


3. Instalar el certificado en gitlab.

Modificamos el fichero /etc/gitlab/gitlab.rb y añadimos estas líneas

#----------------------------------------
#6.1 Edit the /etc/gitlab/gitlab.rb as follows
#----------------------------------------
# your GitLab instance URL
external_url "https://yourdomain.com"

# auto http -> https
nginx['redirect_http_to_https'] = true

# please note to disable letsencrypt
letsencrypt['enable'] = false
#----------------------------------------

Crer un directorio por ejemplo ssl cuya ruta absoluta será /etc/gitlab/ssl y copiamos la clave privada (gitlab.municipio.es.key) y el certificado (gitlab.municipio.es.crt) dentro de esta carpeta.

Cambiamos los permisos de la carpeta y reconfiguramos gitlab. A veces puede requerirse que se vuelva a rearrancar el servicio e incluso a puede ser necesario hcer un "reboot" del servidor

Opcionalmente podemos ver que nos dice del certificado

#6.2 Create the folder in etc/gitlab and copy the certificate
$ sudo mkdir -p /etc/gitlab/ssl
$ sudo chmod 755 /etc/gitlab/ssl
$ sudo cp -f cert_file /etc/gitlab/ssl/
$ sudo gitlab-ctl reconfigure

#6.2.1 Optional: Sometimes it is requiered to restart the gitlab service
$ sudo gitlab-ctl restart
#6.2.2 Optional: Sometimes it is requiered to resboot the server
$ sudo reboot
#6.3 Verify the added certificate $ sudo openssl s_client -showcerts -verify 3 -connect yourdomain.com:443


4. Comprobar que funciona

Con el navegador apuntamos a https://gitlab.municipio.es 


5. Crear un nuevo proyecto y repositorio

Hacer click en el simbolo "+" de la parte superior izquierda y  continuación seleccionmos "New project/repository"



Le damos a"Create blank project"


Damos nombre, namespace/group (root) y "project slug" opcional, y le damos los permisos "Private" y creamos el proyecto


Y nos sale



6. Guardar (Push) una carpeta existente en el repositorio

Como se muestra en la imagen anterior, nos mentemos en la carpeta a guardar y ejecutamos

cd my-python-library
git config --global user.name "Administrator"
git config --global user.email "axxxxxxxx.org" init --initial-branch=main
git remote add origin git@gitlab.xxxx-xx:root/my-python-library.git
git add .
git commit -m "Initial commit"
git push --set-upstream origin main

Ahora ya hemos guardado nuestro proyecto en el gitlab


























miércoles, 28 de agosto de 2024

FreeCAD Resumen

1. Crear un nuevo objeto

En la pestaña por omisión de "DOCUMENTS",  hay un apartado de "Ficheros Recientes" y un poco mas abajo "Crear nuevo .."


Ahora seleccionamos "Sketcher" "Part Design" y "Crear Croquis"


Seleccionamos el plano X-Y.

Para ver la rejilla hay un icono a la deecha de la barra de símbolos para seleccionarla

Antes de empezar a dibujar comprobar que se ha se seleccionado ls dos opciones de "Constraints"

2. Figuras gemétricas planas

Para crear elementos se tiene de izquierda a derecha:


Punto, segmento,arco, circunferencia, elipses (cónicas) , B-Spines, polilíneas, cuadrados, polígonos regulares y ranuras.

Si apretamos el botón derecho sobre el dibujo nos propone las opciones anteriores más comunes:


Pero hay muchas opciones, pore ejmplo a la hora de crear un rectangulo, se puede crear centrado, para ello se marca primero el punto central y luego el punto de un extremo.

2. Modos de trabajo del ratón

En la parte inferir izquierda, por opmisión tengo seleccionado CAD, pero puede haber más modos que se pueden seleccionar con el triangulito de al lado

Y pasando por encima (hovering) podemos ver que hace cada tecla

3. Restricciones

Se entiende por resticciones (constrains) el acotar un lado, establecer longitudes y anchuras, marcar la distancia (horizontal y vertical) entre 2 puntos





2.

martes, 27 de agosto de 2024

Ubuntu 24.04. Instalar FreeCAD. FreeCAD falla. No arranca Ubuntu. "default.target" is not active

Introducción

Se descargo el FreeCAD com aAppImage

Se marcó come ejecutable

y al ejecutar des de una consola dice:

MESA-LOADER: failed to open crocus: /usr/lib/dri/crocus_dri.so: undefined symbol: amdgpu_va_get_start_addr (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri)

failed to load driver: crocus

MESA-LOADER: failed to open swrast: /usr/lib/dri/swrast_dri.so: undefined symbol: amdgpu_va_get_start_addr (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri, suffix _dri)

Y hay un gracioso de un foro que se le ocurre decir que ha instalado "fuse!!!!!". OJO no instalar este "fuse", pues te cambia el entorno gráfico.

Se buscan loas librerias en cuestion dentro de /usb y cuando las encontramos hacemos un link

sudo ln -s /usr/lib/x86_64-linux-gnu/dri/crocus_dri.so /usr/lib/dri/
sudo ln -s /usr/lib/x86_64-linux-gnu/dri/swrast_dri.so /usr/lib/dri/
sudo ln -s /usr/lib/x86_64-linux-gnu/libdrm_amdgpu.so.1 /usr/lib/dri

Y ejecutamos el FreeCAD con esta instrucción desde el directorio donde se ha copiado el FreeCAD

LD_PRELOAD=/usr/lib/libdrm_amdgpu.so.1 ./FreeCAD-0.21.2-Linux-x86_64.AppImage

Y a funcionar.

Se puede crear un fichero sh con esta instrucción y se acaba el problema

Problemas

Si se instala "fuse", se instala el escritorio XFCE4 de Xubuntu, que no está mal, pero que se lía con FreeCAD y las pantallas comienzan a moverse y marean. Que hay que hacer:

1. sudo apt remove fuse

Si por casualidad se nos ocurre eliminar todo el rastro de XFCE4 tal como se dice en https://askubuntu.com/questions/429148/how-do-i-remove-xfce-from-my-computer entonces la liamos

A parti de ahí, al rearrancar salian errores de toda clase:

1. Error al montar mnt-1aab800004d532055

He quitado la línea del etc/fstab que relacionaba ese UUID y ahí se jodió mas. Y salia este error

2. "default.target" is not active. Please review the default.target setting

He podido consultar el default.target con 

systemctl get-default y contesta grahical.target

Y se me ocuirre volver a instalar el entorno gráfico de Ubuntu

sudo apt-update

sudo apt-install ubuntu-desktop

sudo reboot

y a funcionar !!!!!


Tocar el /etc/fstab

Hay que tener cuidado y no tocarlo. Vale la pena utilizar la aplliacion "Discs" y nontar los discos que hacen falta