Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e115bbd
api proposal for t2 sb
yunhaoling Feb 19, 2020
67d0b04
minor update
yunhaoling Feb 19, 2020
b598e9d
minor update
yunhaoling Feb 19, 2020
979f2ec
add mode to receive method
yunhaoling Feb 19, 2020
d853ca4
put session into constructor
yunhaoling Feb 19, 2020
0d83238
move receive mode param into constructor
yunhaoling Feb 19, 2020
18aa235
fix typo
yunhaoling Feb 25, 2020
3b914d5
minor update on send
yunhaoling Feb 25, 2020
a724188
add batch message, retry option, sb connection
yunhaoling Feb 26, 2020
bf5ece0
update as per discussion
yunhaoling Feb 27, 2020
175f7eb
fix typo and remove entity name from connection constructor
yunhaoling Feb 28, 2020
1fa6128
fix typo in samples
yunhaoling Feb 28, 2020
0558c32
remove factory methods and update sample accordingly
yunhaoling Mar 2, 2020
c21dd5e
introduce top level sb client
yunhaoling Mar 4, 2020
c1f212f
update api and samples as per discussion
yunhaoling Mar 4, 2020
16eb476
update message inheritance
yunhaoling Mar 6, 2020
187700f
fix transport type being None
yunhaoling Mar 6, 2020
190a912
Update sdk/servicebus/azure-servicebus/api_samples.py
yunhaoling Mar 6, 2020
68b0e4a
remove rule and transaction
yunhaoling Mar 6, 2020
f57e0b4
Merge branch 'servicebus-track2-api-proposal' of https://github.com/y…
yunhaoling Mar 6, 2020
86e19ef
update as per discussion with Rayma
yunhaoling Mar 12, 2020
785bea9
minor update
yunhaoling Mar 13, 2020
30c37b5
add session_id onto message, and autorenewer class.
KieranBrantnerMagee Mar 18, 2020
53349f7
Kieran's update on the API proposal
yunhaoling Mar 18, 2020
17ae9c9
add rule manager api
yunhaoling Apr 17, 2020
5c58b7c
add api and sample for rule management
yunhaoling Apr 17, 2020
ce69df8
merge
yunhaoling Apr 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 285 additions & 0 deletions sdk/servicebus/azure-servicebus/api_review.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
## APIs:
class ServiceBusClient:
def __init__(
self,
fully_qualified_namespace : str,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has carried over from eventhubs - from what I can tell the Azure portal always lists it as 'host name'. Should we match the portal?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would need service side input

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be called host name in the portal for event hubs as well

credential : TokenCredential,
logging_enable: bool = False,
Comment thread
yunhaoling marked this conversation as resolved.
http_proxy: dict = None,
transport_type: TransportType = TransportType.Amqp,
):

def __enter__(self):
def __exit__(self, *kwargs):
def close(self):

@classmethod
def from_connection_string(cls, conn_str, **kwargs):

def get_queue_sender(self, queue_name, **kwargs) -> ServiceBusSender:
Comment thread
yunhaoling marked this conversation as resolved.
def get_queue_receiver(self, queue_name, **kwargs) -> ServiceBusReceiver:
def get_topic_sender(self, topic_name, **kwargs) -> ServiceBusSender:
def get_subscription_receiver(self, topic_name, subscription_name, **kwargs) -> ServiceBusReceiver:

def get_queue_session_receiver(self, queue_name, session_id, **kwargs) -> ServiceBusSessionReceiver:
def get_subscription_session_receiver(self, topic_name, subscription_name, session_id, **kwargs) -> ServiceBusSessionReceiver:

def get_subscription_rule_manager(self, topic_name, subscription_name) -> SubscriptionRuleManager:

class ServiceBusSender:
def __init__(
self,
fully_qualified_namespace : str,
credential: TokenCredential,
queue_name: str = None,
topic_name: str = None,
logging_enable: bool = False,
http_proxy: dict = None,
transport_type: TransportType = TransportType.Amqp,
retry_total : int = 3,
retry_backoff_factor : float = 30,
retry_backoff_maximum : int = 120 # cur_retry_backoff = retry_backoff_factor * (2^cur_retry_time)
) -> None:
@classmethod
def from_connection_string(
cls,
conn_str : str,
queue_name: str = None,
topic_name: str = None,
**kwargs
) -> ServiceBusSender:

def __enter__(self):
def __exit__(self):

def close(self) -> None:

def create_batch(
self,
max_size_in_bytes : int = None
) -> BatchMessage:

def send(
self,
message : Union[Message, BatchMessage],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YijunXieMS - did we go with EventDataBatch on the Python side as well in EH? If so, I guess this should be ServiceBusMessageBatch?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@richardpark-msft yes EH has EventDataBatch. For the SB question, I'll leave it to Adam and Kieran. I don't have a lot of SB context for now.

@yunhaoling yunhaoling Mar 7, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@richardpark-msft I think then the following up question would be:
whether to name all other Message classes
Message -> ServiceBusMessage
ReceivedMessage -> ServiceBusReceivedMessage
etc.

as for the purpose of keeping consistent

message_timeout : float = None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the unit for this? And do you really support fractional units or is this really just a whole number (ie, int?)

@yunhaoling yunhaoling Mar 6, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ms or s? service behavior, each language ecosystem

) -> None:
def schedule(
self,
message : Union[Message, BatchMessage],
schedule_time_utc : datetime,
) -> List[int]:

def cancel_scheduled_messages(self, sequence_number : Union[int, List[int]]) -> None:


class ServiceBusReceiver:
def __init__(
self,
fully_qualified_namespace : str,
credential: TokenCredential,
queue_name: str = None,
topic_name: str = None,
subscription_name : str = None,
session_id : str = None,
mode : ReceiveSettleMode = ReceiveSettleMode.PeekLock
logging_enable: bool = False,
http_proxy: dict = None,
transport_type: TransportType = TransportType.Amqp,
retry_total : int = 3,
retry_backoff_factor: float = 30,
retry_backoff_maximum: int = 120 # cur_retry_backoff = retry_backoff_factor * (2^cur_retry_time)
) -> None:
Comment thread
annatisch marked this conversation as resolved.
@classmethod
def from_connection_string(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be considered an internal method? (not sure what your options are here)

cls,
conn_str : str,
queue_name: str = None,
topic_name: str = None,
subscription_name : str = None,
session_id: str = None,
mode : ReceiveSettleMode = ReceiveSettleMode.PeekLock
**kwargs
)-> ServiceBusReceiver:

def __enter__(self):
def __exit__(self):

def close(self) -> None:

def __iter__(self):
def __next__(self):
def next(self):
Comment thread
annatisch marked this conversation as resolved.

def peek(
self,
message_count : int = 1,
sequence_number : int = None
) -> List[PeekMessage]:
def receive_deferred_messages(
Comment thread
yunhaoling marked this conversation as resolved.
self,
sequence_numbers : List[int],
) -> List[DeferredMessage]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is DefferredMessage different from ReceivedMessage?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yunhaoling mentioned that the only difference is that a DefferredMessage can't be deferred.
From what I know, when a DefferredMessage is deferred, the message goes back to the deferred queue. We also have tests for this in JS track 1.

def receive(
self,
max_batch_size : int = None,
timeout : float = None,
) -> List[ReceivedMessage]: # Pull mode receive


class ServiceBusSessionReceiver(ServiceBusReceiver):
@property
def session(self) -> ServiceBusSession:


class ServiceBusSession:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We renamed this to ServiceBusSessionManager for .NET.

@property
def get_session_state(self) -> str: # This can be made to property if property is more pythonic way
def set_session_state(self, state : str) -> None: # This can be made to property if property is more pythonic way
Comment thread
yunhaoling marked this conversation as resolved.
def renew_lock(self) -> None:
@property
def session_id(self) -> str:
def expired(self) -> bool:


class Message:
Comment thread
yunhaoling marked this conversation as resolved.
def __init__(self, body : str, encoding : str = 'UTF-8', session_id : str = None, **kwargs) -> None:
def __str__(self):

# @properties
def body(self) -> Union[bytes, Generator[bytes]]: # read-only
def partition_key(self, value : str):
def partition_key(self) -> str:
def session_id(self, value : str):
def session_id(self) -> str:
def via_partition_key(self, value: str):
def via_partition_key(self) -> str:
def time_to_live(self, value : Union[float, timedelta]):
def time_to_live(self) -> datetime.timedelta:
def annotations(self, value : dict[str, Any]):
def annotations(self) -> dict[str, Any]:
def user_properties(self, value : dict[str, Any]):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to just Properties in .NET.

def user_properties(self) -> dict[str, Any]:
def enqueue_sequence_number(self, value : int):
def enqueue_sequence_number(self) -> int:

# Methods
def schedule(self, schedule_time_utc : datetime) -> None:


class BatchMessage(Message):
# inherited from Message
def add(self, message: Message) -> None:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if we're going with what we have in EH - this should be 'try_add' and it should return a bool.

@annatisch annatisch Mar 6, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In EH Python has add, because the behaviour is to raise if the add fails.



class PeekMessage(Message):
def enqueued_time(self) -> datetime: # read-only
def scheduled_enqueue_time(self) -> datetime: # read-only
def sequence_number(self) -> int: # read-only
def partition_id(self) -> str: # read-only
def session_id(self) -> str: # read-only

class ReceivedMessage(Message):

# @properties
def settled(self) -> bool: # read-only
def enqueued_time(self) -> datetime: # read-only
def scheduled_enqueue_time(self) -> datetime: # read-only
def sequence_number(self) -> int: # read-only
def partition_id(self) -> str: # read-only
def locked_until(self) -> datetime: # read-only
def expired(self) -> bool: # read-only
def lock_token(self) -> str: # read-only
def session_id(self) -> str: # read-only

# methods
def renew_lock(self) -> None:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Richard Q: throw if receive in ReceiveAndDelete mode

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's a general question - do we throw on "invalid" methods or do we no-op?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

conclusion: throw error on "invalid" methods

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talk about this on Arch board meeting!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"This" = "should we host the settlement methods (complete, abandon, etc..) on the message or on the client?"

(JS has a different answer for this - we have a context object)

def complete(self) -> None:
def dead_letter(self, description : str = None) -> None:
def abandon(self) -> None:
def defer(self) -> None:


class ReceiveSettleMode:
PeekLock
ReceiveAndDelete


class TransportType(Enum):
Amqp
AmqpOverWebsocket


class ServiceBusSharedKeyCredential:
def __init__(self, policy, key):
def get_token(self, *scopes, **kwargs):

Comment thread
yunhaoling marked this conversation as resolved.

class AutoLockRenew:
def __init__(self, executor=None, max_workers=None):
def __enter__(self):

def __exit__(self, *args):

@richardpark-msft richardpark-msft Apr 17, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file auto-generated? What happened with this indentation? :)


def register(self, renewable, timeout=300):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a rule to suffix timeout with some sort of measurement? Ie, this must be seconds so timeoutSeconds or something of that nature?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also - what does renewable do here? Isn't it renewable by default? Does this enable something interesting?



class SubscriptionRuleManager:
# sql and correlation filter apply to user properties and system properties

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anna: may be under Receiver Scope?

# https://docs.microsoft.com/en-us/azure/service-bus-messaging/topic-filters
# rule action: https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-sql-rule-action
def __init__(
self,
fully_qualified_namespace: str,
topic_name: str,
subscription_name: str,
credential: TokenCredential,
logging_enable: bool = False,
http_proxy: dict = None,
transport_type: TransportType = TransportType.Amqp
) -> SubscriptionRuleManager

@classmethod
def from_connection_string(cls, conn_str, subscription_name, topic_name=None, **kwargs) -> SubscriptionRuleManager:

def get_rules(self, timeout: float = None) -> list[RuleDescription]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment on all of these - should timeout be suffixed with a unit?

@yunhaoling yunhaoling Apr 17, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list_rules
timeout in kwargs*

def add_rule(self, rule_name: str, filter: Union(bool, str, CorrelationFilter), sql_rule_action_expression: str, timeout: float = None):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may need to rename to "create_rule"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(thinking rule as a resource)

def remove_rule(self, rule_name: str, timeout: float = None):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"delete_rule"



class RuleDescription:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anna: making it dict-like object

#TODO: is it necessary to make it separate class or just a list[dict]?
def __init__(self):
@property
def filter(self) -> Union(str, CorrelationFilter): # TODO: return multiple types OK?
def action(self) -> str:
def name(self) -> str:


class CorrelationFilter:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dict-like object

# TODO: is it necessary to make it separate class, depending on the fact that there're so many fields,
# and how people would use this, I personally prefer it being a class
def __init__(self):

@property
def correlation_id(self, val : str):
def correlation_id(self) -> str:
def message_id(self, val : str):
def message_id(self) -> str:
def to(self, val: str):
def to(self) -> str:
def reply_to(self, val : str):
def reply_to(self) -> str:
def label(self, val: str):
def label(self) -> str:
def session_id(self, val: str):
def session_id(self) -> str:
def reply_to_session_id(self, val: str):
def reply_to_session_id(self) -> str:
def content_type(self, val: str):
def content_type(self) -> str:
def user_properties(self, val: dict):
def user_properties(self) -> dict:



Loading