PyFilesystem: una interfaz, diferentes sistemas de archivos

20200526115742

PyFilesystem: una interfaz, diferentes sistemas de archivos

Las librerías estándar de Python incluyen funciones para majenar archivos. Entre ellas, está la función open para abrir archivos. También, dentro de su librería os cuenta con un conjunto de funciones para manejar directorios como mkdir, listdir, o las utilerías dentro de su submódulo os.path.

Understanding Linux filesystems (Opensource.com)

Sin embargo, sólo podemos utilizar las funciones de la librería estándar para manejar archivos y directorios presentes (o "montados") en nuestro sistema de archivos local (en nuestra computadora). Para acceder a archivos y directorios en unidades externas, en nuestra red loca, o en la nube, tendríamos que echar mano de otras funciones. Aunque podríamos resolver el problema con la librería estándar, nos implicaría desarrollar nuestras propias librerías para cada tipo de sistema de archivos que pretendamos accesar.

Afortunadamente, alguien más ya hizo el trabajo de crear una interfaz que sirve para acceder a algunos de los sistemas de archivos más comunes desde Python: el paquete PyFilesystem. En este apunte pusimos algunos casos de uso comunes. Pero si quisiéramos ir más allá, podríamos referirnos entonces a la documentación oficial de PyFilesystem.

Para instalar PyFilesystem en nuestro ambiente de desarrollo de Python, podemos utilizar el comando ubicado en el listado 1.

$ python3 -m pip install fs

Listado 1. Comando para instalar PyFilesystem.


Cómo manejar archivos

¿Cómo se abre un sistema de archivos?

Cada sistema de archivos con el que PyFilesystem es compatible tiene asignado una clase de Python. En esta sección demostramos el sistema de archivos "nativo" del sistema operativo (cuya clase de PyFilesystem correspondiente es OSFS), y el sistema de archivos comprimido en Zip (que podemos accesar con la clase ZipFS). Los sistemas de archivos que al día de hoy aparecen en la documentación son: "App", "FTP", "Memory", "Mount", "Multi", "OS", "Sub", "Tar", "Temporary", y "Zip".

Los códigos de demostración de OSFS y ZipFS están contenidos en los listados 2 y 3 respectivamente. En el código utilizamos bloques with, que nos ahorran las llamadas a las funciones close y al manejo de excepciones dentro del bloque.

En los códigos de demostración se revela la principal fortaleza de PyFilesystem: que sus diferentes clases de sistemas de archivos poseen la misma interfaz de acceso. En ambos ejemplos creamos un directorio con mkdir, abrimos un archivo con open. Además, en ambos ejemplos escribimos los archivos con las misma interfaz write y read.

Un espacio de nombres, o namespace, de PyFilesystem es un conjunto de meta-datos de un archivo o directorio. Existen diferentes espacios de nombres en PyFilesystem, pero no todos son compatibles con todos los sistemas de archivos con los que PyFilesystem es compatible. Si queremos conseguir un meta-dato en particular, primero debemos averiguar en qué espacios de nombres está disponible, y cuál de esos espacios de nombres es compatible con el sistema de archivos que estamos utilizando. Algunos espacios de nombres son "details", "access", y "stat".


# OSFS nos permite trabajar con el sistema de archivos
# nativo de nuestro sistema operativo.
from fs.osfs import OSFS 

# Utilizamos el directorio actual (".") de nuestro sistema de archivos
# como el directorio raiz para nuestras pruebas.
with OSFS(".") as mis_archivos:
    
    # Si el subdirectorio "directorio_de_prueba" no existe, entonces lo
    # creamos.
    if (not mis_archivos.exists("directorio_de_prueba")):
        mis_archivos.makedir("directorio_de_prueba")
       
    # Escribimos un documento llamado "demo.txt" dentro del subdirectorio
    # "directorio_de_prueba"
    with mis_archivos.open("directorio_de_prueba/demo.txt", mode="w") as documento:
        documento.write("¡Hola desde Python!")
    
    # Leemos de vuelta lo que escribimos en "demo.txt"
    with mis_archivos.open("directorio_de_prueba/demo.txt") as documento:
        contenido = documento.read()
        print(contenido)  # -> ¡Hola desde Python!
        
    # Exploramos un espacio de nombres con los meta-datos de nuestro documento
    informacion = mis_archivos.getinfo(
        "directorio_de_prueba/demo.txt", 
        namespaces=["details"]
    )
    
    print("nombre :", informacion.name)
    print("¿es un directorio?", informacion.is_dir)
    print("tamaño :", informacion.size)
    print("tipo :", informacion.type)
    print("¿cuándo se modificó por última vez?", informacion.modified)
        

Listado 2. Código para manipular el sistema de archivos nativo del sistema operativo.


# Importamos el sistema de archivos para archivos comprimidos ZIP.
from fs.zipfs import ZipFS

# Creamos un archivo ZIP 
with ZipFS("archivo_comprimido.zip", write=True) as comprimido:
    
    # Agregamos un sub-directorio ("ejemplo")
    comprimido.makedir("ejemplo", recreate=True)
    
    # Agregamos un archivo ("documento.txt") dentro del subdirectorio
    # que creamos.
    with comprimido.open("ejemplo/documento.txt", mode="w+") as documento:
        documento.write("¡Hola desde el zip!")

        
# Leemos de vuelta el archivo ZIP que creamos.
with ZipFS("archivo_comprimido.zip") as comprimido: 

    # Leemos e imprimimos el contenido del documento que creamos.
    with comprimido.open("ejemplo/documento.txt") as documento:
        contenido = documento.read()
        print(contenido)

Listado 3. Código para manipular un sistema de archivos comprimido.


Cómo manejar directorios

Exploremos ahora la interfaz para manejar archivos con PyFilesystem. El código contenido en el listado 4 contiene ejemplos para: crear directorios, mostrar la jerarquía de directorios y archivos, listar las rutas de cada archivo, conseguir los meta-datos de cada elemento del sistema de archivos, copiar un directorio de forma recursiva, y eliminar un directorio de forma recursiva.

from fs.osfs import OSFS

# Ahora agregamos "create=True" para crear el subdirectorio "prueba" en el
# directorio actual en caso de que no exista.
with OSFS("prueba", create=True) as directorio:
    
    # Creamos unos subdirectorios y archivos de prueba
    directorio.makedirs("uno/dos/tres", recreate=True)
    directorio.makedir("uno/cuatro", recreate=True)
    directorio.touch("uno/a.txt")
    directorio.touch("uno/b.txt")
    directorio.touch("uno/c.md")
    directorio.touch("uno/dos/d.txt")
    
    # Imprime un árbol con la jerarquía de subdirectorios y archivos
    directorio.tree()
    
    # Cada elemento de la lista de rutas es una cadena que indica 
    # está cada subdirectorio y documento dentro del sistema de
    # archivos.
    lista_de_rutas = directorio.listdir("uno")
    print("lista_de_rutas", lista_de_rutas)
    
    # La lista de objetos contiene objetos de tipo "directorio" y de
    # tipo "documento". No contiene cadenas con las rutas, como era
    # el caso de la lista de rutas.
    lista_de_objetos = list(directorio.scandir("uno"))
    print("lista_de_objetos", lista_de_objetos)
    
    # Contiene una lista de objetos similar a lista_de_objetos, pero
    # sólo aquellos que: son documentos, y terminan en .txt.
    lista_filtrada = list(directorio.filterdir("uno", files=["*.txt"]))
    print("lista_filtrada", lista_filtrada)
    
    # Conseguimos los espacios de nombres "details" con los metadatos de
    # todos los elementos contenidos en el directorio "uno" de nuestro
    # sistema de archivos.
    lista_de_metadatos = directorio.scandir("uno", namespaces=["details"])
    for metadatos in lista_de_metadatos:
        # El atributo "raw" de "metadatos" contiene un diccionario con todos
        # los metadatos contenidos en el espacio de nombres.
        print(metadatos.raw)  
    
    # Hacemos una copia del subdirectorio "uno" y todo su contenido.
    directorio.copydir("uno", "copia_de_uno", create=True)
    print("Mira: ahora el directorio copia_de_uno está ahí.")
    directorio.tree()
    
    # Eliminamos la copia del directorio "uno" que acabamos de crear.
    directorio.removetree("copia_de_uno")
    print("Y ahora ya no.")
    directorio.tree()

Listado 4. Código para demostrar algunas funciones de uso común de PyFilesystem para manipular directorios y sus contenidos.


Cómo "caminar" por un sistema de archivos

En el contexto de los sistemas de archivos, el verbo "caminar" se refiere al acceso recursivo de directorios y archivos. Por ejemplo, "caminar" en el directorio "./A" implica:

  1. Listar el contenido (subdirectorios y archivos) del directorio "./A".
  2. Listar el contenido de cada subdirectorio de "./A".
  3. Listar el contenido de cada subdirectorio de cada subdirectorio de "./A".
  4. Y así sucesivamente hasta llegar a los subdirectorios que no tengan subdirectorios.

Las interfaces que ofrece PyFilesystem van más allá: nos permiten procesar cada elemento del sistema de archivos de forma separada y filtrada.

Los listados 5 y 6 hacen una demostración de la "caminata" por dos sistemas de archivos: uno basado en el sistema de archivos nativo del sistema operativo, y otro comprimido en Zip (respectivamente).

from fs.osfs import OSFS

with OSFS(".") as directorio_actual:
    
    print("Este directorio tiene los siguientes documentos:")
    for ruta in directorio_actual.walk.files():
        print(" -", ruta)
    
    print("Este directorio tiene los siguientes subdirectorios")
    for ruta in directorio_actual.walk.dirs():
        print(" -", ruta)
        
    print("Los documentos cuyo nombre termina en *.txt son:")
    for ruta in directorio_actual.walk.files(filter=["*.txt"]):
    	print(" -", ruta)
        
    print("Aquí tienes lista de listas con: nombre, ¿directorio?, y tamaño")
    for ruta, metadatos in directorio_actual.walk.info(namespaces=["details"]):
        print(" -", [ruta, metadatos.is_dir, metadatos.size])
        
    print("Aquí va una lista de listas con: ruta, archivos, y subdirectorios")
    for paso in directorio_actual.walk():
        print(" -", [paso.path, paso.files, paso.dirs])

Listado 5. Demostración de walk sobre un sistema de archivos nativo del sistema operativo.

 

from fs.zipfs import ZipFS

with ZipFS("ejemplo.zip", write=True) as comprimido:
    
    # Creamos unos subdirectorios y archivos de prueba
    comprimido.makedirs("uno/dos/tres", recreate=True)
    comprimido.makedir("uno/cuatro", recreate=True)
    comprimido.touch("uno/a.txt")
    comprimido.touch("uno/b.txt")
    comprimido.touch("uno/c.md")
    comprimido.touch("uno/dos/d.txt")
    
    print("ejemplo.zip tiene los siguientes documentos:")
    for ruta in comprimido.walk.files():
        print(" -", ruta)
    
    print("ejemplo.zip tiene los siguientes subdirectorios")
    for ruta in comprimido.walk.dirs():
        print(" -", ruta)
        
    print("Los documentos cuyo nombre termina en *.txt son:")
    for ruta in comprimido.walk.files(filter=["*.txt"]):
    	print(" -", ruta)
        
    print("Aquí tienes lista de listas con: nombre, ¿directorio?, y tamaño")
    for ruta, metadatos in comprimido.walk.info(namespaces=["details"]):
        print(" -", [ruta, metadatos.is_dir, metadatos.size])
        
    print("Aquí va una lista de listas con: ruta, archivos, y subdirectorios")
    for paso in comprimido.walk():
        print(" -", [paso.path, paso.files, paso.dirs])

Listado 6. Demostración de walk dentro de un archivo comprimido en Zip.

Comentarios

Entradas más populares de este blog

10 palabras valen más que una imagen - 20240526223027

20220214085408 - El reto de hacer amistades nuevas en la vida adulta

20220208192042 - Los modelos abstractos: sólo una cara del diamante