def put_resource_data(resource_id): content_type = "application/octet-stream" if request.headers.get("Content-type"): content_type, _ = parse_header(request.headers["Content-type"]) with db.cursor() as cur: cur.execute( """ SELECT id, data_oid FROM "resource" WHERE id = %(id)s; """, dict(id=resource_id), ) resource = cur.fetchone() if resource is None: raise NotFound() # todo: better use a streaming response here..? with db, db.cursor() as cur: lobj = db.lobject(oid=resource["data_oid"], mode="wb") lobj.seek(0) lobj.truncate() lobj.write(request.data) lobj.close() resource_hash = "sha1:" + hashlib.sha1(request.data).hexdigest() data = dict(id=resource_id, mimetype=content_type, mtime=datetime.datetime.utcnow(), hash=resource_hash) query = querybuilder.update("resource", data) cur.execute(query, data) return "", 200
def put_resource_data(resource_id): content_type = 'application/octet-stream' if request.headers.get('Content-type'): content_type, _ = parse_header(request.headers['Content-type']) with db.cursor() as cur: cur.execute( """ SELECT id, data_oid FROM "resource" WHERE id = %(id)s; """, dict(id=resource_id)) resource = cur.fetchone() if resource is None: raise NotFound() # todo: better use a streaming response here..? with db, db.cursor() as cur: lobj = db.lobject(oid=resource['data_oid'], mode='wb') lobj.seek(0) lobj.truncate() lobj.write(request.data) lobj.close() resource_hash = 'sha1:' + hashlib.sha1(request.data).hexdigest() data = dict(id=resource_id, mimetype=content_type, mtime=datetime.datetime.utcnow(), hash=resource_hash) query = querybuilder.update('resource', data) cur.execute(query, data) return '', 200
def delete_resource_data(resource_id): with db.cursor() as cur: cur.execute( """ SELECT id, data_oid FROM "resource" WHERE id = %(id)s; """, dict(id=resource_id)) resource = cur.fetchone() # todo: better use a streaming response here..? with db: lobj = db.lobject(oid=resource['data_oid'], mode='wb') lobj.unlink() # Then, create a record for the metadata with db, db.cursor() as cur: query = querybuilder.delete('resource') cur.execute(query, dict(id=resource_id)) db.commit() return '', 200
def _get_internal_resource_data(resource_id): """Get all data form an internally-stored resource""" # todo: improve this function to avoid keeping the whole thing in memory # todo: also, make this more generic (and move from here) with db, db.cursor() as cur: cur.execute(""" SELECT id, mimetype, data_oid FROM "resource" WHERE id = %(id)s; """, dict(id=resource_id)) resource = cur.fetchone() if resource is None: raise NotFound() with db: lobject = db.lobject(oid=resource['data_oid'], mode='rb') data = lobject.read() lobject.close() return data
def post_resource_index(): """ We got some data to be stored as a new resource. Then we want to return 201 + URL of the created resource in the Location: header. """ content_type = "application/octet-stream" if request.headers.get("Content-type"): content_type, _ = parse_header(request.headers["Content-type"]) # First, store the data in a PostgreSQL large object with db, db.cursor() as cur: lobj = db.lobject(oid=0, mode="wb") oid = lobj.oid lobj.write(request.data) lobj.close() resource_hash = "sha1:" + hashlib.sha1(request.data).hexdigest() data = dict( metadata="{}", auto_metadata="{}", mimetype=content_type, data_oid=oid, ctime=datetime.datetime.utcnow(), mtime=datetime.datetime.utcnow(), hash=resource_hash, ) # Then, create a record for the metadata query = querybuilder.insert("resource", data) cur.execute(query, data) resource_id = cur.fetchone()[0] # Last, retun 201 + Location: header location = url_for(".get_resource_data", resource_id=resource_id) return "", 201, {"Location": location}
def delete_resource_data(resource_id): with db.cursor() as cur: cur.execute( """ SELECT id, data_oid FROM "resource" WHERE id = %(id)s; """, dict(id=resource_id), ) resource = cur.fetchone() # todo: better use a streaming response here..? with db: lobj = db.lobject(oid=resource["data_oid"], mode="wb") lobj.unlink() # Then, create a record for the metadata with db, db.cursor() as cur: query = querybuilder.delete("resource") cur.execute(query, dict(id=resource_id)) db.commit() return "", 200
def post_resource_index(): """ We got some data to be stored as a new resource. Then we want to return 201 + URL of the created resource in the Location: header. """ content_type = 'application/octet-stream' if request.headers.get('Content-type'): content_type, _ = parse_header(request.headers['Content-type']) # First, store the data in a PostgreSQL large object with db, db.cursor() as cur: lobj = db.lobject(oid=0, mode='wb') oid = lobj.oid lobj.write(request.data) lobj.close() resource_hash = 'sha1:' + hashlib.sha1(request.data).hexdigest() data = dict(metadata='{}', auto_metadata='{}', mimetype=content_type, data_oid=oid, ctime=datetime.datetime.utcnow(), mtime=datetime.datetime.utcnow(), hash=resource_hash) # Then, create a record for the metadata query = querybuilder.insert('resource', data) cur.execute(query, data) resource_id = cur.fetchone()[0] # Last, retun 201 + Location: header location = url_for('.get_resource_data', resource_id=resource_id) return '', 201, {'Location': location}
def serve_resource(resource_id, transfer_block_size=4096): """ Serve resource data via HTTP, setting ETag and Last-Modified headers and honoring ``If-None-Match`` and ``If-modified-since`` headers. Currently supported features: - Set ``ETag`` header (to the hash of resource body) - Set ``Last-Modified`` header (to the last modification date) - Honor the ``If-modified-since`` header (if the resource was not modified, return 304) Planned features: - Return response as a stream, to avoid loading everything in memory. - Honor the ``If-Match`` / ``If-None-Match`` headers - Support ``Range`` requests + 206 partial response - Set ``Cache-control`` and ``Expire`` headers (?) - Properly support HEAD requests. :param resource_id: Id of the resource to be served :param transfer_block_size: Size of the streaming response size. Defaults to 4096 bytes. :return: A valid return value for a Flask view. """ with db, db.cursor() as cur: query = querybuilder.select_pk("resource", fields="id, mimetype, data_oid, mtime, hash") cur.execute(query, dict(id=resource_id)) resource = cur.fetchone() if resource is None: raise NotFound() mimetype = resource["mimetype"] or "application/octet-stream" headers = { "Content-type": mimetype, "Last-modified": resource["mtime"].strftime(HTTP_DATE_FORMAT), "ETag": resource["hash"], } # ------------------------------------------------------------ # Check the if-modified-since header if "if-modified-since" in request.headers: try: if_modified_since_date = datetime.strptime(request.headers["if-modified-since"], HTTP_DATE_FORMAT) except: raise BadRequest("Invalid If-Modified-Since header value") if if_modified_since_date >= resource["mtime"]: # The resource was not modified -> return ``304 NOT MODIFIED`` return Response("", status=304, headers=headers) # ------------------------------------------------------------ # Stream the response data with db: lobject = db.lobject(oid=resource["data_oid"], mode="rb") data = lobject.read() lobject.close() return Response(data, status=200, headers=headers)
def serve_resource(resource_id, transfer_block_size=4096): """ Serve resource data via HTTP, setting ETag and Last-Modified headers and honoring ``If-None-Match`` and ``If-modified-since`` headers. Currently supported features: - Set ``ETag`` header (to the hash of resource body) - Set ``Last-Modified`` header (to the last modification date) - Honor the ``If-modified-since`` header (if the resource was not modified, return 304) Planned features: - Return response as a stream, to avoid loading everything in memory. - Honor the ``If-Match`` / ``If-None-Match`` headers - Support ``Range`` requests + 206 partial response - Set ``Cache-control`` and ``Expire`` headers (?) - Properly support HEAD requests. :param resource_id: Id of the resource to be served :param transfer_block_size: Size of the streaming response size. Defaults to 4096 bytes. :return: A valid return value for a Flask view. """ with db, db.cursor() as cur: query = querybuilder.select_pk( 'resource', fields='id, mimetype, data_oid, mtime, hash') cur.execute(query, dict(id=resource_id)) resource = cur.fetchone() if resource is None: raise NotFound() mimetype = resource['mimetype'] or 'application/octet-stream' headers = { 'Content-type': mimetype, 'Last-modified': resource['mtime'].strftime(HTTP_DATE_FORMAT), 'ETag': resource['hash'], } # ------------------------------------------------------------ # Check the if-modified-since header if 'if-modified-since' in request.headers: try: if_modified_since_date = datetime.strptime( request.headers['if-modified-since'], HTTP_DATE_FORMAT) except: raise BadRequest("Invalid If-Modified-Since header value") if if_modified_since_date >= resource['mtime']: # The resource was not modified -> return ``304 NOT MODIFIED`` return Response('', status=304, headers=headers) # ------------------------------------------------------------ # Stream the response data with db: lobject = db.lobject(oid=resource['data_oid'], mode='rb') data = lobject.read() lobject.close() return Response(data, status=200, headers=headers)
def open_resource(self): oid = self._resource_record['data_oid'] return db.lobject(oid=oid, mode='rb')