Skip to content

Commit 5368bac

Browse files
committed
tests and opener changes
1 parent 9f5901d commit 5368bac

8 files changed

Lines changed: 73 additions & 70 deletions

File tree

docs/source/extension.rst

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@ Creating an extension
44
=====================
55

66
Once a filesystem has been implemented, it can be integrated with other
7-
projects and applications using PyFilesystem.
7+
applications and projects using PyFilesystem.
88

99

1010
Naming Convention
1111
-----------------
1212

1313
For visibility in PyPi, we recommend that your package be prefixed with
14-
``fs-``, for example your package may be named `fs-awesome` or `fs-
15-
awesomefs`.
14+
``fs-``. For instance if you have implemented an ``AwesomeFS``
15+
PyFilesystem class, your packaged could be be named ``fs-awesome`` or
16+
``fs-awesomefs``.
1617

1718

1819
Opener
1920
------
2021

21-
In order for your filesystem to be opened through :doc:`openers` like
22-
the other builtin filesystems, you should define a
23-
:class:`~fs.opener.base.Opener` class for your filesystem.
22+
In order for your filesystem to be opened with an :ref:`FS URL <fs-urls>`
23+
you should define an :class:`~fs.opener.base.Opener` class.
2424

2525
Here's an example taken from an Amazon S3 Filesystem::
2626

@@ -51,26 +51,25 @@ Here's an example taken from an Amazon S3 Filesystem::
5151
)
5252
return s3fs
5353

54-
My convention this would be declared in ``opener.py``.
54+
By convention this would be defined in ``opener.py``.
5555

5656

5757
To register the opener you will need to define an `entry point
5858
<http://setuptools.readthedocs.io/en/latest/setuptools.html?highlight=entry%20points#dynamic-discovery-of-services-and-plugins>`_
5959
in your setup.py. See below for an example.
6060

6161

62-
The ``setup.py`` file
63-
---------------------
62+
The setup.py file
63+
-----------------
6464

6565
Refer to the `setuptools documentation <https://setuptools.readthedocs.io/>`_
6666
to see how to write a ``setup.py`` file. There are only a few things that
6767
should be kept in mind when creating a Pyfilesystem2 extension. Make sure that:
6868

69-
* ``fs`` is in the ``install_requires`` list, in order to always have
70-
Pyfilesystem2 installed before your extension. You should reference
71-
the version number with the ``~=`` operator which ensures that the
72-
install will get any bugfix releases of PyFilesystem but not any
73-
potentially breaking changes.
69+
* ``fs`` is in the ``install_requires`` list. You should reference the
70+
version number with the ``~=`` operator which ensures that the install
71+
will get any bugfix releases of PyFilesystem but not any potentially
72+
breaking changes.
7473
* Ìf you created an opener, include it as an ``fs.opener`` entry point,
7574
using the name of the entry point as the protocol to be used.
7675

@@ -100,27 +99,23 @@ Here is an minimal ``setup.py`` for our project:
10099
Good Practices
101100
--------------
102101

103-
* Keep track of your achievements ! Add ``__version__``, ``__author__``,
104-
``__author_email__`` and ``__license__`` variables to your project
105-
(either in ``fs/awesomefs.py`` or ``fs/awesomefs/__init__.py`` depending
106-
on the chosen structure), containing:
107-
108-
``__version__``
109-
the version of the extension (use `Semantic Versioning
110-
<http://semver.org/>`_ if possible !)
102+
Keep track of your achievements! Add the following values to your ``__init__.py``:
111103

112-
``__author__``
113-
your name(s)
104+
* ``__version__`` The version of the extension (we recommend following
105+
`Semantic Versioning <http://semver.org/>`_),
106+
* ``__author__`` Your name(s).
107+
* ``__author_email__`` Your email(s).
108+
* ``__license__`` The module's license.
114109

115-
``__author_email__``
116-
your email(s)
110+
Let us Know
111+
-----------
117112

118-
``__license__``
119-
the license of the subpackage
113+
Contact us to add your filesystem to the `PyFilesystem Wiki <https://www.pyfilesystem.org/page/index-of-filesystems/>`_.
120114

121115

122116
Live Example
123117
------------
124118

125119
See `fs.sshfs <https://github.com/althonos/fs.sshfs>`_ for a functioning
126-
PyFilesystem2 extension implementing a Pyfilesystem2 filesystem over SSH.
120+
PyFilesystem2 extension implementing a Pyfilesystem2 filesystem over
121+
SSH.

docs/source/openers.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _fs-urls:
2+
13
FS URLs
24
=======
35

fs/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.0.6a2"
1+
__version__ = "2.0.6a3"

fs/base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -846,8 +846,7 @@ def open(self,
846846
``utf-8``)
847847
:param str errors: What to do with unicode decode errors (see
848848
`stdlib docs <https://docs.python.org/3/library/codecs.html#error-handlers>`_)
849-
:param str newline: New line parameter (See `stdlib docs
850-
<https://docs.python.org/3/library/functions.html#open>`_)
849+
:param str newline: New line parameter (See stdlib docs).
851850
:param options: Additional keyword parameters to set
852851
implementation specific options (if required). See
853852
implementation docs for details.

fs/opener/__init__.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212

1313
# Import objects into fs.opener namespace
1414
from .base import Opener
15-
from .errors import OpenerError, ParseError, Unsupported
16-
from .registry import registry, Registry
15+
from .registry import registry
1716

1817
# Alias functions defined as Registry methods
1918
open_fs = registry.open_fs
@@ -24,11 +23,7 @@
2423
# __all__ with aliases and classes
2524
__all__ = [
2625
"registry",
27-
"Registry",
2826
"Opener",
29-
"OpenerError",
30-
"ParseError",
31-
"Unsupported",
3227
'open_fs',
3328
'open',
3429
'manage_fs',

fs/opener/errors.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
fs.opener.errors
44
================
55
6-
Errors raised when attempting to open a filesystem
6+
Errors raised when attempting to open a filesystem.
7+
78
"""
89

910
class ParseError(ValueError):
@@ -13,9 +14,11 @@ class ParseError(ValueError):
1314
class OpenerError(Exception):
1415
"""Base class for opener related errors."""
1516

16-
class Unsupported(OpenerError):
17-
"""May be raised by opener if the opener fails to open a FS."""
17+
18+
class UnsupportedProtocol(OpenerError):
19+
"""May be raised if no opener could be found for a given
20+
protocol."""
1821

1922

20-
class EntryPointError(OpenerError, RuntimeError):
23+
class EntryPointError(OpenerError):
2124
"""Raised by the registry when an entry point cannot be loaded."""

fs/opener/registry.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
Defines the Registry, which maps protocols and FS URLs to their
77
respective Opener.
8+
89
"""
910

1011
from __future__ import absolute_import
@@ -18,7 +19,7 @@
1819
import pkg_resources
1920

2021
from .base import Opener
21-
from .errors import ParseError, Unsupported, EntryPointError
22+
from .errors import ParseError, UnsupportedProtocol, EntryPointError
2223

2324

2425
class Registry(object):
@@ -56,12 +57,11 @@ class Registry(object):
5657
@classmethod
5758
def parse(cls, fs_url):
5859
"""
59-
Parse a Filesystem URL and return a :class:`ParseResult`, or raise
60-
:class:`ParseError` (subclass of ValueError) if the FS URL is
61-
not value.
60+
Parse a Filesystem URL and return a :class:`ParseResult`, or
61+
raise :class:`ParseError` (subclass of ValueError) if the FS URL
62+
is not value.
6263
63-
:param fs_url: A filesystem URL
64-
:type fs_url: str
64+
:param str fs_url: A filesystem URL
6565
:rtype: :class:`ParseResult`
6666
6767
"""
@@ -85,13 +85,6 @@ def parse(cls, fs_url):
8585
path
8686
)
8787

88-
@property
89-
def protocols(self):
90-
return [
91-
entry_point.name
92-
for entry_point in pkg_resources.iter_entry_points('fs.opener')
93-
]
94-
9588
def __init__(self, default_opener='osfs'):
9689
"""
9790
Create a registry object.
@@ -102,27 +95,34 @@ def __init__(self, default_opener='osfs'):
10295
10396
"""
10497
self.default_opener = default_opener
98+
self.protocols = [
99+
entry_point.name
100+
for entry_point in
101+
pkg_resources.iter_entry_points('fs.opener')
102+
]
105103

106104
def __repr__(self):
107105
return "<fs-registry {!r}>".format(self.protocols)
108106

109107
def get_opener(self, protocol):
110-
"""Get the opener class associated to a given protocol.
108+
"""
109+
Get the opener class associated to a given protocol.
111110
112-
:param str protocol: A filesystem URL protocol
113-
:rtype: ``Opener``
114-
:raises `~fs.opener.errors.Unsupported`: If no opener could be found
115-
:raises EntryPointLoadingError: If the returned entry point is
116-
not an ``Opener`` subclass or could not be loaded
111+
:param str protocol: A filesystem protocol.
112+
:rtype: ``Opener``.
113+
:raises `~fs.opener.errors.UnsupportedProtocol`: If no opener could be found.
114+
:raises `EntryPointLoadingError`: If the returned entry
115+
point is not an ``Opener`` subclass or could not be loaded
117116
successfully.
117+
118118
"""
119119
entry_point = next(
120120
pkg_resources.iter_entry_points('fs.opener', protocol),
121121
None
122122
)
123123

124124
if entry_point is None:
125-
raise Unsupported(
125+
raise UnsupportedProtocol(
126126
"protocol '{}' is not supported".format(protocol)
127127
)
128128

tests/test_opener.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99

1010
from fs import opener
1111
from fs.osfs import OSFS
12-
from fs.opener import errors
12+
from fs.opener import registry, errors
1313
from fs.memoryfs import MemoryFS
1414

1515

1616
class TestParse(unittest.TestCase):
1717

18+
def test_registry_repr(self):
19+
str(registry)
20+
repr(registry)
21+
1822
def test_parse_not_url(self):
19-
with self.assertRaises(opener.ParseError):
23+
with self.assertRaises(errors.ParseError):
2024
parsed = opener.parse('foo/bar')
2125

2226
def test_parse_simple(self):
@@ -66,15 +70,21 @@ def test_parse_path(self):
6670
class TestRegistry(unittest.TestCase):
6771

6872
def test_registry_protocols(self):
69-
# Check regitry.protocols list the names of all available entry points
70-
entry_map = pkg_resources.get_entry_map('fs', 'fs.opener')
73+
# Check registry.protocols list the names of all available entry points
74+
75+
protocols = [
76+
entry_point.name
77+
for entry_point in
78+
pkg_resources.iter_entry_points('fs.opener')
79+
]
80+
7181
self.assertEqual(
72-
sorted(entry_map.keys()),
82+
sorted(protocols),
7383
sorted(opener.registry.protocols)
7484
)
7585

7686
def test_unknown_protocol(self):
77-
with self.assertRaises(opener.Unsupported):
87+
with self.assertRaises(errors.UnsupportedProtocol):
7888
opener.open_fs('unknown://')
7989

8090
def test_entry_point_load_error(self):
@@ -119,7 +129,6 @@ def test_entry_point_create_error(self):
119129
'could not instantiate opener; some creation error', str(ctx.exception))
120130

121131

122-
123132
class TestManageFS(unittest.TestCase):
124133

125134
def test_manage_fs_url(self):
@@ -139,9 +148,9 @@ def test_manage_fs_error(self):
139148
1/0
140149
except ZeroDivisionError:
141150
pass
142-
143151
self.assertTrue(mem_fs.isclosed())
144152

153+
145154
class TestOpeners(unittest.TestCase):
146155

147156
def test_repr(self):

0 commit comments

Comments
 (0)