Django StreamingHttpResponse deleting file after user finishes downloading it - python

I am having problem tracking when a file download has been completed when StreamingHttpResponse is used. My intention is to delete the file once it has been downloaded by the user.
Doing the following returned an exception in terminal killing the server.
def down(request, file_name):
if request.method == 'GET':
if file_name:
import os
fh = get_object_or_404(FileHandler, filename=file_name)
csv_path = os.path.join(fh.path, fh.filename)
csv_file = open(csv_path)
response = StreamingHttpResponse(csv_file, content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{}"'.format(fh.filename)
csv_file.close()
# I can now delete the file using os.remove(csv_path). Not sure since response has not been returned
return response
return HttpResponseRedirect('/b2b/export/')
Trace back:
----------------------------------------
Exception happened during processing of request from ('127.0.0.1', 59899)
Traceback (most recent call last):
File "/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/SocketServer.py", line 599, in process_request_thread
self.finish_request(request, client_address)
File "/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/SocketServer.py", line 334, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/Users/Michael/.virtualenvs/scrape/lib/python2.7/site-packages/django/core/servers/basehttp.py", line 102, in __init__
super(WSGIRequestHandler, self).__init__(*args, **kwargs)
File "/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/SocketServer.py", line 655, in __init__
self.handle()
File "/Users/Michael/.virtualenvs/scrape/lib/python2.7/site-packages/django/core/servers/basehttp.py", line 182, in handle
handler.run(self.server.get_app())
File "/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/wsgiref/handlers.py", line 92, in run
self.close()
File "/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/wsgiref/simple_server.py", line 33, in close
self.status.split(' ',1)[0], self.bytes_sent
AttributeError: 'NoneType' object has no attribute 'split'
----------------------------------------
What works is as follows but am not sure when to delete the file or know when download has been completed. Most importantly how to close the file:
def down(request, file_name):
if request.method == 'GET':
if file_name:
import os
fh = get_object_or_404(FileHandler, filename=file_name)
csv_path = os.path.join(fh.path, fh.filename)
response = StreamingHttpResponse(open(csv_path), content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{}"'.format(fh.filename)
return response
return HttpResponseRedirect('/b2b/export/')

I have tried the above suggestion by #mwag; however with FileWrapper.
In my case, wanted to zip up directories and get rid of the archive when download is done.
import os, time, zipfile
from django.http import StreamingHttpResponse
from wsgiref.util import FileWrapper
class FileDeleteWrapper(FileWrapper):
def __init__(self, filepath, *args, **kwargs):
self.filepath = filepath
super(FileDeleteWrapper, self).__init__(*args, **kwargs)
def __del__(self, *args, **kwargs):
os.remove(self.filepath)
# View function
def zipFiles(request, assetId):
asset = get_object_or_404(Asset, id=assetId)
try:
files = File.objects.filter(asset=asset)
prefix = str(time.time()) +'_'
zipPath = os.path.join(
settings.ZIPPED_FILES_DIR,
prefix + asset.label+'.zip'
)
z = zipfile.ZipFile(zipPath, 'w', zipfile.ZIP_DEFLATED)
for f in files:
path = os.path.join(
mainSet.MEDIA_ROOT,
str(f.file)
)
z.write(path, str(f))
z.close()
chunkSize = 16384
response = StreamingHttpResponse(
FileDeleteWrapper(
filepath = zipPath,
filelike=open(zipPath, 'rb'),
blksize=chunkSize
)
)
response['Content-Length'] = os.path.getsize(zipPath)
response['Content-Disposition'] = "attachment; filename=%s" % asset.label+'.zip'
return response
except Exception as e:
if mainSet.DEBUG:
print(type(e))
else:
# log expception
raise Http404

Try creating a class that will delete the file when it is gc'd. For example, something like the below might work:
class open_then_delete(object):
def __init__(self, filename, mode='rb'):
self.filename = filename
self.file_obj = open(filename, mode)
def __del__(self):
self.close()
def close(self):
if self.file_obj:
self.file_obj.close()
self.file_obj = None
self.cleanup()
def cleanup(self):
if self.filename:
try:
sys.stderr.write('open_then_delete: del ' + self.filename)
os.remove(self.filename)
except:
pass
self.filename = None
def __getattr__(self, attr):
return getattr(self.file_obj, attr)
def __iter__(self):
return iter(self.file_obj)
# below is your code, modified to use use_then_delete
def down(request, file_name):
if request.method == 'GET':
if file_name:
import os
fh = get_object_or_404(FileHandler, filename=file_name)
csv = open_then_delete(os.path.join(fh.path, fh.filename))
response = StreamingHttpResponse(csv.open(), content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="{}"'.format(fh.filename)
return response
return HttpResponseRedirect('/b2b/export/')

Related

Parse .csv file in tornado web server

I'm trying to parse a .csv file using this code
class uploadHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
def post(self):
file = self.request.files['uploadedFile'][0]
filename = file['filename']
output = open("file/" + filename, 'wb')
output.write(file['body'])
self.write("http://localhost:8080/file/" + filename)
self.finish("uploaded")
df = pandas.read_csv("file\\" + filename)
print(df)
if (__name__ == "__main__"):
app = tornado.web.Application([
("/", uploadHandler),
("/file/(.*)", tornado.web.StaticFileHandler, {"path" : "file"})
])
app.listen(8080)
print("Listening on port 8080")
tornado.ioloop.IOLoop.instance().start()
I get the error
File "pandas\_libs\parsers.pyx", line 554, in pandas._libs.parsers.TextReader.__cinit__
pandas.errors.EmptyDataError: No columns to parse from file
How can I parse this file?
I tried accessing this file in different parts of the code but I get the same error. The file gets uploaded correctly.
You have to close the output handler before read the file with Pandas:
...
output = open("file/" + filename, 'wb')
output.write(file['body'])
output.close() # <- HERE
...
But use a context manager instead of closing the file yourself:
...
with open("file/" + filename, 'wb') as output:
output.write(file['body'])
...

How do i upload a folder containing metadata to pinata using a script in python-brownie?

I've been trying for the past 24 hours but can't find a solution.
This is the code:
import os
from pathlib import Path
import requests
PINATA_BASE_URL = "https://api.pinata.cloud/"
endpoint = "pinning/pinFileToIPFS"
# Change this filepath
filepath = "C:/Users/acer/Desktop/Ciao"
filename = os.listdir(filepath)
print(filename)
headers = {
"pinata_api_key": os.getenv("PINATA_API_KEY"),
"pinata_secret_api_key": os.getenv("PINATA_API_SECRET"),
}
def main():
with Path(filepath).open("rb") as fp:
image_binary = filepath.read()
print(image_binary)
response = requests.post(
PINATA_BASE_URL + endpoint,
files={"file": (filename, image_binary)},
headers=headers,
)
print(response.json())
if __name__ == "__main__":
main()
I tried to open the folder where the metadata was stored and than i sent the request with the list of files in the folder.
This is the error:
['no.txt', 'yeah.txt']
Traceback (most recent call last):
File "C:\Users\acer\Desktop\SOLIDITY_PYTHON\nft-bored-ape\scripts\upload_to_pinata.py", line 30, in <module>
main()
File "C:\Users\acer\Desktop\SOLIDITY_PYTHON\nft-bored-ape\scripts\upload_to_pinata.py", line 18, in main
with Path(filepath).open("rb") as fp:
File "C:\Users\acer\AppData\Local\Programs\Python\Python310\lib\pathlib.py", line 1119, in open
return self._accessor.open(self, mode, buffering, encoding, errors,
PermissionError: [Errno 13] Permission denied: 'C:\\Users\\acer\\Desktop\\Ciao'
Nevermind...
After some research i found the answer to my own question.
Here is the code:
# Tulli's script :-)
from brownie import config
import requests, os, typing as tp
PINATA_BASE_URL = "https://api.pinata.cloud/"
endpoint = "pinning/pinFileToIPFS"
# Here you could use os.getenv("VARIABLE_NAME"),
# i used config from my .yaml file. Your choice!
headers = {
"pinata_api_key": config["pinata"]["api-keys"],
"pinata_secret_api_key": config["pinata"]["api-private"],
}
def get_all_files(directory: str) -> tp.List[str]:
"""get a list of absolute paths to every file located in the directory"""
paths: tp.List[str] = []
for root, dirs, files_ in os.walk(os.path.abspath(directory)):
for file in files_:
paths.append(os.path.join(root, file))
return paths
def upload_folder_to_pinata(filepath):
all_files: tp.List[str] = get_all_files(filepath)
# The replace function is a must,
# pinata servers doesn't recognize the backslash.
# Your filepath is probably different than mine,
# so in the split function put your "penultimate_file/".
# Strip the square brackets and the apostrophe,
# because we don't want it as part of the metadata ipfs name
files = [
(
"file",
(
str(file.replace("\\", "/").split("Desktop/")[-1:])
.strip("[]")
.strip("'"),
open(file, "rb"),
),
)
for file in all_files
]
response: requests.Response = requests.post(
PINATA_BASE_URL + endpoint,
files=files,
headers=headers,
)
# If you want to see all the stats then do this:
# return/print/do both separately response.json()
return "ipfs.io/ipfs/" + response.json()["IpfsHash"]
def main():
upload_folder_to_pinata("Put your full filepath here")
if __name__ == "__main__":
main()

join() argument must be str, bytes, or os.PathLike object, not 'NoneType'

I am making a CLI tool using python and click. I have a command called ext that has two options --type and --path and then another command ext. The function organizes the files in the specified path based on the extension specified in the given type. This is the command and the error I get:
Categorize ext --type image --path C:\Users\ACERq\Pictures\Screenshots ext
join() argument must be str, bytes, or os.PathLike object, not 'NoneType'
I have another similar command which takes just one path option, that function works properly with the same path
import click
from src.services.ext_functions import *
class Context:
def __init__(self, type, path):
self.type = type
self.path = path
#click.group()
#click.option("-t", "--type", type=str, help = "Type of extension", required=False, default="media")
#click.option("-p", "--path", type=str, help = "Path to organize", required=False, default=os.getcwd())
#click.pass_context
def main(ctx, type, path):
"""Organize files based on extension"""
ctx.obj = Context(type, path)
pass
#main.command()
#click.pass_context
def ext(ctx):
"""Organize files based on specified extension types"""
extension = ctx.obj.type
folder_to_track = ctx.obj.path
click.echo(extension_category(extension, folder_to_track))
#main.command()
#click.pass_context
def all(ctx):
"""Organize all files based on extension types"""
folder_to_track = ctx.obj.path
click.echo(all_extensions_category(folder_to_track))
Full traceback
Traceback (most recent call last):
File "D:\Python-projects\Categorize-CLI\.venv\Scripts\Categorize-script.py", line 33, in <module>
sys.exit(load_entry_point('Categorize-CLI', 'console_scripts', 'Categorize')())
File "D:\Python-projects\Categorize-CLI\.venv\lib\site-packages\click\core.py", line 1130, in __call__
return self.main(*args, **kwargs)
File "D:\Python-projects\Categorize-CLI\.venv\lib\site-packages\click\core.py", line 1055, in main
rv = self.invoke(ctx)
File "D:\Python-projects\Categorize-CLI\.venv\lib\site-packages\click\core.py", line 1657, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "D:\Python-projects\Categorize-CLI\.venv\lib\site-packages\click\core.py", line 1657, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
File "D:\Python-projects\Categorize-CLI\.venv\lib\site-packages\click\core.py", line 1404, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "D:\Python-projects\Categorize-CLI\.venv\lib\site-packages\click\core.py", line 760, in invoke
return __callback(*args, **kwargs)
File "D:\Python-projects\Categorize-CLI\.venv\lib\site-packages\click\decorators.py", line 26, in new_func
return f(get_current_context(), *args, **kwargs)
File "d:\python-projects\categorize-cli\src\commands\ext.py", line 23, in ext
click.echo(extension_category(extension, folder_to_track))
File "d:\python-projects\categorize-cli\src\services\ext_functions.py", line 82, in extension_category
folder_path = os.path.join(folder_to_track, folder_name)
File "C:\Users\ACERq\AppData\Local\Programs\Python\Python310\lib\ntpath.py", line 117, in join
genericpath._check_arg_types('join', path, *paths)
File "C:\Users\ACERq\AppData\Local\Programs\Python\Python310\lib\genericpath.py", line 152, in _check_arg_types
raise TypeError(f'{funcname}() argument must be str, bytes, or '
TypeError: join() argument must be str, bytes, or os.PathLike object, not 'NoneType'
extension_category()
def extension_category(extension, folder_to_track):
start_time = time.monotonic()
movedFiles = False
count = 0
if check_files(folder_to_track):
for file in os.listdir(folder_to_track):
if not os.path.isdir(os.path.join(folder_to_track, file)):
try:
file_mappings = collections.defaultdict()
for filename in os.listdir(folder_to_track):
for value in extension:
if not os.path.isdir(os.path.join(folder_to_track, filename)) and any(
filename.endswith(value) for filename in os.listdir(folder_to_track)) == True:
file_mappings.setdefault(get_key(extension), []).append(filename)
for folder_name, folder_items in file_mappings.items():
folder_path = os.path.join(folder_to_track, folder_name)
folder_exists = os.path.exists(folder_path)
if not folder_exists:
os.mkdir(folder_path)
for filename in os.listdir(folder_to_track):
for value in extension:
if not os.path.isdir(os.path.join(folder_to_track, filename)) and filename.endswith(
value):
count = count + 1
source = os.path.join(folder_to_track, filename)
destination = os.path.join(folder_path, filename)
moveIncrementing(source,destination) # move all files containing sub_file_name in their filenames
movedFiles = True
if folder_exists:
for filename in os.listdir(folder_to_track):
for value in extension:
if not os.path.isdir(os.path.join(folder_to_track, filename)) and filename.endswith(
value):
count = count + 1
source = os.path.join(folder_to_track, filename)
destination = os.path.join(folder_path, filename)
moveIncrementing(source,
destination) # move all files containing sub_file_name in their filenames
movedFiles = True
end_time = time.monotonic()
displayProgressbar(count)
if movedFiles:
if count == 1:
return f"Successfully moved {count} file{os.linesep}Time taken: {timedelta(seconds=end_time - start_time)}"
else:
return f"Successfully moved {count} files{os.linesep}Time taken: {timedelta(seconds=end_time - start_time)}"
else:
return "Files with that extension do not exist in {}".format(folder_to_track)
except Exception as e:
print(e)
return f'{folder_to_track}: is either empty or not organizable'
else:
return f'{folder_to_track}: is either empty or not organizable'
If I call the function like this: extension_category(image, r"C:\Users\ACERq\Pictures\Screenshots") it works
Update
I managed to make it work but then I encountered a new problem
When I just check if the inputted value is equal to the extension then it works but when I add some more modifications it doesn't work
I finally fixed the problem, I thought the problem was due to the parameter folder_to_track but it was actually the second parameter extension that was causing the issue. The function took the parameter as the value of the extensions-dict and not the key. So instead of image as the extension, it should be [".png", ".jpg"] etc
import click
from src.services.ext_functions import *
class Context:
def __init__(self, type, path):
self.type = type
self.path = path
#click.command()
#click.option("-t", "--type", type=str, help = "Type of extension", required=True)
#click.option("-p", "--path", type=str, help = "Path to organize", required=False, default=os.getcwd())
#click.pass_context
def main(ctx, type, path):
"""Organize files based on extension"""
ctx.obj = Context(type, path)
extension = ctx.obj.type
folder_to_track = ctx.obj.path
categories = [key for key, value in extensions.items()]
if extension in categories and os.path.exists(folder_to_track):
click.echo(extension_category(extensions[extension], folder_to_track))
elif not extension in categories:
click.echo("{} is not one of the types of extensions".format(extension))
elif not os.path.exists(folder_to_track):
click.echo("{}: does not exist".format(folder_to_track))

How to zip a file to STDOUT with zipfile?

I have a program that creates a zip file containing a couple of files using zipfile. It takes an output filename as argument, but if no file or - is given, it should write to STDOUT. But if I pass sys.stdout as file parameter to ZipFile, it gives me the following error:
File "/usr/lib/python2.7/zipfile.py", line 775, in __init__
self._start_disk = self.fp.tell()
IOError: [Errno 29] Illegal seek
Here's my code:
import zipfile, sys
def myopen(name, mode='r'):
if name is not None:
try: return open(name, mode)
except EnvironmentError as e:
print >> sys.stderr, '%s: %s' % (name, e.strerror)
sys.exit(3)
elif mode == 'r': return sys.stdin
elif mode in 'aw': return sys.stdout
else: return None
outfile = None
# build the files ...
files = {'file1': 'Here is an example file.\n', 'file2': 'Some more ...'}
with myopen(outfile, 'w') as f, zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED, True) as zf:
for name, content in files.iteritems(): zf.writestr(name, content)
How can I make this work?

Python FTPLIB error 530 Permission Denied

I've tried the script below:
import os
from ftplib import FTP
ftp = FTP("ftpsite","myuser", "mypass")
ftp.login()
ftp.retrlines("LIST")
ftp.cwd("folderOne")
ftp.cwd("subFolder")
listing = []
ftp.retrlines("LIST", listing.append)
words = listing[0].split(None, 8)
filename = words[-1].lstrip()
#download the file
local_filename = os.path.join(r"C:\example", file)
lf = open(local_filename, "wb")
ftp.retrbinary("RETR " + filename, lf.write, 8*1024)
lf.close()
But everytime I run the script, it says:
Traceback (most recent call last):
File "C:\User\Desktop\sample\ex.py", line 4, in <module>
ftp = FTP("ftpsite", "myuser", "mypass")
File "C:\Python27\lib\ftplib.py", line 119, in __init__
self.login(user, passwd, acct)
File "C:\Python27\lib\ftplib.py", line 387, in login
resp = self.sendcmd('USER ' + user)
File "C:\Python27\lib\ftplib.py", line 244, in sendcmd
return self.getresp()
File "C:\Python27\lib\ftplib.py", line 219, in getresp
raise error_perm, resp
error_perm: 530 Permission denied.
I don't know what 530 Permission Denied means.Can anyone tell me what does that means?
It seems like the ftp server allows anonymous access; You don't need pass username, password.
FTP constructor accept hostname(or IP), not URL.
import sys
import os
from ftplib import FTP
ftp = FTP("ftpsite.com")
ftp.login()
ftp.cwd("/ftp/site/directory/")
listing = []
ftp.retrlines("LIST", listing.append)
words = listing[0].split(None, 8)
filesize = int(words[4])
filename = words[-1].lstrip()
class VerboseWriter:
def __init__(self, lf, filesize):
self.progress = 0
self.lf = lf
self.filesize = filesize
def write(self, data):
self.lf.write(data)
self.progress += len(data)
sys.stdout.write('\r{}/{} ({:.1%})'.format(self.progress, self.filesize, float(self.progress)/self.filesize))
sys.stdout.flush()
#download the file
with open(os.path.join(r"c:\example", filename), 'wb') as f:
ftp.retrbinary("RETR " + filename, VerboseWriter(lf, filesize).write, 8*1024)
print
ftp.quit()

Categories

Resources