Add support for entry points

Change-Id: I11fd40645479a815b29afaac0b12d45f6f7e6c21
This commit is contained in:
Yves-Gwenael Bourhis 2019-03-12 16:04:51 +01:00
parent 98a0fb2405
commit 02bcffc174
5 changed files with 144 additions and 12 deletions

View File

@ -245,10 +245,44 @@ How to extend
Given the ever-widening OpenStack ecosystem, OSPurge can't support every
OpenStack services. We intend to support in-tree, only the 'core' services.
Fortunately, OSPurge is easily extensible. All you have to do is add a new
Python module in the ``resources`` package and define one or more Python
class(es) that subclass ``ospurge.resources.base.ServiceResource``. Your module
will automatically be loaded and your methods called. Have a look at the
Fortunately, OSPurge is easily extensible. There are 2 methods and you can
chose the one you prefer:
1: Add a new Python module in the ``resources`` package and define one or more
Python class(es) that subclass ``ospurge.resources.base.ServiceResource``.
Your module will automatically be loaded and your methods called.
2: Create your standalone python modules and in your module's setup.py or
setup.cfg file add an entry point to ``ospurge_resources`` pointing to the
python module in which you subclass ``ospurge.resources.base.ServiceResource``.
setup.py example::
from setuptools import setup
setup(
name='my_ospurge_extension',
entry_points={
'ospurge_resources': [
'foo = my_module.submodule_with_subclass',
],
}
)
setup.cfg example::
[entry_points]
ospurge_resources =
foo = my_module.submodule_with_subclass
Once your module installed, it will automatically be loaded and your methods
called.
More examples on entry points:
https://amir.rachum.com/blog/2017/07/28/python-entry-points/
Have a look
at the
``main.main`` and ``main.runner`` functions to fully understand the mechanism.
Note: We won't accept any patch that broaden what OSPurge supports, beyond

View File

@ -0,0 +1,28 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from ospurge.resources.base import ServiceResource
class Foo(ServiceResource):
ORDER = 15
def list(self):
return []
def delete(self, resource):
pass
@staticmethod
def to_str(resource):
return "Foo"

View File

@ -10,9 +10,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
import os
import types
import typing
import unittest
import pkg_resources
import six
import shade
from ospurge.resources.base import ServiceResource
@ -20,6 +26,21 @@ from ospurge.tests import mock
from ospurge import utils
def register_test_entry_point():
test_resource_file = os.path.abspath(
os.path.join(
os.path.dirname(__file__), 'resources/entry_points.py'
)
)
distribution = pkg_resources.Distribution.from_filename(test_resource_file)
entry_point = pkg_resources.EntryPoint(
'foo', 'ospurge.tests.resources.entry_points', dist=distribution
)
distribution._ep_map = {utils.ENTRY_POINTS_NAME: {'foo': entry_point}}
pkg_resources.working_set.add(distribution, 'foo')
return entry_point
class TestUtils(unittest.TestCase):
def test_replace_project_info_in_config(self):
config = {
@ -43,6 +64,25 @@ class TestUtils(unittest.TestCase):
}
})
def test_load_ospurge_resource_modules(self):
modules = utils.load_ospurge_resource_modules()
self.assertIsInstance(modules, typing.Dict)
for name, module in six.iteritems(modules):
# assertIsInstance(name, typing.AnyStr) fails with:
# TypeError: Type variables cannot be used with isinstance().
self.assertIsInstance(name, six.string_types)
self.assertIsInstance(module, types.ModuleType)
def test_load_entry_points_modules(self):
register_test_entry_point()
modules = utils.load_entry_points_modules()
self.assertIsInstance(modules, typing.Dict)
for name, module in six.iteritems(modules):
# assertIsInstance(name, typing.AnyStr) fails with:
# TypeError: Type variables cannot be used with isinstance().
self.assertIsInstance(name, six.string_types)
self.assertIsInstance(module, types.ModuleType)
def test_get_all_resource_classes(self):
classes = utils.get_resource_classes()
self.assertIsInstance(classes, typing.List)

View File

@ -17,24 +17,45 @@ import os
import pkgutil
import re
import pkg_resources
from ospurge.resources import base
def get_resource_classes(resources=None):
"""
Import all the modules in the `resources` package and return all the
subclasses of the `ServiceResource` ABC that match the `resources` arg.
ENTRY_POINTS_NAME = 'ospurge_resources'
This way we can easily extend OSPurge by just adding a new file in the
`resources` dir.
"""
def load_ospurge_resource_modules():
"""Import all the modules in the `resources` package."""
modules = {}
iter_modules = pkgutil.iter_modules(
[os.path.join(os.path.dirname(__file__), 'resources')],
prefix='ospurge.resources.'
)
for (_, name, ispkg) in iter_modules:
if not ispkg:
importlib.import_module(name)
modules[name] = importlib.import_module(name)
return modules
def load_entry_points_modules(name=ENTRY_POINTS_NAME):
"""Import all modules in the `name` entry point."""
entry_points = {}
for entry_point in pkg_resources.iter_entry_points(name):
entry_points[entry_point.name] = entry_point.load()
return entry_points
def get_resource_classes(resources=None):
"""
Load all ospurge resource and entry point modules and return all the
subclasses of the `ServiceResource` ABC that match the `resources` arg.
This way we can easily extend OSPurge by just adding a new file in the
`resources` dir or a package with `ENTRY_POINTS_NAME` entry point.
"""
load_ospurge_resource_modules()
load_entry_points_modules()
all_classes = base.ServiceResource.__subclasses__()

View File

@ -17,6 +17,15 @@ from typing import Iterable
from typing import List
from typing import Optional
from typing import TypeVar
from typing import AnyStr
def load_ospurge_resource_modules() -> Dict:
...
def load_entry_points_modules(name: AnyStr) -> Dict:
...
def get_resource_classes(resources: Optional[Iterable[str]]=None) -> List: