Add support for entry points
Change-Id: I11fd40645479a815b29afaac0b12d45f6f7e6c21
This commit is contained in:
parent
98a0fb2405
commit
02bcffc174
42
README.rst
42
README.rst
@ -245,10 +245,44 @@ How to extend
|
|||||||
|
|
||||||
Given the ever-widening OpenStack ecosystem, OSPurge can't support every
|
Given the ever-widening OpenStack ecosystem, OSPurge can't support every
|
||||||
OpenStack services. We intend to support in-tree, only the 'core' services.
|
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
|
Fortunately, OSPurge is easily extensible. There are 2 methods and you can
|
||||||
Python module in the ``resources`` package and define one or more Python
|
chose the one you prefer:
|
||||||
class(es) that subclass ``ospurge.resources.base.ServiceResource``. Your module
|
|
||||||
will automatically be loaded and your methods called. Have a look at the
|
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.
|
``main.main`` and ``main.runner`` functions to fully understand the mechanism.
|
||||||
|
|
||||||
Note: We won't accept any patch that broaden what OSPurge supports, beyond
|
Note: We won't accept any patch that broaden what OSPurge supports, beyond
|
||||||
|
28
ospurge/tests/resources/entry_points.py
Normal file
28
ospurge/tests/resources/entry_points.py
Normal 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"
|
@ -10,9 +10,15 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import types
|
||||||
import typing
|
import typing
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
import shade
|
import shade
|
||||||
|
|
||||||
from ospurge.resources.base import ServiceResource
|
from ospurge.resources.base import ServiceResource
|
||||||
@ -20,6 +26,21 @@ from ospurge.tests import mock
|
|||||||
from ospurge import utils
|
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):
|
class TestUtils(unittest.TestCase):
|
||||||
def test_replace_project_info_in_config(self):
|
def test_replace_project_info_in_config(self):
|
||||||
config = {
|
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):
|
def test_get_all_resource_classes(self):
|
||||||
classes = utils.get_resource_classes()
|
classes = utils.get_resource_classes()
|
||||||
self.assertIsInstance(classes, typing.List)
|
self.assertIsInstance(classes, typing.List)
|
||||||
|
@ -17,24 +17,45 @@ import os
|
|||||||
import pkgutil
|
import pkgutil
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
|
|
||||||
from ospurge.resources import base
|
from ospurge.resources import base
|
||||||
|
|
||||||
|
|
||||||
def get_resource_classes(resources=None):
|
ENTRY_POINTS_NAME = 'ospurge_resources'
|
||||||
"""
|
|
||||||
Import all the modules in the `resources` package 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.
|
def load_ospurge_resource_modules():
|
||||||
"""
|
"""Import all the modules in the `resources` package."""
|
||||||
|
modules = {}
|
||||||
iter_modules = pkgutil.iter_modules(
|
iter_modules = pkgutil.iter_modules(
|
||||||
[os.path.join(os.path.dirname(__file__), 'resources')],
|
[os.path.join(os.path.dirname(__file__), 'resources')],
|
||||||
prefix='ospurge.resources.'
|
prefix='ospurge.resources.'
|
||||||
)
|
)
|
||||||
for (_, name, ispkg) in iter_modules:
|
for (_, name, ispkg) in iter_modules:
|
||||||
if not ispkg:
|
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__()
|
all_classes = base.ServiceResource.__subclasses__()
|
||||||
|
|
||||||
|
@ -17,6 +17,15 @@ from typing import Iterable
|
|||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import TypeVar
|
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:
|
def get_resource_classes(resources: Optional[Iterable[str]]=None) -> List:
|
||||||
|
Loading…
Reference in New Issue
Block a user