Skip to content

Commit

Permalink
Merge pull request #2 from eflglobal/release/2.0.2
Browse files Browse the repository at this point in the history
Release/2.0.2
  • Loading branch information
todofixthis authored Aug 7, 2017
2 parents 988e8c3 + 5f3de42 commit f3244c1
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dist: trusty
language: python
python:
- '2.7'
Expand All @@ -9,7 +10,7 @@ deploy:
on:
branch: master
provider: pypi
distributions: 'sdist bdist_wheel'
distributions: 'bdist_wheel sdist'
user: efl_phx
password:
secure: XE0fhr1GgWTUgV9ssVYJkO+V9AehYJy2kf5T9T7YB1SQMLOeqr0QcpUEbKYgObxVuAu6gXd0WbUYSMcayblrWQdVwZ9f8/wadU9JPT+3QNTy7/S/hcExbKhzT6+BWZF0t87ln5t4W7WpKqrenyPFtreCmtSkjMBT0IsZ1hTIDGZwlsbvD4Owxa4FdAIGi5vWldM6Ffm1/R5RFrLNpVECDnGUIfQhggjIcu9/4W/fSueTwLNaiwG+CmLFqoXrfjut1EplX+N2NTcLobGKCqztr5PcgplrcDWaZI7mRKAZCIB3z9xEjLNZz7FVVIgodi+A3/Ih3z4or2C1n5KIaAhmCgvoDdzLY4mqofd53uxc3QxNVnZbcg62J6kr4XzVxjdPGmaw0WJDWO3iCfdUZwXMSwXBUdTXXAymsE/AxpvyN+rTrPGxO2NuYYQqSOLC9PlWZU2jzmbCDPT0shrOQvNMSlKZ/D13nuWdWhKPAka/yKq/tZErc8nLEa2EwcHesu4Z8RAFymNnuv//e+HsVQLxOoO82zXwKCS0zM9HA7L5xiP94EPEcT6qHFCo8HclfAJKVrCZLe1DE1IejEryIEIrVQzuzeoBwR9Dbf8v2KHT0S00O6d55l+C7m0HaPNS+UqYEljj3UR2avgt101ILzTG2ILhsBHGsD9lz0Tep0XhEdc=
24 changes: 19 additions & 5 deletions class_registry/entry_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,25 @@ class EntryPointClassRegistry(BaseRegistry):
"""
A class registry that loads classes using setuptools entry points.
"""
def __init__(self, group):
# type: (Text) -> None
def __init__(self, group, attr_name=None):
# type: (Text, Optional[Text]) -> None
"""
:param group:
The name of the entry point group that will be used to load
new classes.
:param attr_name:
If set, the registry will "brand" each class with its
corresponding registry key. This makes it easier to
perform reverse lookups later.
Note: if a class already defines this attribute, the
registry will overwrite it!
"""
super(EntryPointClassRegistry, self).__init__()

self.group = group
self.attr_name = attr_name
self.group = group

self._cache = None # type: Optional[Dict[Text, type]]
"""
Expand All @@ -46,9 +55,14 @@ def __repr__(self):

def get_class(self, key):
try:
return self._get_cache()[key]
cls = self._get_cache()[key]
except KeyError:
return self.__missing__(key)
cls = self.__missing__(key)

if self.attr_name:
setattr(cls, self.attr_name, key)

return cls

def items(self):
return iteritems(self._get_cache())
Expand Down
3 changes: 0 additions & 3 deletions class_registry/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ class BaseRegistry(with_metaclass(ABCMeta, Mapping)):
"""
Base functionality for registries.
"""
def __init__(self):
super(BaseRegistry, self).__init__()

def __dir__(self):
return list(self.keys())

Expand Down
43 changes: 43 additions & 0 deletions docs/entry_points.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,47 @@ Simply declare an :py:class:`EntryPointClassRegistry` instance, and it will
automatically find any classes registered to that entry point group across every
single installed project in your virtualenv!

---------------
Reverse Lookups
---------------
From time to time, you may need to perform a "reverse lookup": Given a class or
instance, you want to determine which registry key is associated with it.

For :py:class:`ClassRegistry`, performing a reverse lookup is simple because the
registry key is (usually) defined by an attribute on the class itself.

However, :py:class:`EntryPointClassRegistry` uses an external source to define
the registry keys, so it's a bit tricky to go back and find the registry key for
a given class.

If you would like to enable reverse lookups in your application, you can provide
an optional ``attr_name`` argument to the registry's initializer, which will
cause the registry to "brand" every object it returns with the corresponding
registry key.

.. code-block:: python
In [1]: from class_registry import EntryPointClassRegistry
In [2]: pokedex = EntryPointClassRegistry('pokemon', attr_name='element')
In [3]: fire_pokemon = pokedex['fire']
In [4]: fire_pokemon.element
Out[4]: 'fire'
In [5]: water_pokemon_class = pokedex.get_class('water')
In [6]: water_pokemon_class.element
Out[6]: 'water'
We set ``attr_name='element'`` when initializing the
:py:class:`EntryPointClassRegistry`, so it set the ``element`` attribute
on every class and instance that it returned.

.. caution::

If a class already has an attribute with the same name, the registry will
overwrite it.

.. _entry points: http://setuptools.readthedocs.io/en/latest/setuptools.html#dynamic-discovery-of-services-and-plugins
2 changes: 1 addition & 1 deletion docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ corresponding registry key:
sparky = pokedex['fire']
assert isinstance(sparky, Charizard)
Note in the above example that ``sparky`` is an _instance_ of ``Charizard``.
Note in the above example that ``sparky`` is an `instance` of ``Charizard``.

If you try to access a registry key that has no classes registered, it will
raise a :py:class:`class_registry.RegistryKeyError`:
Expand Down
24 changes: 5 additions & 19 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from codecs import StreamReader, open
from os.path import dirname, join, realpath
from sys import version_info

from setuptools import setup

Expand All @@ -17,36 +16,23 @@
long_description = f.read()


##
# For compatibility with versions of pip < 9, we will determine
# dependencies at runtime.
# Maybe once Travis upgrades their containers to use a newer version,
# we'll switch to the newer syntax (:
dependencies = [
'six',
]

if version_info[0] < 3:
# noinspection SpellCheckingInspection
dependencies.extend([
'typing', # 'typing; python_version < "3.0"',
])


##
# Off we go!
setup(
name = 'class-registry',
description = 'Factory+Registry pattern for Python classes.',
url = 'https://class-registry.readthedocs.io/',

version = '2.0.1',
version = '2.0.2',

packages = ['class_registry'],

long_description = long_description,

install_requires = dependencies,
install_requires = [
'six',
'typing; python_version < "3.0"',
],

test_suite = 'test',
test_loader = 'nose.loader:TestLoader',
Expand Down
13 changes: 13 additions & 0 deletions test/entry_points_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ def test_happy_path(self):
self.assertIsInstance(psychic, Mew)
self.assertEqual(psychic.name, 'snuggles')

def test_branding(self):
"""
Configuring the registry to "brand" each class with its
corresponding key.
"""
registry = EntryPointClassRegistry('pokemon', attr_name='poke_type')

fire_type = registry.get_class('fire')
self.assertEqual(getattr(fire_type, 'poke_type'), 'fire')

water = registry['water']
self.assertEqual(getattr(water, 'poke_type'), 'water')

def test_len(self):
"""
Getting the length of an entry point class registry.
Expand Down

0 comments on commit f3244c1

Please sign in to comment.