¿Cómo guardar un archivo de imagen en una base de datos de Postgres?

Para propósitos de aprendizaje, estoy creando un sitio usando Python + Flask. Quiero recuperar una imagen de la base de datos y mostrarla en pantalla. Pero un paso a la vez.

No tengo idea de cómo guardar una imagen en mi base de datos en primer lugar. Mis búsquedas solo revelaron que tengo que usar un tipo de bytea en mi base de datos. Luego obtengo mi imagen y de alguna manera (??) la convierto a una matriz de bytes (bytea == array of bites?) Y de alguna manera (??) uso esta matriz en un comando de inserción.

Pude descubrir (tal vez) cómo hacerlo en Java ( aquí ) y C # ( aquí ), pero realmente me gustaría usar Python, al menos por ahora.

¿Alguien me puede ayudar?

Hay toneladas de preguntas de este tipo en este sitio. Pero la mayoría de ellos (fácilmente más del 85%) son contestados como “No debes guardar imágenes en tu base de datos, pertenecen a fs” y no responden la pregunta. El rest no soluciona mi problema. Entonces, no marque esto como duplicado si el duplicado tiene este tipo de respuesta.

Normalmente no escribo progtwigs de ejemplo completos para las personas, pero no lo demandaste y es bastante simple, así que aquí tienes:

 #!/usr/bin/env python3 import os import sys import psycopg2 import argparse db_conn_str = "dbname=regress user=craig" create_table_stm = """ CREATE TABLE files ( id serial primary key, orig_filename text not null, file_data bytea not null ) """ def main(argv): parser = argparse.ArgumentParser() parser_action = parser.add_mutually_exclusive_group(required=True) parser_action.add_argument("--store", action='store_const', const=True, help="Load an image from the named file and save it in the DB") parser_action.add_argument("--fetch", type=int, help="Fetch an image from the DB and store it in the named file, overwriting it if it exists. Takes the database file identifier as an argument.", metavar='42') parser.add_argument("filename", help="Name of file to write to / fetch from") args = parser.parse_args(argv[1:]) conn = psycopg2.connect(db_conn_str) curs = conn.cursor() # Ensure DB structure is present curs.execute("SELECT 1 FROM information_schema.tables WHERE table_schema = %s AND table_name = %s", ('public','files')) result = curs.fetchall() if len(result) == 0: curs.execute(create_table_stm) # and run the command if args.store: # Reads the whole file into memory. If you want to avoid that, # use large object storage instead of bytea; see the psycopg2 # and postgresql documentation. f = open(args.filename,'rb') # The following code works as-is in Python 3. # # In Python 2, you can't just pass a 'str' directly, as psycopg2 # will think it's an encoded text string, not raw bytes. You must # either use psycopg2.Binary to wrap it, or load the data into a # "bytearray" object. # # so either: # # filedata = psycopg2.Binary( f.read() ) # # or # # filedata = buffer( f.read() ) # filedata = f.read() curs.execute("INSERT INTO files(id, orig_filename, file_data) VALUES (DEFAULT,%s,%s) RETURNING id", (args.filename, filedata)) returned_id = curs.fetchone()[0] f.close() conn.commit() print("Stored {0} into DB record {1}".format(args.filename, returned_id)) elif args.fetch is not None: # Fetches the file from the DB into memory then writes it out. # Same as for store, to avoid that use a large object. f = open(args.filename,'wb') curs.execute("SELECT file_data, orig_filename FROM files WHERE id = %s", (int(args.fetch),)) (file_data, orig_filename) = curs.fetchone() # In Python 3 this code works as-is. # In Python 2, you must get the str from the returned buffer object. f.write(file_data) f.close() print("Fetched {0} into file {1}; original filename was {2}".format(args.fetch, args.filename, orig_filename)) conn.close() if __name__ == '__main__': main(sys.argv) 

Escrito con Python 3.3. El uso de Python 2.7 requiere que lea el archivo y lo convierta en un objeto de buffer o use las funciones de objetos grandes. La conversión a Python 2.6 y versiones anteriores requiere la instalación de argparse, probablemente otros cambios.

Querrá cambiar la cadena de conexión de la base de datos a algo que sea adecuado para su sistema si va a realizar una prueba de ejecución.

Si está trabajando con imágenes grandes, considere usar el soporte de objetos grandes de psycopg2 en lugar de bytea , en particular, lo_import for store, lo_export para escribir directamente en un archivo, y las funciones de lectura de objetos grandes para leer fragmentos pequeños de la imagen a la vez.

Espero que esto funcione para tí.

 import Image import StringIO im = Image.open("file_name.jpg") # Getting the Image fp = StringIO.StringIO() im.save(fp,"JPEG") output = fp.getvalue() # The output is 8-bit String. 

StringIO Image

 import psycopg2 import sys def readImage(): try: fin = open("woman.jpg", "rb") img = fin.read() return img except IOError, e: print "Error %d: %s" % (e.args[0],e.args[1]) sys.exit(1) finally: if fin: fin.close() try: con = psycopg2.connect(database="testdb", user="abc") cur = con.cursor() data = readImage() binary = psycopg2.Binary(data) cur.execute("INSERT INTO images(id, data) VALUES (1, %s)", (binary,) ) con.commit() except psycopg2.DatabaseError, e: if con: con.rollback() print 'Error %s' % e sys.exit(1) finally: if con: con.close() 

Esa es mi solución, puede funcionar en mi sitio web:

 @main.route('/upload', methods=['GET', 'POST']) def upload_avatar(): if request.method == 'POST': file = request.files['file'] if file and allowed_file(file.filename): current_user.avatar_local = file.read() db.session.add(current_user) db.session.commit() return redirect(url_for('main.user_page', username=current_user.username)) return render_template('upload_avatar.html', user=current_user) 

usando Flask, Flask-Alchemy para manejar la base de datos.

 {% block edit_avatar %} 

{% endblock %}

ese es el archivo html. Puedes insertarlo en tu html.

Puede usar la base64 de Python para codificar y decodificar cadenas binarias arbitrarias en cadenas de texto.