Opened 3 months ago

Last modified 3 months ago

#128 assigned enhancement

Transfer to/from in memory variables

Reported by: ftputiluser Owned by: schwa
Priority: minor Milestone:
Component: Library Version: 3.4
Keywords: download, upload, BytesIO Cc:

Description (last modified by schwa)

What is the best way to modify ftputil code to allow upload/download from/to BytesIO variable?(The upload/download functions allow only string path variables as inputs). I would like to avoid monkey patching the LocalFile or RemoteFile classes.

Change History (5)

comment:1 Changed 3 months ago by schwa

If I understand your question correctly, I would do it like this:

import io

import ftputil


my_file = io.BytesIO()

with ftputil.FTPHost(hostname, user, password) as host:
    # Download
    with host.open(remote_path, "rb") as fobj:
        remote_data = fobj.read()
        my_file.write(remote_data)  # or `my_file = io.BytesIO(remote_data)`
    # Upload
    with host.open(another_remote_path, "wb") as fobj:
        future_remote_data = my_file.read()  # or `.getvalue()`
        fobj.write(future_remote_data)

See https://ftputil.sschwarzer.net/trac/wiki/Documentation#file-like-objects

If you want a conditional download or upload, you can check the last modification time on the server with host.path.getmtime (corresponding os.path.getmtime). If you do this, make sure you have a time zone correction applied if needed (see https://ftputil.sschwarzer.net/trac/wiki/Documentation , section "Time zone correction").

Does this answer your question? If not, please explain a bit more what you have in mind, maybe with some hypothetical example code.

comment:2 Changed 3 months ago by schwa

Description: modified (diff)
Keywords: download upload BytesIO added
Status: newassigned

comment:3 Changed 3 months ago by schwa

For the record, it seems there's a lot of overlap with ticket #118. But let's continue the discussion here now.

comment:4 Changed 3 months ago by ftputiluser

Sorry if I was a bit vague. What I actually wanted was to implement the same api as the

def upload(self, source, target, callback=None):

method, which behavior will depend on the type of source parameter. If it is an instanceof str than the method will be the same as of today but if the it's of type BytesIO than LocalFile? class obj method will not try to treat it as path to a file and open it... Should I just inherit from FtpHost? and override the above mentioned methods?

  • The option you described above which uses the file like objects would work of course, but it is too verbose and I would like to be able to use the upload/download methods with a BytesIO type parameters.

comment:5 in reply to:  4 Changed 3 months ago by schwa

Thanks for your feedback.

Replying to ftputiluser:

Sorry if I was a bit vague. What I actually wanted was to implement the same api as the

def upload(self, source, target, callback=None):

method, which behavior will depend on the type of source parameter. If it is an instanceof str than the method will be the same as of today but if the it's of type BytesIO than LocalFile class obj method will not try to treat it as path to a file and open it...

I'm thinking now of doing something similar and allowing a file-like object as an alternative to source in the upload* methods and target in the download* methods. This would of course include BytesIO objects. However, before making any promises I'll need to check how this would fit into the *File API in the file_transfer module.

Should I just inherit from FtpHost and override the above mentioned methods?

  • The option you described above which uses the file like objects would work of course, but it is too verbose and I would like to be able to use the upload/download methods with a BytesIO type parameters.

If you want to do this in several places, I didn't expect you to write the suggested code everytime. :-) Of course you can wrap this in a function or overridden method. Yes, I think it would make sense to inherit from FTPHost and override upload and possibly other upload/download method(s) so that they behave as I described in my previous paragraph. But there's a caveat:

A possible downside of overriding the upload* and/or download* methods is that you would "have to" support the callback argument because objects of subclasses should be able to stand in for objects of their baseclass (see https://www.tomdalling.com/blog/software-design/solid-class-design-the-liskov-substitution-principle/ ). That said, if you limit the access to the subclass enough or you're sure you don't need the callback functionality, it may be a fair tradeoff not to support the callback parameter or support it later if you need to. You have to be aware of the risk though that code that uses FTPHost objects in general may break if it happens to use a callback argument with the methods from your subclass.

Note: See TracTickets for help on using tickets.