Skip to content
This repository has been archived by the owner on Dec 13, 2021. It is now read-only.

Repositorio de imágenes #5

Open
jesustorresdev opened this issue Jul 24, 2019 · 8 comments
Open

Repositorio de imágenes #5

jesustorresdev opened this issue Jul 24, 2019 · 8 comments

Comments

@jesustorresdev
Copy link
Owner

Al meter el almacenamiento de imágenes en WebServices surge el problema de que rompemos la arquitectura.

Ahora un adaptador sabe como se guardan cosas:

  1. sabe de uuid y que con ellos se identifican las entidades.
  2. sabe que los datos se guardan en disco y en que rutas. ¿Que ocurre si creamos un adaptador tipo SQL para las entidades? Que no tendrá importancia porque las imágenes se seguirán guardando en el mismo sitio.

Ante eso existen dos soluciones.

1. Las imágenes son un atributo de los badges.

Durante la creación la imagen puede guardarse como un StringIO en el atributo image del badge y es el badge lo que manejamos. Cuando salvamos el badge, se guarda la imagen en un archivo aparte y en el json en el disco duro se indica su ruta.

Cuando se recupera un badge se puede, se puede abrir la image y poner el objeto File en el atributo imagen del badge recién recuperado.

El mayor pero es que se accede a los archivos de imagen y se abren aunque después no se vayan a usar.

Una opción un poco mejor es crear una clase Image que guarde o un StringIO o la ruta al archivo. Si se intenta leer o escribir y está en modo StringIO, se hace la operación. Pero si se detecta que es una ruta, se abre el archivo, y luego se realiza la operación correspondiente sobre el archivo abierto.

2. Imágenes como entidades.

Esto habría que pensarlo bien y no está claro que haga mucha falta.

@mbdaso
Copy link
Contributor

mbdaso commented Jul 28, 2019

Una opción un poco mejor es crear una clase Image que guarde o un StringIO o la ruta al archivo. Si se intenta leer o escribir y está en modo StringIO, se hace la operación. Pero si se detecta que es una ruta, se abre el archivo, y luego se realiza la operación correspondiente sobre el archivo abierto.

Si creo una clase Image que gestione abrir/cerrar las imágenes, la entidad Badge tendrá una dependencia con esa clase asi que para no rompera la arquitectura las alternativas que veo son:

  1. Inyección de dependencias
  2. La clase Imagen es una entidad
    ¿Cuál es la mejor? A mí me parece más facil la segunda.

@jesustorresdev
Copy link
Owner Author

jesustorresdev commented Jul 29, 2019 via email

@mbdaso
Copy link
Contributor

mbdaso commented Jul 31, 2019

Probé hacer una clase Image que gestionara abrir/cerrar la imagen y luego en la entidad Badge con el decorador @Property hacer que cuando se accediara a Badge.image llamar a Image.get_value(), pero vi que era mucho más simple hacer el método open_image().
En EntityJsonRepository, cuando se guarda una medalla si detecta que Badge.image es un BytesIO, lo guarda en un archivo separado y pone en Badge.image el "file://..." con la ruta del archivo. Asi no se cargan las imagenes en EntityJsonRepository.load, por ejemplo cuando se usa el método BadgeService.name_exists para comprobar que no existe el nombre de la medalla antes de crearla.
Para cargar la imagen hay que llamar a open_image, que comprueba si esta abierta o no, y lo gestiona devolviendo un BytesIO.

Siempre paso lo que llega por internet, sea base64 o una url a un objeto BytesIO y luego creo la medalla. Creo que es más simple que guardar el string base64 en el json de la medalla y luego estar comprobando si es un file:// o un data:..., además debería ocupar menos espacio en disco.

@jesustorresdev
Copy link
Owner Author

jesustorresdev commented Jul 31, 2019 via email

@mbdaso
Copy link
Contributor

mbdaso commented Aug 1, 2019

O sea que Badge.image es un ByteIO o un str ¿no?

O Badge.image contiene una objecto clase Image que puede tener el ByteIO o
el la str con la URL como miembros privados?

Si, es un atributo en donde unas veces pongo el BytesIO y otras el Path:

@dataclass
class Badge:
    id: EntityID
    name: str
    description: str
    criteria: List[str]
    image: Union[Path, str, BytesIO]
    image_type: str

Así Badges sería de la clase Imagen que puede ser realmente un FileImage o
un ByteStreamImage según si se usan los datos o la ruta del archivo.

El problema es el create en api.py:

# Crear medalla
            self.badge_service.create(name=request_json['name'], description=request_json['description'],
                                      criteria=request_json['criteria'], image=self.validated_image_bytes)

Ahi le paso self.validated_image_bytes que es lo que saco decodificando request_json['image'], pero si la clase Badge tuviera un Image entonces tendría que hacer algo como:

# Crear medalla
            image = Image(self.validated_image_bytes)
            self.badge_service.create(name=request_json['name'], 
                                      description=request_json['description'],
                                      criteria=request_json['criteria'], image=image)

Según entendí las capas deberían ser independientes unas de otras, entonces eso no rompería la arquitectura? Estoy usando algo de la capa de dominio en la capa de infraestructura.

Yo creo que si Badges.image ya es un ByteIO, open_image debe devolver el
objeto BytesIO.
Pero si Badges.image es una str, lo que hay que hacer es abrir el archivo
con open() y devolver eso.

Ambos retornos adminten los mismos métodos. Son básicamente compatibles.
Quien use lo devuelto por open_image para leer o escribir no sabrá si lo
hace sobre un BytesIO o sobre un archivo de verdad.

Esa era la idea, pero me falto una cosa. Lo acabo de modificar:

def open_image(self, image: Union[str, BytesIO]) -> BytesIO:
        '''
        Método para leer la imagen. Si recibe un str abre la imagen,
        si recibe un BytesIO es que la imagen ya estaba abierta y lo devuelve
        '''
        fileformat = 'file://'
        if image.startswith(fileformat):
            image = image.replace(fileformat, '')
            with open(image, 'rb') as f:
                image_bytes = BytesIO(f.read())
        elif isinstance(image, BytesIO):
            image_bytes = image
        else:
            image_bytes = None
            raise TypeError(f'BadgeService.open_image: format not recognized')
        return image_bytes

@jesustorresdev
Copy link
Owner Author

jesustorresdev commented Aug 1, 2019 via email

@mbdaso
Copy link
Contributor

mbdaso commented Aug 4, 2019

Añadida clase BadgeImage y demás correcciones en #8

@jesustorresdev
Copy link
Owner Author

Cuando creas que la incidencia está resuelta la cierras indicando donde la resolviste. Otra opción es que un comentario de commit diga fix #5 o algo así. Y se cierra sola la incidencia al hacer el merge del PR.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants