Skip to content

Commit db57751

Browse files
authored
Changes for COH-27559, COH-27560, COH-27561 (#2)
Improve generics; Flesh out utility classes.
1 parent 630d733 commit db57751

17 files changed

+1086
-943
lines changed

.github/workflows/validate.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ jobs:
1616
strategy:
1717
fail-fast: false
1818
matrix:
19-
python-version: ["3.10"]
20-
poetry-version: ["1.1.15"]
19+
python-version: ["3.10.x", "3.11.x"]
20+
poetry-version: ["1.4.2"]
2121
os: [ubuntu-latest]
2222
runs-on: ${{ matrix.os }}
2323
steps:

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
.pytest_cache
99
*.iml
1010
etc
11+
docs
1112
build
1213
poetry.lock
1314
__pycache__

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ repos:
2626
- id: black
2727

2828
- repo: https://github.com/pre-commit/mirrors-mypy
29-
rev: v1.1.1
29+
rev: v1.2.0
3030
hooks:
3131
- id: mypy
3232

coherence/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22
# Licensed under the Universal Permissive License v 1.0 as shown at
33
# https://oss.oracle.com/licenses/upl.
44

5+
from __future__ import annotations
6+
57
__version__ = "0.1.0"
68

9+
from decimal import Decimal
10+
from typing import TypeAlias
11+
712
# expose these symbols in top-level namespace
813
from .aggregator import Aggregators as Aggregators
914
from .client import MapEntry as MapEntry
@@ -13,3 +18,4 @@
1318
from .client import Session as Session
1419
from .client import TlsOptions as TlsOptions
1520
from .filter import Filters as Filters
21+
from .processor import Processors as Processors

coherence/aggregator.py

+137-125
Large diffs are not rendered by default.

coherence/client.py

+11-10
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ def invoke_all(
388388

389389
@abc.abstractmethod
390390
async def aggregate(
391-
self, aggregator: EntryAggregator, keys: Optional[set[K]] = None, filter: Optional[Filter] = None
391+
self, aggregator: EntryAggregator[R], keys: Optional[set[K]] = None, filter: Optional[Filter] = None
392392
) -> R:
393393
"""
394394
Perform an aggregating operation against the entries specified by the passed keys.
@@ -430,7 +430,7 @@ def entries(
430430
) -> AsyncIterator[MapEntry[K, V]]:
431431
"""
432432
Return a set view of the entries contained in this map that satisfy the criteria expressed by the filter.
433-
Each element in the returned set is a :func:`coherence.client.MapEntry`.
433+
Each element in the returned set is a :class:`coherence.client.MapEntry`.
434434
435435
:param filter: the Filter object representing the criteria that the entries of this map should satisfy
436436
:param comparator: the Comparator object which imposes an ordering on entries in the resulting set; or `None`
@@ -643,7 +643,7 @@ def invoke_all(
643643

644644
@_pre_call_cache
645645
async def aggregate(
646-
self, aggregator: EntryAggregator, keys: Optional[set[K]] = None, filter: Optional[Filter] = None
646+
self, aggregator: EntryAggregator[R], keys: Optional[set[K]] = None, filter: Optional[Filter] = None
647647
) -> R:
648648
r = self._request_factory.aggregate_request(aggregator, keys, filter)
649649
results = await self._client_stub.aggregate(r)
@@ -926,16 +926,16 @@ class Session:
926926
927927
This class emits the following events:
928928
929-
1. :func:`coherence.event.MapLifecycleEvent.DESTROYED`: when the underlying cache is destroyed
930-
2. :func:`coherence.event.MapLifecycleEvent.TRUNCATED`: When the underlying cache is truncated
931-
3. :func:`coherence.event.MapLifecycleEvent.RELEASED`: When the underlying cache is released
932-
4. :func:`coherence.event.SessionLifecycleEvent.CONNECT`: when the Session detects the underlying `gRPC`
929+
1. :class:`coherence.event.MapLifecycleEvent.DESTROYED`: when the underlying cache is destroyed
930+
2. :class:`coherence.event.MapLifecycleEvent.TRUNCATED`: When the underlying cache is truncated
931+
3. :class:`coherence.event.MapLifecycleEvent.RELEASED`: When the underlying cache is released
932+
4. :class:`coherence.event.SessionLifecycleEvent.CONNECT`: when the Session detects the underlying `gRPC`
933933
channel has connected.
934-
5. :func:`coherence.event.SessionLifecycleEvent.DISCONNECT`: when the Session detects the underlying `gRPC`
934+
5. :class:`coherence.event.SessionLifecycleEvent.DISCONNECT`: when the Session detects the underlying `gRPC`
935935
channel has disconnected
936-
6. :func:`coherence.event.SessionLifecycleEvent.RECONNECTED`: when the Session detects the underlying `gRPC`
936+
6. :class:`coherence.event.SessionLifecycleEvent.RECONNECTED`: when the Session detects the underlying `gRPC`
937937
channel has re-connected
938-
7. :func:`coherence.event.SessionLifecycleEvent.CLOSED`: when the Session has been closed
938+
7. :class:`coherence.event.SessionLifecycleEvent.CLOSED`: when the Session has been closed
939939
940940
"""
941941

@@ -1297,6 +1297,7 @@ async def __load_next_page(self) -> None:
12971297
12981298
:return: None
12991299
"""
1300+
print("### DEBUG: __load_next_page() called!")
13001301
request: PageRequest = self._client._request_factory.page_request(self._cookie)
13011302
print("### DEBUG: __load_next_page() called!")
13021303
self._stream = self._get_stream(request)

coherence/comparator.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
# Copyright (c) 2022 Oracle and/or its affiliates.
1+
# Copyright (c) 2022, 2023, Oracle and/or its affiliates.
22
# Licensed under the Universal Permissive License v 1.0 as shown at
33
# https://oss.oracle.com/licenses/upl.
44

55
from __future__ import annotations
66

77
from abc import ABC
8+
from typing import Any
89

910
from coherence.extractor import UniversalExtractor
1011
from coherence.serialization import proxy
@@ -33,4 +34,4 @@ def __init__(self, property_name: str) -> None:
3334
class ExtractorComparator(Comparator):
3435
def __init__(self, property_name: str) -> None:
3536
super().__init__()
36-
self.extractor = UniversalExtractor(property_name)
37+
self.extractor = UniversalExtractor[Any](property_name)

coherence/extractor.py

+76-37
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
# Copyright (c) 2022 Oracle and/or its affiliates.
1+
# Copyright (c) 2022, 2023, Oracle and/or its affiliates.
22
# Licensed under the Universal Permissive License v 1.0 as shown at
33
# https://oss.oracle.com/licenses/upl.
44

55
from __future__ import annotations
66

77
from abc import ABC, abstractmethod
8-
from typing import Any, Optional, Sequence, TypeVar, cast
8+
from typing import Any, Generic, Optional, Sequence, TypeAlias, TypeVar, cast
99

1010
from coherence.serialization import proxy
1111

12-
K = TypeVar("K", covariant=True)
13-
V = TypeVar("V", covariant=True)
14-
R = TypeVar("R", covariant=True)
12+
E = TypeVar("E")
13+
K = TypeVar("K")
14+
V = TypeVar("V")
15+
R = TypeVar("R")
16+
T = TypeVar("T")
1517

1618

17-
class ValueExtractor(ABC):
19+
class ValueExtractor(ABC, Generic[T, E]):
1820
def __init__(self) -> None:
1921
"""
2022
ValueExtractor is used to both extract values (for example, for sorting or filtering) from an object,
@@ -24,7 +26,7 @@ def __init__(self) -> None:
2426
"""
2527
super().__init__()
2628

27-
def compose(self, before: ValueExtractor) -> ValueExtractor:
29+
def compose(self, before: ValueExtractor[T, E]) -> ValueExtractor[T, E]:
2830
"""
2931
Returns a composed extractor that first applies the *before* extractor to its input, and then applies this
3032
extractor to the result. If evaluation of either extractor throws an exception, it is relayed to the caller
@@ -40,7 +42,7 @@ def compose(self, before: ValueExtractor) -> ValueExtractor:
4042
else:
4143
return ChainedExtractor([before, self])
4244

43-
def and_then(self, after: ValueExtractor) -> ValueExtractor:
45+
def and_then(self, after: ValueExtractor[T, E]) -> ValueExtractor[T, E]:
4446
"""
4547
Returns a composed extractor that first applies this extractor to its input, and then applies the *after*
4648
extractor to the result. If evaluation of either extractor throws an exception, it is relayed to the caller
@@ -57,19 +59,19 @@ def and_then(self, after: ValueExtractor) -> ValueExtractor:
5759
return after.compose(self)
5860

5961
@classmethod
60-
def extract(cls, from_field_or_method: str, params: Optional[list[Any]] = None) -> ValueExtractor:
62+
def extract(cls, from_field_or_method: str, params: Optional[list[Any]] = None) -> ValueExtractor[T, E]:
6163
"""
6264
Returns an extractor that extracts the value of the specified field.
6365
64-
:param from_field_or_method: he name of the field or method to extract the value from.
66+
:param from_field_or_method: the name of the field or method to extract the value from.
6567
:param params: the parameters to pass to the method.
66-
:return: an instance of :func:`coherence.extractor.UniversalExtractor`
68+
:return: an instance of :class:`coherence.extractor.UniversalExtractor`
6769
"""
6870
return UniversalExtractor(from_field_or_method, params)
6971

7072

7173
@proxy("extractor.UniversalExtractor")
72-
class UniversalExtractor(ValueExtractor):
74+
class UniversalExtractor(ValueExtractor[T, Any]):
7375
def __init__(self, name: str, params: Optional[list[Any]] = None) -> None:
7476
"""
7577
Universal ValueExtractor implementation.
@@ -85,29 +87,29 @@ def __init__(self, name: str, params: Optional[list[Any]] = None) -> None:
8587
this extractor is considered a method extractor.
8688
8789
:param name: A method or property name.
88-
:param params: he parameter array. Must be `null` or `zero length` for a property based extractor.
90+
:param params: the parameter array. Must be `null` or `zero length` for a property based extractor.
8991
"""
9092
super().__init__()
9193
self.name: str = name
9294
self.params = params
9395

9496
@classmethod
95-
def create(cls, name: str, params: Optional[list[Any]] = None) -> UniversalExtractor:
97+
def create(cls, name: str, params: Optional[list[Any]] = None) -> UniversalExtractor[T]:
9698
"""
97-
Class method to create an instance of :func:`coherence.extractor.UniversalExtractor`
99+
Class method to create an instance of :class:`coherence.extractor.UniversalExtractor`
98100
99101
:param name: A method or property name.
100-
:param params: he parameter array. Must be `null` or `zero length` for a property based extractor.
101-
:return: an instance of :func:`coherence.extractor.UniversalExtractor`
102+
:param params: the parameter array. Must be `null` or `zero length` for a property based extractor.
103+
:return: an instance of :class:`coherence.extractor.UniversalExtractor`
102104
"""
103105
return cls(name, params)
104106

105107

106-
class AbstractCompositeExtractor(ValueExtractor):
107-
def __init__(self, extractors: Sequence[ValueExtractor]) -> None:
108+
class AbstractCompositeExtractor(ValueExtractor[T, E]):
109+
def __init__(self, extractors: Sequence[ValueExtractor[T, E]]) -> None:
108110
"""
109-
Abstract super class for :func:`coherence.extractor.ValueExtractor` implementations that are based on an
110-
underlying array of :func:`coherence.extractor.ValueExtractor` objects.
111+
Abstract super class for :class:`coherence.extractor.ValueExtractor` implementations that are based on an
112+
underlying array of :class:`coherence.extractor.ValueExtractor` objects.
111113
112114
:param extractors: an array of extractors
113115
"""
@@ -116,35 +118,58 @@ def __init__(self, extractors: Sequence[ValueExtractor]) -> None:
116118

117119

118120
@proxy("extractor.ChainedExtractor")
119-
class ChainedExtractor(AbstractCompositeExtractor):
120-
def __init__(self, extractors_or_method: str | Sequence[ValueExtractor]) -> None:
121+
class ChainedExtractor(AbstractCompositeExtractor[T, Any]):
122+
def __init__(self, extractors_or_method: str | Sequence[ValueExtractor[T, Any]]) -> None:
121123
"""
122-
Composite :func:`coherence.extractor.ValueExtractor` implementation based on an array of extractors. The
124+
Composite :class:`coherence.extractor.ValueExtractor` implementation based on an array of extractors. The
123125
extractors in the array are applied sequentially left-to-right, so a result of a previous extractor serves as
124126
a target object for a next one.
125127
126-
:param extractors_or_method: an array of :func:`coherence.extractor.ValueExtractor`, or a dot-delimited
128+
:param extractors_or_method: an array of :class:`coherence.extractor.ValueExtractor`, or a dot-delimited
127129
sequence of method names which results in a ChainedExtractor that is based on an array of corresponding
128-
:func:`coherence.extractor.UniversalExtractor` objects
130+
:class:`coherence.extractor.UniversalExtractor` objects
129131
"""
130132
if type(extractors_or_method) == str:
131133
e = list()
132134
names = extractors_or_method.split(".")
133135
for name in names:
134-
v = UniversalExtractor(name)
136+
v: UniversalExtractor[T] = UniversalExtractor(name)
135137
e.append(v)
136138
super().__init__(e)
137139
else:
138-
super().__init__(cast(Sequence[ValueExtractor], extractors_or_method))
140+
super().__init__(cast(Sequence[ValueExtractor[T, Any]], extractors_or_method))
141+
142+
143+
@proxy("extractor.MultiExtractor")
144+
class MultiExtractor(AbstractCompositeExtractor[Any, Any]):
145+
def __init__(self, extractors_or_method: str | Sequence[ValueExtractor[Any, Any]]) -> None:
146+
"""
147+
Composite :class:`coherence.extractor.ValueExtractor` implementation based on an array of extractors. The
148+
extractors in the array are applied sequentially left-to-right, so a result of a previous extractor serves as
149+
a target object for a next one.
150+
151+
:param extractors_or_method: an array of :class:`coherence.extractor.ValueExtractor`, or a dot-delimited
152+
sequence of method names which results in a ChainedExtractor that is based on an array of corresponding
153+
:class:`coherence.extractor.UniversalExtractor` objects
154+
"""
155+
if type(extractors_or_method) == str:
156+
e = list()
157+
names = extractors_or_method.split(",")
158+
for name in names:
159+
v: UniversalExtractor[Any] = UniversalExtractor(name)
160+
e.append(v)
161+
super().__init__(e)
162+
else:
163+
super().__init__(cast(Sequence[ValueExtractor[Any, Any]], extractors_or_method))
139164

140165

141166
@proxy("extractor.IdentityExtractor")
142-
class IdentityExtractor(ValueExtractor):
167+
class IdentityExtractor(ValueExtractor[T, Any]):
143168
__instance = None
144169

145170
def __init__(self) -> None:
146171
"""
147-
A Trivial :func:`coherence.extractor.ValueExtractor` implementation that does not actually extract anything
172+
A Trivial :class:`coherence.extractor.ValueExtractor` implementation that does not actually extract anything
148173
from the passed value, but returns the value itself.
149174
150175
Constructs a new `IdentityExtractor` instance.
@@ -163,11 +188,11 @@ def __init__(self) -> None:
163188

164189

165190
class ValueManipulator(ValueUpdater):
166-
"""ValueManipulator represents a composition of :func:`coherence.extractor.ValueExtractor` and
167-
:func:`coherence.extractor.ValueUpdater` implementations."""
191+
"""ValueManipulator represents a composition of :class:`coherence.extractor.ValueExtractor` and
192+
:class:`coherence.extractor.ValueUpdater` implementations."""
168193

169194
@abstractmethod
170-
def get_extractor(self) -> ValueExtractor:
195+
def get_extractor(self) -> ValueExtractor[T, E]:
171196
"""
172197
Retrieve the underlying ValueExtractor reference.
173198
@@ -188,7 +213,7 @@ class CompositeUpdater(ValueManipulator):
188213
"""A ValueUpdater implementation based on an extractor-updater pair that could also be used as a
189214
ValueManipulator."""
190215

191-
def __init__(self, method_or_extractor: str | ValueExtractor, updater: Optional[ValueUpdater] = None) -> None:
216+
def __init__(self, method_or_extractor: ExtractorExpression[T, E], updater: Optional[ValueUpdater] = None) -> None:
192217
"""
193218
Constructs a new `CompositeUpdater`.
194219
@@ -197,7 +222,7 @@ def __init__(self, method_or_extractor: str | ValueExtractor, updater: Optional[
197222
"""
198223
super().__init__()
199224
if updater is not None: # Two arg constructor
200-
self.extractor = cast(ValueExtractor, method_or_extractor)
225+
self.extractor = cast(ValueExtractor[T, E], method_or_extractor)
201226
self.updater = updater
202227
else: # One arg with method name
203228
last = str(method_or_extractor).rfind(".")
@@ -207,7 +232,7 @@ def __init__(self, method_or_extractor: str | ValueExtractor, updater: Optional[
207232
self.extractor = ChainedExtractor(str(method_or_extractor)[0:last])
208233
self.updater = UniversalUpdater(str(method_or_extractor)[last + 1 :])
209234

210-
def get_extractor(self) -> ValueExtractor:
235+
def get_extractor(self) -> ValueExtractor[T, E]:
211236
return self.extractor
212237

213238
def get_updator(self) -> ValueUpdater:
@@ -227,10 +252,24 @@ def __init__(self, method: str) -> None:
227252
228253
If method ends in a '()', then the name is a method name. This implementation assumes that a target's class
229254
will have one and only one method with the specified name and this method will have exactly one parameter; if
230-
the method is a property name, there should be a corresponding JavaBean property modifier method or it will
255+
the method is a property name, there should be a corresponding JavaBean property modifier method, or it will
231256
be used as a key in a Map.
232257
233258
:param method: a method or property name
234259
"""
235260
super().__init__()
236261
self.name = method
262+
263+
264+
def extract(expression: str) -> ValueExtractor[T, E]:
265+
if "." in expression:
266+
return ChainedExtractor(expression)
267+
elif "," in expression:
268+
return MultiExtractor(expression)
269+
else:
270+
return UniversalExtractor(expression)
271+
272+
273+
ExtractorExpression: TypeAlias = ValueExtractor[T, E] | str
274+
ManipulatorExpression: TypeAlias = ValueManipulator | str
275+
UpdaterExpression: TypeAlias = ValueUpdater | str

0 commit comments

Comments
 (0)