Skip to content

Commit 7cf02ae

Browse files
fix: Make public API more similar to generated clients (#56)
* fix: Make public API more similar to generated clients * fix: Assorted fixes * fix: Export clients in google.cloud.pubsublite.cloudpubsub * fix: Add tests, break out repeated code, and restructure admin client * fix: Import admin types to __init__.py and add overrides on async clients to copy docs * fix: Lint issues * fix: Fix deadlock in streaming pull shutdown * fix: Ensure client awaitables passed to await_unless_failed are awaited
1 parent 32bc302 commit 7cf02ae

37 files changed

+1463
-211
lines changed

.coveragerc

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ exclude_lines =
2929
# Ignore abstract methods
3030
raise NotImplementedError
3131
@abstractmethod
32+
# Ignore delegating methods
33+
self._impl.
34+
# Ignore __exit__ "return self"
35+
return self
3236

3337
omit =
3438
*/pubsublite_v1/*.py

google/cloud/pubsublite/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,14 @@
9696
from google.cloud.pubsublite_v1.types.subscriber import SeekResponse
9797
from google.cloud.pubsublite_v1.types.subscriber import SubscribeRequest
9898
from google.cloud.pubsublite_v1.types.subscriber import SubscribeResponse
99+
from google.cloud.pubsublite.admin_client_interface import AdminClientInterface
100+
from google.cloud.pubsublite.admin_client import AdminClient
99101

100102
__all__ = (
103+
# Manual files
104+
"AdminClient",
105+
"AdminClientInterface",
106+
# Generated files
101107
"AdminServiceAsyncClient",
102108
"AdminServiceClient",
103109
"AttributeValues",
+75-33
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,109 @@
1-
from abc import ABC, abstractmethod
2-
from typing import List
1+
from typing import Optional, List
32

3+
from overrides import overrides
4+
from google.api_core.client_options import ClientOptions
5+
from google.auth.credentials import Credentials
6+
from google.protobuf.field_mask_pb2 import FieldMask
7+
8+
from google.cloud.pubsublite.admin_client_interface import AdminClientInterface
9+
from google.cloud.pubsublite.internal.constructable_from_service_account import (
10+
ConstructableFromServiceAccount,
11+
)
12+
from google.cloud.pubsublite.internal.endpoints import regional_endpoint
13+
from google.cloud.pubsublite.internal.wire.admin_client_impl import AdminClientImpl
414
from google.cloud.pubsublite.types import (
515
CloudRegion,
6-
TopicPath,
7-
LocationPath,
816
SubscriptionPath,
17+
LocationPath,
18+
TopicPath,
919
)
10-
from google.cloud.pubsublite_v1 import Topic, Subscription
11-
from google.protobuf.field_mask_pb2 import FieldMask
20+
from google.cloud.pubsublite_v1 import AdminServiceClient, Subscription, Topic
21+
22+
23+
class AdminClient(AdminClientInterface, ConstructableFromServiceAccount):
24+
"""
25+
An admin client for Pub/Sub Lite. Only operates on a single region.
26+
"""
27+
28+
_impl: AdminClientInterface
29+
30+
def __init__(
31+
self,
32+
region: CloudRegion,
33+
credentials: Optional[Credentials] = None,
34+
transport: Optional[str] = None,
35+
client_options: Optional[ClientOptions] = None,
36+
):
37+
"""
38+
Create a new AdminClient.
1239
40+
Args:
41+
region: The cloud region to connect to.
42+
credentials: The credentials to use when connecting.
43+
transport: The transport to use.
44+
client_options: The client options to use when connecting. If used, must explicitly set `api_endpoint`.
45+
"""
46+
if client_options is None:
47+
client_options = ClientOptions(api_endpoint=regional_endpoint(region))
48+
self._impl = AdminClientImpl(
49+
AdminServiceClient(
50+
client_options=client_options,
51+
transport=transport,
52+
credentials=credentials,
53+
),
54+
region,
55+
)
1356

14-
class AdminClient(ABC):
15-
@abstractmethod
57+
@overrides
1658
def region(self) -> CloudRegion:
17-
"""The region this client is for."""
59+
return self._impl.region()
1860

19-
@abstractmethod
61+
@overrides
2062
def create_topic(self, topic: Topic) -> Topic:
21-
"""Create a topic, returns the created topic."""
63+
return self._impl.create_topic(topic)
2264

23-
@abstractmethod
65+
@overrides
2466
def get_topic(self, topic_path: TopicPath) -> Topic:
25-
"""Get the topic object from the server."""
67+
return self._impl.get_topic(topic_path)
2668

27-
@abstractmethod
69+
@overrides
2870
def get_topic_partition_count(self, topic_path: TopicPath) -> int:
29-
"""Get the number of partitions in the provided topic."""
71+
return self._impl.get_topic_partition_count(topic_path)
3072

31-
@abstractmethod
73+
@overrides
3274
def list_topics(self, location_path: LocationPath) -> List[Topic]:
33-
"""List the Pub/Sub lite topics that exist for a project in a given location."""
75+
return self._impl.list_topics(location_path)
3476

35-
@abstractmethod
77+
@overrides
3678
def update_topic(self, topic: Topic, update_mask: FieldMask) -> Topic:
37-
"""Update the masked fields of the provided topic."""
79+
return self._impl.update_topic(topic, update_mask)
3880

39-
@abstractmethod
81+
@overrides
4082
def delete_topic(self, topic_path: TopicPath):
41-
"""Delete a topic and all associated messages."""
83+
return self._impl.delete_topic(topic_path)
4284

43-
@abstractmethod
85+
@overrides
4486
def list_topic_subscriptions(self, topic_path: TopicPath):
45-
"""List the subscriptions that exist for a given topic."""
87+
return self._impl.list_topic_subscriptions(topic_path)
4688

47-
@abstractmethod
89+
@overrides
4890
def create_subscription(self, subscription: Subscription) -> Subscription:
49-
"""Create a subscription, returns the created subscription."""
91+
return self._impl.create_subscription(subscription)
5092

51-
@abstractmethod
93+
@overrides
5294
def get_subscription(self, subscription_path: SubscriptionPath) -> Subscription:
53-
"""Get the subscription object from the server."""
95+
return self._impl.get_subscription(subscription_path)
5496

55-
@abstractmethod
97+
@overrides
5698
def list_subscriptions(self, location_path: LocationPath) -> List[Subscription]:
57-
"""List the Pub/Sub lite subscriptions that exist for a project in a given location."""
99+
return self._impl.list_subscriptions(location_path)
58100

59-
@abstractmethod
101+
@overrides
60102
def update_subscription(
61103
self, subscription: Subscription, update_mask: FieldMask
62104
) -> Subscription:
63-
"""Update the masked fields of the provided subscription."""
105+
return self._impl.update_subscription(subscription, update_mask)
64106

65-
@abstractmethod
107+
@overrides
66108
def delete_subscription(self, subscription_path: SubscriptionPath):
67-
"""Delete a subscription and all associated messages."""
109+
return self._impl.delete_subscription(subscription_path)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from abc import ABC, abstractmethod
2+
from typing import List
3+
4+
from google.cloud.pubsublite.types import (
5+
CloudRegion,
6+
TopicPath,
7+
LocationPath,
8+
SubscriptionPath,
9+
)
10+
from google.cloud.pubsublite_v1 import Topic, Subscription
11+
from google.protobuf.field_mask_pb2 import FieldMask
12+
13+
14+
class AdminClientInterface(ABC):
15+
"""
16+
An admin client for Pub/Sub Lite. Only operates on a single region.
17+
"""
18+
19+
@abstractmethod
20+
def region(self) -> CloudRegion:
21+
"""The region this client is for."""
22+
23+
@abstractmethod
24+
def create_topic(self, topic: Topic) -> Topic:
25+
"""Create a topic, returns the created topic."""
26+
27+
@abstractmethod
28+
def get_topic(self, topic_path: TopicPath) -> Topic:
29+
"""Get the topic object from the server."""
30+
31+
@abstractmethod
32+
def get_topic_partition_count(self, topic_path: TopicPath) -> int:
33+
"""Get the number of partitions in the provided topic."""
34+
35+
@abstractmethod
36+
def list_topics(self, location_path: LocationPath) -> List[Topic]:
37+
"""List the Pub/Sub lite topics that exist for a project in a given location."""
38+
39+
@abstractmethod
40+
def update_topic(self, topic: Topic, update_mask: FieldMask) -> Topic:
41+
"""Update the masked fields of the provided topic."""
42+
43+
@abstractmethod
44+
def delete_topic(self, topic_path: TopicPath):
45+
"""Delete a topic and all associated messages."""
46+
47+
@abstractmethod
48+
def list_topic_subscriptions(self, topic_path: TopicPath):
49+
"""List the subscriptions that exist for a given topic."""
50+
51+
@abstractmethod
52+
def create_subscription(self, subscription: Subscription) -> Subscription:
53+
"""Create a subscription, returns the created subscription."""
54+
55+
@abstractmethod
56+
def get_subscription(self, subscription_path: SubscriptionPath) -> Subscription:
57+
"""Get the subscription object from the server."""
58+
59+
@abstractmethod
60+
def list_subscriptions(self, location_path: LocationPath) -> List[Subscription]:
61+
"""List the Pub/Sub lite subscriptions that exist for a project in a given location."""
62+
63+
@abstractmethod
64+
def update_subscription(
65+
self, subscription: Subscription, update_mask: FieldMask
66+
) -> Subscription:
67+
"""Update the masked fields of the provided subscription."""
68+
69+
@abstractmethod
70+
def delete_subscription(self, subscription_path: SubscriptionPath):
71+
"""Delete a subscription and all associated messages."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# flake8: noqa
2+
from .message_transformer import MessageTransformer
3+
from .nack_handler import NackHandler
4+
from .publisher_client_interface import (
5+
PublisherClientInterface,
6+
AsyncPublisherClientInterface,
7+
)
8+
from .publisher_client import PublisherClient, AsyncPublisherClient
9+
from .subscriber_client_interface import (
10+
SubscriberClientInterface,
11+
AsyncSubscriberClientInterface,
12+
)
13+
from .subscriber_client import SubscriberClient, AsyncSubscriberClient

google/cloud/pubsublite/cloudpubsub/internal/assigning_subscriber.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,23 @@
33

44
from google.cloud.pubsub_v1.subscriber.message import Message
55

6-
from google.cloud.pubsublite.cloudpubsub.subscriber import AsyncSubscriber
6+
from google.cloud.pubsublite.cloudpubsub.internal.single_subscriber import (
7+
AsyncSingleSubscriber,
8+
)
79
from google.cloud.pubsublite.internal.wait_ignore_cancelled import wait_ignore_cancelled
810
from google.cloud.pubsublite.internal.wire.assigner import Assigner
911
from google.cloud.pubsublite.internal.wire.permanent_failable import PermanentFailable
1012
from google.cloud.pubsublite.types import Partition
1113

12-
PartitionSubscriberFactory = Callable[[Partition], AsyncSubscriber]
14+
PartitionSubscriberFactory = Callable[[Partition], AsyncSingleSubscriber]
1315

1416

1517
class _RunningSubscriber(NamedTuple):
16-
subscriber: AsyncSubscriber
18+
subscriber: AsyncSingleSubscriber
1719
poller: Future
1820

1921

20-
class AssigningSubscriber(AsyncSubscriber, PermanentFailable):
22+
class AssigningSingleSubscriber(AsyncSingleSubscriber, PermanentFailable):
2123
_assigner_factory: Callable[[], Assigner]
2224
_subscriber_factory: PartitionSubscriberFactory
2325

@@ -47,7 +49,7 @@ def __init__(
4749
async def read(self) -> Message:
4850
return await self.await_unless_failed(self._messages.get())
4951

50-
async def _subscribe_action(self, subscriber: AsyncSubscriber):
52+
async def _subscribe_action(self, subscriber: AsyncSingleSubscriber):
5153
message = await subscriber.read()
5254
await self._messages.put(message)
5355

google/cloud/pubsublite/cloudpubsub/internal/async_publisher_impl.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
from google.cloud.pubsublite.cloudpubsub.message_transforms import (
66
from_cps_publish_message,
77
)
8-
from google.cloud.pubsublite.cloudpubsub.publisher import AsyncPublisher
8+
from google.cloud.pubsublite.cloudpubsub.internal.single_publisher import (
9+
AsyncSinglePublisher,
10+
)
911
from google.cloud.pubsublite.internal.wire.publisher import Publisher
1012

1113

12-
class AsyncPublisherImpl(AsyncPublisher):
14+
class AsyncSinglePublisherImpl(AsyncSinglePublisher):
1315
_publisher_factory: Callable[[], Publisher]
1416
_publisher: Optional[Publisher]
1517

0 commit comments

Comments
 (0)