Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

experimental api refresh #248

Merged
merged 19 commits into from Jan 1, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [2.1.4] - Unreleased

A few methods have been renamed for greater clarity (but functionality remains the same).

The old methods are now aliases and will continue to work, but will
issue a deprecation warning via the `warnings` module.
Please update your code accordingly.

- `getbytes` -> `readbytes`
- `getfile` -> `download`
- `gettext` -> `readtext`
- `setbytes` -> `writebytes`
- `setbinfile` -> `upload`
- `settext` -> `writetext`

### Changed

- Changed default chunk size in `copy_file_data` to 1MB

## [2.1.3] - 2018-12-24

### Fixed
Expand Down
10 changes: 5 additions & 5 deletions docs/source/guide.rst
Expand Up @@ -90,13 +90,13 @@ FS objects have a :meth:`~fs.base.FS.close` methd which will perform any require
You can call ``close`` explicitly once you are finished using a filesystem. For example::

>>> home_fs = open_fs('osfs://~/')
>>> home_fs.settext('reminder.txt', 'buy coffee')
>>> home_fs.writetext('reminder.txt', 'buy coffee')
>>> home_fs.close()

If you use FS objects as a context manager, ``close`` will be called automatically. The following is equivalent to the previous example::

>>> with open_fs('osfs://~/') as home_fs:
... home_fs.settext('reminder.txt', 'buy coffee')
... home_fs.writetext('reminder.txt', 'buy coffee')

Using FS objects as a context manager is recommended as it will ensure every FS is closed.

Expand Down Expand Up @@ -158,7 +158,7 @@ The :class:`~fs.base.FS.makedir` and :class:`~fs.base.FS.makedirs` methods also
>>> home_fs = open_fs('~/')
>>> game_fs = home_fs.makedirs('projects/game')
>>> game_fs.touch('__init__.py')
>>> game_fs.settext('README.md', "Tetris clone")
>>> game_fs.writetext('README.md', "Tetris clone")
>>> game_fs.listdir('/')
['__init__.py', 'README.md']

Expand All @@ -176,9 +176,9 @@ You can open a file from a FS object with :meth:`~fs.base.FS.open`, which is ver

In the case of a ``OSFS``, a standard file-like object will be returned. Other filesystems may return a different object supporting the same methods. For instance, :class:`~fs.memoryfs.MemoryFS` will return a ``io.BytesIO`` object.

PyFilesystem also offers a number of shortcuts for common file related operations. For instance, :meth:`~fs.base.FS.getbytes` will return the file contents as a bytes, and :meth:`~fs.base.FS.gettext` will read unicode text. These methods is generally preferable to explicitly opening files, as the FS object may have an optimized implementation.
PyFilesystem also offers a number of shortcuts for common file related operations. For instance, :meth:`~fs.base.FS.readbytes` will return the file contents as a bytes, and :meth:`~fs.base.FS.readtext` will read unicode text. These methods is generally preferable to explicitly opening files, as the FS object may have an optimized implementation.

Other *shortcut* methods are :meth:`~fs.base.FS.setbin`, :meth:`~fs.base.FS.setbytes`, :meth:`~fs.base.FS.settext`.
Other *shortcut* methods are :meth:`~fs.base.FS.download`, :meth:`~fs.base.FS.upload`, :meth:`~fs.base.FS.writebytes`, :meth:`~fs.base.FS.writetext`.

Walking
~~~~~~~
Expand Down
12 changes: 6 additions & 6 deletions docs/source/implementers.rst
Expand Up @@ -90,14 +90,12 @@ In the general case, it is a good idea to look at how these methods are implemen
* :meth:`~fs.base.FS.copydir`
* :meth:`~fs.base.FS.create`
* :meth:`~fs.base.FS.desc`
* :meth:`~fs.base.FS.download`
* :meth:`~fs.base.FS.exists`
* :meth:`~fs.base.FS.filterdir`
* :meth:`~fs.base.FS.getbytes`
* :meth:`~fs.base.FS.getfile`
* :meth:`~fs.base.FS.getmeta`
* :meth:`~fs.base.FS.getsize`
* :meth:`~fs.base.FS.getsyspath`
* :meth:`~fs.base.FS.gettext`
* :meth:`~fs.base.FS.gettype`
* :meth:`~fs.base.FS.geturl`
* :meth:`~fs.base.FS.hassyspath`
Expand All @@ -111,15 +109,17 @@ In the general case, it is a good idea to look at how these methods are implemen
* :meth:`~fs.base.FS.movedir`
* :meth:`~fs.base.FS.open`
* :meth:`~fs.base.FS.opendir`
* :meth:`~fs.base.FS.readbytes`
* :meth:`~fs.base.FS.readtext`
* :meth:`~fs.base.FS.removetree`
* :meth:`~fs.base.FS.scandir`
* :meth:`~fs.base.FS.setbin`
* :meth:`~fs.base.FS.setbytes`
* :meth:`~fs.base.FS.setfile`
* :meth:`~fs.base.FS.settext`
* :meth:`~fs.base.FS.settimes`
* :meth:`~fs.base.FS.touch`
* :meth:`~fs.base.FS.upload`
* :meth:`~fs.base.FS.validatepath`
* :meth:`~fs.base.FS.writebytes`
* :meth:`~fs.base.FS.writetext`

.. _helper-methods:

Expand Down
2 changes: 1 addition & 1 deletion docs/source/info.rst
Expand Up @@ -17,7 +17,7 @@ Here's an example of retrieving file information::

>>> from fs.osfs import OSFS
>>> fs = OSFS('.')
>>> fs.settext('example.txt', 'Hello, World!')
>>> fs.writetext('example.txt', 'Hello, World!')
>>> info = fs.getinfo('example.txt', namespaces=['details'])
>>> info.name
'example.txt'
Expand Down
57 changes: 43 additions & 14 deletions fs/base.py
Expand Up @@ -15,7 +15,8 @@
import time
import typing
from contextlib import closing
from functools import partial
from functools import partial, wraps
import warnings

import six

Expand Down Expand Up @@ -61,6 +62,22 @@
__all__ = ["FS"]


def _new_name(method, old_name):
"""Return a method with a deprecation warning."""
# Looks suspiciously like a decorator, but isn't!
@wraps(method)
def _method(*args, **kwargs):
warnings.warn(
"method '{}' has been deprecated, please rename to '{}'".format(
old_name, method.__name__
),
DeprecationWarning,
)
return method(*args, **kwargs)

return _method


@six.add_metaclass(abc.ABCMeta)
class FS(object):
"""Base class for FS objects.
Expand Down Expand Up @@ -346,7 +363,7 @@ def close(self):

Example:
>>> with OSFS('~/Desktop') as desktop_fs:
... desktop_fs.settext(
... desktop_fs.writetext(
... 'note.txt',
... "Don't forget to tape Game of Thrones"
... )
Expand Down Expand Up @@ -379,7 +396,7 @@ def copy(self, src_path, dst_path, overwrite=False):
raise errors.DestinationExists(dst_path)
with closing(self.open(src_path, "rb")) as read_file:
# FIXME(@althonos): typing complains because open return IO
self.setbinfile(dst_path, read_file) # type: ignore
self.upload(dst_path, read_file) # type: ignore

def copydir(self, src_path, dst_path, create=False):
# type: (Text, Text, bool) -> None
Expand Down Expand Up @@ -550,7 +567,7 @@ def exclude_file(patterns, info):
iter_info = itertools.islice(iter_info, start, end)
return iter_info

def getbytes(self, path):
def readbytes(self, path):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think read/write is more intuitive that get/set in filesystem parlance. Although, writing to a file doesn't necessarily imply truncating it first. I'm hoping that the fact there is appendbytes and friends makes it clear what this does.

# type: (Text) -> bytes
"""Get the contents of a file as bytes.

Expand All @@ -568,7 +585,9 @@ def getbytes(self, path):
contents = read_file.read()
return contents

def getfile(self, path, file, chunk_size=None, **options):
getbytes = _new_name(readbytes, "getbytes")

def download(self, path, file, chunk_size=None, **options):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getfile has always felt awkward as it doesn't return a file, like gettext returns text.

From a network filesystem download makes total sense, it may not be quite so obvious for other filesystems, but I think it actually fits. It suggests (to me at least) that the data is being retrieved from the filesystem.

# type: (Text, BinaryIO, Optional[int], **Any) -> None
"""Copies a file from the filesystem to a file-like object.

Expand All @@ -591,14 +610,16 @@ def getfile(self, path, file, chunk_size=None, **options):

Example:
>>> with open('starwars.mov', 'wb') as write_file:
... my_fs.getfile('/movies/starwars.mov', write_file)
... my_fs.download('/movies/starwars.mov', write_file)

"""
with self._lock:
with self.openbin(path, **options) as read_file:
tools.copy_file_data(read_file, file, chunk_size=chunk_size)

def gettext(
getfile = _new_name(download, "getfile")

def readtext(
self,
path, # type: Text
encoding=None, # type: Optional[Text]
Expand Down Expand Up @@ -630,6 +651,8 @@ def gettext(
contents = read_file.read()
return contents

gettext = _new_name(readtext, "gettext")

def getmeta(self, namespace="standard"):
# type: (Text) -> Mapping[Text, object]
"""Get meta information regarding a filesystem.
Expand Down Expand Up @@ -1074,7 +1097,7 @@ def move(self, src_path, dst_path, overwrite=False):
with self._lock:
with self.open(src_path, "rb") as read_file:
# FIXME(@althonos): typing complains because open return IO
self.setbinfile(dst_path, read_file) # type: ignore
self.upload(dst_path, read_file) # type: ignore
self.remove(src_path)

def open(
Expand Down Expand Up @@ -1226,7 +1249,7 @@ def scandir(
iter_info = itertools.islice(iter_info, start, end)
return iter_info

def setbytes(self, path, contents):
def writebytes(self, path, contents):
# type: (Text, bytes) -> None
# FIXME(@althonos): accept bytearray and memoryview as well ?
"""Copy binary data to a file.
Expand All @@ -1244,7 +1267,9 @@ def setbytes(self, path, contents):
with closing(self.open(path, mode="wb")) as write_file:
write_file.write(contents)

def setbinfile(self, path, file):
setbytes = _new_name(writebytes, "setbytes")

def upload(self, path, file):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like download, I think upload better describes what this does than the clumsy setbinfile.

# type: (Text, BinaryIO) -> None
"""Set a file to the contents of a binary file object.

Expand All @@ -1263,14 +1288,16 @@ def setbinfile(self, path, file):

Example:
>>> with open('myfile.bin') as read_file:
... my_fs.setbinfile('myfile.bin', read_file)
... my_fs.upload('myfile.bin', read_file)

"""
with self._lock:
with self.open(path, "wb") as dst_file:
tools.copy_file_data(file, dst_file)

def setfile(
setbinfile = _new_name(upload, "setbinfile")

def writefile(
self,
path, # type: Text
file, # type: IO
Expand Down Expand Up @@ -1303,7 +1330,7 @@ def setfile(

Example:
>>> with open('myfile.bin') as read_file:
... my_fs.setfile('myfile.bin', read_file)
... my_fs.writefile('myfile.bin', read_file)

"""
mode = "wb" if encoding is None else "wt"
Expand Down Expand Up @@ -1340,7 +1367,7 @@ def settimes(self, path, accessed=None, modified=None):

self.setinfo(path, raw_info)

def settext(
def writetext(
self,
path, # type: Text
contents, # type: Text
Expand Down Expand Up @@ -1373,6 +1400,8 @@ def settext(
) as write_file:
write_file.write(contents)

settext = _new_name(writetext, "settext")

def touch(self, path):
# type: (Text) -> None
"""Touch a file on the filesystem.
Expand Down
2 changes: 1 addition & 1 deletion fs/compress.py
Expand Up @@ -98,7 +98,7 @@ def write_zip(
sys_path = src_fs.getsyspath(path)
except NoSysPath:
# Write from bytes
_zip.writestr(zip_info, src_fs.getbytes(path))
_zip.writestr(zip_info, src_fs.readbytes(path))
else:
# Write from a file which is (presumably)
# more memory efficient
Expand Down
8 changes: 4 additions & 4 deletions fs/copy.py
Expand Up @@ -138,10 +138,10 @@ def copy_file(
with _src_fs.lock(), _dst_fs.lock():
if _dst_fs.hassyspath(dst_path):
with _dst_fs.openbin(dst_path, "w") as write_file:
_src_fs.getfile(src_path, write_file)
_src_fs.download(src_path, write_file)
else:
with _src_fs.openbin(src_path) as read_file:
_dst_fs.setbinfile(dst_path, read_file)
_dst_fs.upload(dst_path, read_file)


def copy_file_internal(
Expand Down Expand Up @@ -171,10 +171,10 @@ def copy_file_internal(
src_fs.copy(src_path, dst_path, overwrite=True)
elif dst_fs.hassyspath(dst_path):
with dst_fs.openbin(dst_path, "w") as write_file:
src_fs.getfile(src_path, write_file)
src_fs.download(src_path, write_file)
else:
with src_fs.openbin(src_path) as read_file:
dst_fs.setbinfile(dst_path, read_file)
dst_fs.upload(dst_path, read_file)


def copy_file_if_newer(
Expand Down
16 changes: 6 additions & 10 deletions fs/ftpfs.py
Expand Up @@ -552,9 +552,7 @@ def _parse_mlsx(cls, lines):
raw_info["basic"] = {"name": name, "is_dir": is_dir}
raw_info["ftp"] = facts # type: ignore
raw_info["details"] = {
"type": (
int(ResourceType.directory if is_dir else ResourceType.file)
)
"type": (int(ResourceType.directory if is_dir else ResourceType.file))
}

details = raw_info["details"]
Expand Down Expand Up @@ -703,9 +701,7 @@ def removedir(self, path):
raise # pragma: no cover

def _scandir(
self,
path, # type: Text
namespaces=None # type: Optional[Container[Text]]
self, path, namespaces=None # type: Text # type: Optional[Container[Text]]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Black broke mymy comments. Todo

):
# type: (...) -> Iterator[Info]
_path = self.validatepath(path)
Expand Down Expand Up @@ -745,7 +741,7 @@ def scandir(
iter_info = itertools.islice(iter_info, start, end)
return iter_info

def setbinfile(self, path, file):
def upload(self, path, file):
# type: (Text, BinaryIO) -> None
_path = self.validatepath(path)
with self._lock:
Expand All @@ -755,18 +751,18 @@ def setbinfile(self, path, file):
str("STOR ") + _encode(_path, self.ftp.encoding), file
)

def setbytes(self, path, contents):
def writebytes(self, path, contents):
# type: (Text, ByteString) -> None
if not isinstance(contents, bytes):
raise TypeError("contents must be bytes")
self.setbinfile(path, io.BytesIO(contents))
self.upload(path, io.BytesIO(contents))

def setinfo(self, path, info):
# type: (Text, RawInfo) -> None
if not self.exists(path):
raise errors.ResourceNotFound(path)

def getbytes(self, path):
def readbytes(self, path):
# type: (Text) -> bytes
_path = self.validatepath(path)
data = io.BytesIO()
Expand Down