Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fs/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.0.8"
__version__ = "2.0.9a0"
18 changes: 18 additions & 0 deletions fs/appfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class UserDataFS(_AppFS):
"""
A filesystem for per-user application data.

May also be opened with
``open_fs('userdata://appname:author:version')``.

:param str appname: The name of the application.
:param str author: The name of the author (used on Windows).
:param str version: Optional version string, if a unique location
Expand All @@ -80,6 +83,9 @@ class UserConfigFS(_AppFS):
"""
A filesystem for per-user config data.

May also be opened with
``open_fs('userconf://appname:author:version')``.

:param str appname: The name of the application.
:param str author: The name of the author (used on Windows).
:param str version: Optional version string, if a unique location
Expand All @@ -97,6 +103,9 @@ class UserCacheFS(_AppFS):
"""
A filesystem for per-user application cache data.

May also be opened with
``open_fs('usercache://appname:author:version')``.

:param str appname: The name of the application.
:param str author: The name of the author (used on Windows).
:param str version: Optional version string, if a unique location
Expand All @@ -114,6 +123,9 @@ class SiteDataFS(_AppFS):
"""
A filesystem for application site data.

May also be opened with
``open_fs('sitedata://appname:author:version')``.

:param str appname: The name of the application.
:param str author: The name of the author (used on Windows).
:param str version: Optional version string, if a unique location
Expand All @@ -131,6 +143,9 @@ class SiteConfigFS(_AppFS):
"""
A filesystem for application config data.

May also be opened with
``open_fs('siteconf://appname:author:version')``.

:param str appname: The name of the application.
:param str author: The name of the author (used on Windows).
:param str version: Optional version string, if a unique location
Expand All @@ -148,6 +163,9 @@ class UserLogFS(_AppFS):
"""
A filesystem for per-user application log data.

May also be opened with
``open_fs('userlog://appname:author:version')``.

:param str appname: The name of the application.
:param str author: The name of the author (used on Windows).
:param str version: Optional version string, if a unique location
Expand Down
2 changes: 1 addition & 1 deletion fs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,7 @@ def check(self):
Check a filesystem may be used.

Will throw a :class:`~fs.errors.FilesystemClosed` if the
filesystem is closed.
filesystem is closed.

:returns: None
:raises fs.errors.FilesystemClosed: if the filesystem
Expand Down
1 change: 0 additions & 1 deletion fs/iotools.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ def __iter__(self):
return iter(self._f)



def make_stream(name,
bin_file,
mode='r',
Expand Down
13 changes: 12 additions & 1 deletion fs/mountfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from __future__ import print_function
from __future__ import unicode_literals

from six import text_type

from . import errors
from .base import FS
from .memoryfs import MemoryFS
Expand Down Expand Up @@ -67,10 +69,19 @@ def mount(self, path, fs):

:param path: A path within the MountFS.
:type path: str
:param fs: A filesystem object to mount.
:param fs: A filesystem object or FS URL to mount.
:type fs: :class:`~fs.base.FS`

"""
if isinstance(fs, text_type):
from .opener import open_fs
fs = open_fs(fs)

if not isinstance(fs, FS):
raise TypeError(
'fs argument must be an FS object or a FS URL'
)

if fs is self:
raise ValueError('Unable to mount self')
_path = forcedir(abspath(normpath(path)))
Expand Down
20 changes: 17 additions & 3 deletions fs/multifs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@
from collections import namedtuple
from operator import itemgetter

from six import text_type

from .base import FS
from .mode import check_writable
from . import errors
from .opener import open_fs
from .path import abspath, normpath


_PrioritizedFS = namedtuple(
'_PrioritizedFS',
['priority', 'fs']
[
'priority',
'fs'
]
)


Expand Down Expand Up @@ -61,8 +67,8 @@ def add_fs(self, name, fs, write=False, priority=0):
:param name: A unique name to refer to the filesystem being
added.
:type name: str
:param fs: The filesystem to add
:type fs: Filesystem
:param fs: The filesystem to add.
:type fs: Filesystem or FS URL.
:param write: If this value is True, then the ``fs`` will be
used as the writeable FS.
:type write: bool
Expand All @@ -75,6 +81,14 @@ def add_fs(self, name, fs, write=False, priority=0):

"""

if isinstance(fs, text_type):
fs = open_fs(fs)

if not isinstance(fs, FS):
raise TypeError(
'fs argument should be an FS object or FS URL'
)

self._filesystems[name] = _PrioritizedFS(
priority=(priority, self._sort_index),
fs=fs
Expand Down
55 changes: 55 additions & 0 deletions fs/opener/appfs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# coding: utf-8
"""Defines the MemOpener."""

from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

from .base import Opener
from .errors import OpenerError
from .. import appfs


class AppFSOpener(Opener):

protocols = [
'userdata',
'userconf',
'sitedata',
'siteconf',
'usercache',
'userlog'
]

_protocol_mapping = {
'userdata': appfs.UserDataFS,
'userconf': appfs.UserConfigFS,
'sitedata': appfs.SiteDataFS,
'siteconf': appfs.SiteConfigFS,
'usercache': appfs.UserCacheFS,
'userlog': appfs.UserLogFS
}

def open_fs(self, fs_url, parse_result, writeable, create, cwd):
fs_class = self._protocol_mapping[parse_result.protocol]

tokens = parse_result.resource.split(':', 3)
if len(tokens) == 2:
appname, author = tokens
version = None
elif len(tokens) == 3:
appname, author, version = tokens
else:
raise OpenerError(
'resource should be <appname>:<author> '
'or <appname>:<author>:<version>'
)

fs_instance = fs_class(
appname,
author=author,
version=version,
create=create
)
return fs_instance

2 changes: 0 additions & 2 deletions fs/wrapfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ def delegate_fs(self):
"""
return self._wrap_fs


def appendbytes(self, path, data):
self.check()
_fs, _path = self.delegate_path(path)
Expand All @@ -88,7 +87,6 @@ def appendtext(self, path, text,
errors=errors,
newline=newline)


def getinfo(self, path, namespaces=None):
self.check()
_fs, _path = self.delegate_path(path)
Expand Down
7 changes: 7 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@
'tar = fs.opener.tarfs:TarOpener',
'temp = fs.opener.tempfs:TempOpener',
'zip = fs.opener.zipfs:ZipOpener',

'userdata = fs.opener.appfs:AppFSOpener',
'userconf = fs.opener.appfs:AppFSOpener',
'sitedata = fs.opener.appfs:AppFSOpener',
'siteconf = fs.opener.appfs:AppFSOpener',
'usercache = fs.opener.appfs:AppFSOpener',
'userlog = fs.opener.appfs:AppFSOpener',
]},
license="MIT",
long_description=DESCRIPTION,
Expand Down
10 changes: 8 additions & 2 deletions tests/test_mountfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,21 @@ def make_fs(self):

class TestMountFSBehaviours(unittest.TestCase):

def test_bad_mount(self):
mount_fs = MountFS()
with self.assertRaises(TypeError):
mount_fs.mount('foo', 5)
with self.assertRaises(TypeError):
mount_fs.mount('foo', b'bar')

def test_listdir(self):
mount_fs = MountFS()
self.assertEqual(mount_fs.listdir('/'), [])
m1 = MemoryFS()
m2 = TempFS()
m3 = MemoryFS()
m4 = TempFS()
mount_fs.mount('/m1', m1)
mount_fs.mount('/m2', m2)
mount_fs.mount('/m2', 'temp://')
mount_fs.mount('/m3', m3)
with self.assertRaises(MountError):
mount_fs.mount('/m3/foo', m4)
Expand Down
12 changes: 12 additions & 0 deletions tests/test_multifs.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ def test_no_auto_close(self):
self.assertFalse(m1.isclosed())
self.assertFalse(m2.isclosed())

def test_opener(self):
"""Test use of FS URLs."""
multi_fs = MultiFS()
with self.assertRaises(TypeError):
multi_fs.add_fs(u'foo', 5)
multi_fs.add_fs(u'f1', u'mem://')
multi_fs.add_fs(u'f2', u'temp://')
self.assertIsInstance(
multi_fs.get_fs(u'f1'),
MemoryFS
)

def test_priority(self):
"""Test priority order is working"""
m1 = MemoryFS()
Expand Down
21 changes: 21 additions & 0 deletions tests/test_opener.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,24 @@ def test_open_fs(self):
mem_fs = opener.open_fs("mem://")
mem_fs_2 = opener.open_fs(mem_fs)
self.assertEqual(mem_fs, mem_fs_2)

def test_open_userdata(self):
with self.assertRaises(errors.OpenerError):
opener.open_fs('userdata://foo:bar:baz:egg')

app_fs = opener.open_fs(
'userdata://fstest:willmcgugan:1.0',
create=True
)
self.assertEqual(app_fs.app_dirs.appname, 'fstest')
self.assertEqual(app_fs.app_dirs.appauthor, 'willmcgugan')
self.assertEqual(app_fs.app_dirs.version, '1.0')

def test_open_userdata_no_version(self):
app_fs = opener.open_fs(
'userdata://fstest:willmcgugan',
create=True
)
self.assertEqual(app_fs.app_dirs.appname, 'fstest')
self.assertEqual(app_fs.app_dirs.appauthor, 'willmcgugan')
self.assertEqual(app_fs.app_dirs.version, None)