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

Drop usage of pkg_resources #553

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

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/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Python filesystem abstraction layer.
"""

__import__("pkg_resources").declare_namespace(__name__) # type: ignore
__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore

from . import path
from ._fscompat import fsdecode, fsencode
Expand Down
2 changes: 1 addition & 1 deletion fs/opener/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

# Declare fs.opener as a namespace package
__import__("pkg_resources").declare_namespace(__name__) # type: ignore
__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore

# Import opener modules so that `registry.install` if called on each opener
from . import appfs, ftpfs, memoryfs, osfs, tarfs, tempfs, zipfs
Expand Down
38 changes: 31 additions & 7 deletions fs/opener/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,44 @@

from __future__ import absolute_import, print_function, unicode_literals

import sys
import typing

import collections
import contextlib
import pkg_resources

from ..errors import ResourceReadOnly
from .base import Opener
from .errors import EntryPointError, UnsupportedProtocol
from .parse import parse_fs_url

if typing.TYPE_CHECKING:
from typing import Callable, Dict, Iterator, List, Text, Tuple, Type, Union
from typing import (
Callable,
Dict,
Iterator,
List,
Sequence,
Text,
Tuple,
Type,
Union,
)

from ..base import FS

if sys.version_info >= (3, 8):
# The entry_points function takes keyword arguments starting with Python 3.10
# https://docs.python.org/3.10/library/importlib.metadata.html#entry-points
from importlib import metadata as importlib_metadata
else:
import importlib_metadata


def _get_entry_points():
# type: () -> Sequence[importlib_metadata.EntryPoint]
return importlib_metadata.entry_points().get("fs.opener", [])


class Registry(object):
"""A registry for `Opener` instances."""
Expand Down Expand Up @@ -74,10 +96,7 @@ def protocols(self):
"""`list`: the list of supported protocols."""
_protocols = list(self._protocols)
if self.load_extern:
_protocols.extend(
entry_point.name
for entry_point in pkg_resources.iter_entry_points("fs.opener")
)
_protocols.extend(entry_point.name for entry_point in _get_entry_points())
_protocols = list(collections.OrderedDict.fromkeys(_protocols))
return _protocols

Expand All @@ -103,7 +122,12 @@ def get_opener(self, protocol):

if self.load_extern:
entry_point = next(
pkg_resources.iter_entry_points("fs.opener", protocol), None
(
entry_point
for entry_point in _get_entry_points()
if entry_point.name == protocol
),
None,
)
else:
entry_point = None
Expand Down
7 changes: 4 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ install_requires =
appdirs~=1.4.3
setuptools
six ~=1.10
enum34 ~=1.1.6 ; python_version < '3.4'
typing ~=3.6 ; python_version < '3.6'
backports.os ~=0.1 ; python_version < '3.0'
enum34 ~=1.1.6 ; python_version < '3.4'
typing ~=3.6 ; python_version < '3.6'
backports.os ~=0.1 ; python_version < '3.0'
importlib-metadata ~=2.0 ; python_version < '3.8'

[options.extras_require]
scandir =
Expand Down
76 changes: 51 additions & 25 deletions tests/test_opener.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
from __future__ import unicode_literals

import sys

import os
import pkg_resources
import shutil
import tempfile
import unittest
Expand All @@ -13,14 +10,19 @@
from fs.memoryfs import MemoryFS
from fs.opener import errors, registry
from fs.opener.parse import ParseResult
from fs.opener.registry import Registry
from fs.opener.registry import Registry, importlib_metadata
from fs.osfs import OSFS

try:
from unittest import mock
except ImportError:
import mock

try:
from pytest import MonkeyPatch
except ImportError:
from _pytest.monkeypatch import MonkeyPatch


class TestParse(unittest.TestCase):
def test_registry_repr(self):
Expand Down Expand Up @@ -106,34 +108,44 @@ def test_parse_params_decode(self):


class TestRegistry(unittest.TestCase):
def setUp(self):
self.monkeypatch = MonkeyPatch()

def test_protocols(self):
self.assertIsInstance(opener.registry.protocols, list)

def test_registry_protocols(self):
# Check registry.protocols list the names of all available extension
extensions = [
pkg_resources.EntryPoint("proto1", "mod1"),
pkg_resources.EntryPoint("proto2", "mod2"),
]
m = mock.MagicMock(return_value=extensions)
with mock.patch.object(
sys.modules["pkg_resources"], "iter_entry_points", new=m
):
class EntryPoint(object):
def __init__(self, name):
self.name = name

def _my_entry_points():
return {"fs.opener": [EntryPoint("proto1"), EntryPoint("proto2")]}

with self.monkeypatch.context() as m:
m.setattr(importlib_metadata, "entry_points", _my_entry_points)
self.assertIn("proto1", opener.registry.protocols)
self.assertIn("proto2", opener.registry.protocols)
self.assertNotIn("wheel", opener.registry.protocols)

def test_unknown_protocol(self):
with self.assertRaises(errors.UnsupportedProtocol):
opener.open_fs("unknown://")

def test_entry_point_load_error(self):
class EntryPoint(object):
def __init__(self, name):
self.name = name

entry_point = mock.MagicMock()
entry_point.load.side_effect = ValueError("some error")
def load(self):
raise ValueError("some error")

iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
def _my_entry_points():
return {"fs.opener": [EntryPoint("test")]}

with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
with self.monkeypatch.context() as m:
m.setattr(importlib_metadata, "entry_points", _my_entry_points)
with self.assertRaises(errors.EntryPointError) as ctx:
opener.open_fs("test://")
self.assertEqual(
Expand All @@ -144,11 +156,18 @@ def test_entry_point_type_error(self):
class NotAnOpener(object):
pass

entry_point = mock.MagicMock()
entry_point.load = mock.MagicMock(return_value=NotAnOpener)
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
class EntryPoint(object):
def __init__(self, name):
self.name = name

def load(self):
return NotAnOpener

with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
def _my_entry_points():
return {"fs.opener": [EntryPoint("test")]}

with self.monkeypatch.context() as m:
m.setattr(importlib_metadata, "entry_points", _my_entry_points)
with self.assertRaises(errors.EntryPointError) as ctx:
opener.open_fs("test://")
self.assertEqual("entry point did not return an opener", str(ctx.exception))
Expand All @@ -161,11 +180,18 @@ def __init__(self, *args, **kwargs):
def open_fs(self, *args, **kwargs):
pass

entry_point = mock.MagicMock()
entry_point.load = mock.MagicMock(return_value=BadOpener)
iter_entry_points = mock.MagicMock(return_value=iter([entry_point]))
class EntryPoint(object):
def __init__(self, name):
self.name = name

def load(self):
return BadOpener

def _my_entry_points():
return {"fs.opener": [EntryPoint("test")]}

with mock.patch("pkg_resources.iter_entry_points", iter_entry_points):
with self.monkeypatch.context() as m:
m.setattr(importlib_metadata, "entry_points", _my_entry_points)
with self.assertRaises(errors.EntryPointError) as ctx:
opener.open_fs("test://")
self.assertEqual(
Expand Down Expand Up @@ -217,7 +243,7 @@ def tearDown(self):

def test_repr(self):
# Check __repr__ works
for entry_point in pkg_resources.iter_entry_points("fs.opener"):
for entry_point in importlib_metadata.entry_points().get("fs.opener", []):
_opener = entry_point.load()
repr(_opener())

Expand Down