Skip to content

Commit ae053c5

Browse files
committed
Adds proper implementation of Lookup.get_default()
1 parent 97b1bd9 commit ae053c5

File tree

6 files changed

+208
-13
lines changed

6 files changed

+208
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
- Adds a EntryPointLookup.
88
- Adds a DelegatedLookup.
99
- Adds a ProxyLookup.
10+
- Adds a proper resolution for system default lookup Lookup.get_default().
1011
- Fixes issue with listeners registration disappearing immediately when using object-bound methods.
1112
- Content of a GenericLookup can now behave like a Container (ie. you can do things like "obj in content").
1213
- When an instance is not hashable, provides an alternative using id() of the object in order to be

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ my_content.remove(child1)
9191

9292
## Other lookups
9393

94+
* `lookups.Lookup.get_default()`: The default lookup in a system.
9495
* `lookups.ProxyLookup`: A lookup that merge results from several lookups.
9596
* `lookups.DelegatedLookup`: A lookup that redirects to another (dynamic) lookup, through a LookupProvider.
9697
* `lookups.EntryPointLookup`: A lookup loading its instances from a setuptools entry point group (ie. provided by any installed package).

lookups/lookup.py

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,66 @@ class Lookup(ABC):
2424
A general registry permitting clients to find instances of services (implementation of a given
2525
interface).
2626
27-
This class is inspired Netbeans Platform lookup mechanism. The Lookup API concentrates on the
27+
This class is inspired by Netbeans Platform lookup mechanism. The Lookup API concentrates on the
2828
lookup, not on the registration.
2929
'''
3030

31+
_DEFAULT_LOOKUP: Lookup = None # type: ignore
32+
_DEFAULT_LOOKUP_PROVIDER: LookupProvider = None # type: ignore
33+
_DEFAULT_ENTRY_POINT_GROUP = 'lookup.default'
34+
3135
@classmethod
3236
def get_default(cls) -> Lookup:
3337
'''
3438
Method to obtain the global lookup in the whole system.
3539
3640
The actual returned implementation can be different in different systems, but the default
37-
one is based on lookup.Lookups.???.
41+
one is based on lookups.ProxyLookup.
42+
43+
The resolution is the following:
44+
- If there is already a default lookup provider defined, returns its lookup.
45+
- If there is already a default lookup defined, returns it.
46+
- Loads on EntryPointLookup on 'lookup.default' group:
47+
- If it finds a lookup which happens to also be a lookup provider, returns its lookup.
48+
- If it finds a lookup, returns it.
49+
- If it finds a lookup provider, returns DelegatedLookup from it.
50+
- Otherwise, returns a ProxyLookup with just the EntryPointLookup as source.
3851
3952
:return: The global lookup in the system
4053
:rtype: Lookup
4154
'''
42-
# Temporary solution
43-
from .generic_lookup import GenericLookup
44-
from .instance_content import InstanceContent
45-
return GenericLookup(InstanceContent())
55+
56+
if (cls._DEFAULT_LOOKUP is not None) or (cls._DEFAULT_LOOKUP_PROVIDER is not None):
57+
if cls._DEFAULT_LOOKUP_PROVIDER is not None:
58+
lookup = cls._DEFAULT_LOOKUP_PROVIDER.get_lookup()
59+
if lookup is not None:
60+
return lookup
61+
62+
return cls._DEFAULT_LOOKUP
63+
64+
from .entry_point import EntryPointLookup
65+
epl = EntryPointLookup(cls._DEFAULT_ENTRY_POINT_GROUP)
66+
cls._DEFAULT_LOOKUP = epl.lookup(Lookup)
67+
if cls._DEFAULT_LOOKUP is not None:
68+
if isinstance(cls._DEFAULT_LOOKUP, LookupProvider):
69+
cls._DEFAULT_LOOKUP_PROVIDER = cls._DEFAULT_LOOKUP
70+
lookup = cls._DEFAULT_LOOKUP_PROVIDER.get_lookup()
71+
if lookup is not None:
72+
return lookup
73+
74+
return cls._DEFAULT_LOOKUP
75+
76+
provider = epl.lookup(LookupProvider)
77+
if provider is not None:
78+
from .delegated_lookup import DelegatedLookup
79+
cls._DEFAULT_LOOKUP = DelegatedLookup(provider)
80+
81+
return cls._DEFAULT_LOOKUP
82+
83+
from .proxy_lookup import ProxyLookup
84+
cls._DEFAULT_LOOKUP = ProxyLookup(epl)
85+
86+
return cls._DEFAULT_LOOKUP
4687

4788
@abstractmethod
4889
def lookup(self, cls: Type[object]) -> Optional[object]:

setup.cfg

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,21 @@ dev =
5454
check-manifest
5555

5656
[options.entry_points]
57-
lookup.tests =
57+
lookups.test_entry_point =
5858
parent = tests.tools:TestParentObject
5959
child = tests.tools:TestChildObject
6060
other = tests.tools:TestOtherObject
61+
lookups.test_default_lookup =
62+
a_lookup = tests.test_lookup_default:DefaultLookup
63+
lookups.test_default_lookup_lookup_provider =
64+
a_lookup = tests.test_lookup_default:DefaultLookupLookupProvider
65+
lookups.test_default_lookup_provider =
66+
a_lookup = tests.test_lookup_default:DefaulLookupProvider
67+
lookups.test_default_no_lookup =
68+
parent = tests.tools:TestParentObject
69+
child = tests.tools:TestChildObject
70+
other = tests.tools:TestOtherObject
71+
lookups.test_default_empty_entry_point_group =
6172

6273
[flake8]
6374
max-line-length = 100

tests/test_entry_point.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def check_item(expected_classes, item):
5151

5252

5353
def test_instantiation():
54-
assert EntryPointLookup('lookup.tests')
54+
assert EntryPointLookup('lookups.test_entry_point')
5555

5656

5757
def test_non_existant_group():
@@ -60,14 +60,14 @@ def test_non_existant_group():
6060

6161
@pytest.mark.parametrize('search, expected_classes', MEMBER_FIXTURES)
6262
def test_lookup(search, expected_classes):
63-
lookup = EntryPointLookup('lookup.tests')
63+
lookup = EntryPointLookup('lookups.test_entry_point')
6464

6565
assert isinstance(lookup.lookup(search), expected_classes)
6666

6767

6868
@pytest.mark.parametrize('search, expected_classes', MEMBER_FIXTURES)
6969
def test_lookup_item(search, expected_classes):
70-
lookup = EntryPointLookup('lookup.tests')
70+
lookup = EntryPointLookup('lookups.test_entry_point')
7171

7272
item = lookup.lookup_item(search)
7373
check_item(expected_classes, item)
@@ -76,15 +76,15 @@ def test_lookup_item(search, expected_classes):
7676

7777
@pytest.mark.parametrize('search, expected_classes', MEMBER_FIXTURES)
7878
def test_lookup_all(search, expected_classes):
79-
lookup = EntryPointLookup('lookup.tests')
79+
lookup = EntryPointLookup('lookups.test_entry_point')
8080

8181
all_instances = lookup.lookup_all(search)
8282
check_all_instances(expected_classes, all_instances)
8383

8484

8585
@pytest.mark.parametrize('search, expected_classes', MEMBER_FIXTURES)
8686
def test_lookup_result(search, expected_classes):
87-
lookup = EntryPointLookup('lookup.tests')
87+
lookup = EntryPointLookup('lookups.test_entry_point')
8888
if not isinstance(expected_classes, Sequence):
8989
expected_classes = (expected_classes, )
9090

@@ -110,7 +110,7 @@ def test_lookup_result(search, expected_classes):
110110

111111
@pytest.mark.parametrize('search, expected_classes', MEMBER_FIXTURES)
112112
def test_listeners(search, expected_classes):
113-
lookup = EntryPointLookup('lookup.tests')
113+
lookup = EntryPointLookup('lookups.test_entry_point')
114114

115115
result = lookup.lookup_result(search)
116116

tests/test_lookup_default.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) 2021 Contributors as noted in the AUTHORS file
3+
#
4+
# This Source Code Form is subject to the terms of the Mozilla Public
5+
# License, v. 2.0. If a copy of the MPL was not distributed with this
6+
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
7+
8+
# System imports
9+
10+
# Third-party imports
11+
import pytest
12+
13+
# Local imports
14+
from lookups import Lookup, LookupProvider, DelegatedLookup, ProxyLookup
15+
from lookups.singleton import SingletonLookup
16+
from .tools import TestParentObject, TestChildObject, TestOtherObject
17+
18+
19+
class DefaultLookup(SingletonLookup):
20+
21+
def __init__(self):
22+
super().__init__(TestParentObject())
23+
24+
25+
class DefaulLookupProvider(LookupProvider):
26+
27+
def get_lookup(self):
28+
return SingletonLookup(TestParentObject())
29+
30+
31+
class DefaultLookupLookupProvider(SingletonLookup, LookupProvider):
32+
33+
def __init__(self):
34+
super().__init__(TestOtherObject())
35+
self._lookup = SingletonLookup(TestParentObject())
36+
37+
def get_lookup(self):
38+
return self._lookup
39+
40+
41+
@pytest.fixture
42+
def cleanup():
43+
Lookup._DEFAULT_LOOKUP = None
44+
Lookup._DEFAULT_LOOKUP_PROVIDER = None
45+
Lookup._DEFAULT_ENTRY_POINT_GROUP = 'lookup.default'
46+
47+
yield
48+
49+
Lookup._DEFAULT_LOOKUP = None
50+
Lookup._DEFAULT_LOOKUP_PROVIDER = None
51+
Lookup._DEFAULT_ENTRY_POINT_GROUP = 'lookup.default'
52+
53+
54+
def test_default_lookup(cleanup):
55+
Lookup._DEFAULT_ENTRY_POINT_GROUP = 'lookups.test_default_lookup'
56+
dflt = Lookup.get_default()
57+
assert dflt
58+
assert isinstance(dflt, DefaultLookup)
59+
60+
all_instances = dflt.lookup_all(object)
61+
assert len(all_instances) == 1
62+
assert isinstance(all_instances[0], TestParentObject)
63+
64+
assert Lookup.get_default() is dflt
65+
66+
assert Lookup._DEFAULT_LOOKUP is dflt
67+
assert Lookup._DEFAULT_LOOKUP_PROVIDER is None
68+
69+
70+
def test_default_lookup_lookup_provider(cleanup):
71+
Lookup._DEFAULT_ENTRY_POINT_GROUP = 'lookups.test_default_lookup_lookup_provider'
72+
dflt = Lookup.get_default()
73+
assert dflt
74+
assert isinstance(dflt, SingletonLookup)
75+
76+
all_instances = dflt.lookup_all(object)
77+
assert len(all_instances) == 1
78+
assert isinstance(all_instances[0], TestParentObject)
79+
80+
assert Lookup.get_default() is dflt
81+
82+
assert isinstance(Lookup._DEFAULT_LOOKUP, DefaultLookupLookupProvider)
83+
assert Lookup._DEFAULT_LOOKUP_PROVIDER is Lookup._DEFAULT_LOOKUP
84+
85+
# Try have the provider return None
86+
Lookup._DEFAULT_LOOKUP_PROVIDER._lookup = None
87+
new_dflt = Lookup.get_default()
88+
assert new_dflt is Lookup._DEFAULT_LOOKUP
89+
90+
all_instances = new_dflt.lookup_all(object)
91+
assert len(all_instances) == 1
92+
assert isinstance(all_instances[0], TestOtherObject)
93+
94+
95+
def test_default_lookup_provider(cleanup):
96+
Lookup._DEFAULT_ENTRY_POINT_GROUP = 'lookups.test_default_lookup_provider'
97+
dflt = Lookup.get_default()
98+
assert dflt
99+
assert isinstance(dflt, DelegatedLookup)
100+
101+
all_instances = dflt.lookup_all(object)
102+
assert len(all_instances) == 1
103+
assert isinstance(all_instances[0], TestParentObject)
104+
105+
assert Lookup.get_default() is dflt
106+
107+
assert Lookup._DEFAULT_LOOKUP is dflt
108+
assert Lookup._DEFAULT_LOOKUP_PROVIDER is None
109+
110+
111+
def test_default_no_lookup(cleanup):
112+
Lookup._DEFAULT_ENTRY_POINT_GROUP = 'lookups.test_default_no_lookup'
113+
dflt = Lookup.get_default()
114+
assert dflt
115+
assert isinstance(dflt, ProxyLookup)
116+
117+
all_instances = dflt.lookup_all(object)
118+
assert all_instances
119+
assert (len(all_instances) % 3) == 0 # Because pytest can double up our entry points
120+
assert {type(instance) for instance in all_instances} == set([
121+
TestParentObject, TestChildObject, TestOtherObject])
122+
123+
assert Lookup.get_default() is dflt
124+
125+
assert Lookup._DEFAULT_LOOKUP is dflt
126+
assert Lookup._DEFAULT_LOOKUP_PROVIDER is None
127+
128+
129+
def test_default_empty_entry_point_group(cleanup):
130+
Lookup._DEFAULT_ENTRY_POINT_GROUP = 'lookups.test_default_empty_entry_point_group'
131+
dflt = Lookup.get_default()
132+
assert dflt
133+
assert isinstance(dflt, ProxyLookup)
134+
135+
all_instances = dflt.lookup_all(object)
136+
assert not all_instances
137+
138+
assert Lookup.get_default() is dflt
139+
140+
assert Lookup._DEFAULT_LOOKUP is dflt
141+
assert Lookup._DEFAULT_LOOKUP_PROVIDER is None

0 commit comments

Comments
 (0)