Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Design of Everyday APIs - PyCon 2024

The Design of Everyday APIs - PyCon 2024

What makes a good API for a library? Or more importantly, what makes it bad? This talk discusses the principles of what goes into user-centered design, and how best to apply those principles when writing a Python library for fellow developers.

Lynn Root

May 17, 2024
Tweet

More Decks by Lynn Root

Other Decks in Programming

Transcript

  1. – Don Norman, The Design of Everyday Things Design is

    concerned with how things work, how they are controlled, and the nature of the interaction between people and technology.
  2. – Don Norman, The Design of Everyday Things Design is

    concerned with how things work, how they are controlled, and the nature of the interaction between people and technology.
  3. – Don Norman, The Design of Everyday Things Two of

    the most important characteristics of good design are discoverability and understanding.
  4. Discoverability: am I able to figure out what actions are

    possible, and where and how to perform them?
  5. 5 Key Elements of Discoverability ► Affordances ► Signi fi

    ers ► Constraints ► Mappings ► Feedback
  6. 1 of 2 class Message: def __init__(self, id: str, data:

    str, published_at: str) -> None: self.id = id self.data = data self.published_at = published_at Library
  7. class Message: def __init__(self, id: str, data: str, published_at: str)

    -> None: class PubSubClient: def __init__(self, topic: str, subscription: str) -> None: def create_topic(self) -> None: def create_subscription(self) -> None: def add_message(self, msg: Message, timeout: int, retries: int) -> None: def get_message(self, timeout: int, retries: int) -> Message | None: def mark_message_done(self, msg_id: str, timeout: int, retries: int) -> None: def clear_message_queue(self) -> None: pass def close_client(self) -> None: pass Library 2 of 2
  8. 1 of 2 client = chaos_queue.PubSubClient(TOPIC, SUBSCRIPTION) try: client.create_topic() except

    chaos_queue.TopicExists: pass try: client.create_subscription() except chaos_queue.SubscriptionExists: pass User
  9. 2 of 2 for i in range(5): message = chaos_queue.Message(

    id=str(uuid.uuid4()), data=f"hello {i}", published_at=datetime.datetime.now().isoformat() ) try: client.add_message(message, 1, 0) except (chaos_queue.TimeoutError, queue.Full): pass while True: message = client.get_message(2, 2) if not message: break print(message) process_data(message.data) client.mark_message_done(message, 0, 0) client.close_client() User
  10. for i in range(5): message = chaos_queue.Message( id=str(uuid.uuid4()), data=f"hello {i}",

    published_at=datetime.datetime.now().isoformat() ) try: client.add_message(message, 1, 0) except (chaos_queue.TimeoutError, queue.Full): pass while True: message = client.get_message(2, 2) if not message: break print(message) process_data(message.data) client.mark_message_done(message, 0, 0) client.close_client() User 2 of 2
  11. for i in range(5): message = chaos_queue.Message( id=str(uuid.uuid4()), data=f"hello {i}",

    published_at=datetime.datetime.now().isoformat() ) try: client.add_message(message, 1, 0) except (chaos_queue.TimeoutError, queue.Full): pass while True: message = client.get_message(2, 2) if not message: break print(message) process_data(message.data) client.mark_message_done(message, 0, 0) client.close_client() User 2 of 2
  12. for i in range(5): message = chaos_queue.Message( id=str(uuid.uuid4()), data=f"hello {i}",

    published_at=datetime.datetime.now().isoformat() ) try: client.add_message(message, 1, 0) except (chaos_queue.TimeoutError, queue.Full): pass while True: message = client.get_message(2, 2) if not message: break print(message) process_data(message.data) client.mark_message_done(message, 0, 0) client.close_client() User 2 of 2
  13. before class PubSubClient: def __init__(self, topic: str, subscription: str) ->

    None: def create_topic(self) -> None: def create_subscription(self) -> None: def add_message(self, msg: Message, timeout: int, retries: int) -> None: def get_message(self, timeout: int, retries: int) -> Message | None: def mark_message_done(self, msg_id: str, timeout: int, retries: int) -> None: def clear_message_queue(self) -> None: pass def close_client(self) -> None: pass Library
  14. before class PubSubClient: def __init__(self, topic: str, subscription: str) ->

    None: def create_topic(self) -> None: def create_subscription(self) -> None: def add_message(self, msg: Message, timeout: int, retries: int) -> None: def get_message(self, timeout: int, retries: int) -> Message | None: def mark_message_done(self, msg_id: str, timeout: int, retries: int) -> None: def clear_message_queue(self) -> None: pass def close_client(self) -> None: pass Library
  15. after class PubSubClient: def __init__(self, topic: str, subscription: str) ->

    None: def create_topic(self) -> None: def create_subscription(self) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close_client(self) -> None: Library
  16. after class PubSubClient: def __init__(self, topic: str, subscription: str) ->

    None: def create_topic(self) -> None: def create_subscription(self) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close_client(self) -> None: Library
  17. before Library class PubSubClient: def __init__(self, topic: str, subscription: str)

    -> None: def create_topic(self) -> None: def create_subscription(self) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close_client(self) -> None:
  18. before Library class PubSubClient: def __init__(self, topic: str, subscription: str)

    -> None: def create_topic(self) -> None: def create_subscription(self) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close_client(self) -> None:
  19. after class PubClient: def __init__(self, topic: str) -> None: def

    create(self) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close(self) -> None: Library
  20. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close(self) -> None: before Library
  21. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close(self) -> None: before Library
  22. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close(self) -> None: after Library
  23. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close(self) -> None: after Library
  24. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close(self) -> None: before Library
  25. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, timeout: int, retries: int) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, timeout: int, retries: int) -> Message | None: def ack(self, msg_id: str, timeout: int, retries: int) -> None: def drain(self) -> None: def close(self) -> None: before Library
  26. after class PubClient: def __init__(self, topic: str) -> None: def

    create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, timeout: int = 30, retries: int = 0, ) -> None: def close(self) -> None: class SubClient: ... Library
  27. after Library class PubClient: def __init__(self, topic: str) -> None:

    def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, timeout: int = 30, retries: int = 0, ) -> None: def close(self) -> None: class SubClient: ...
  28. after class PubClient: def __init__(self, topic: str) -> None: def

    create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, timeout: int = 30, retries: int = 0, request_id: RequestIdType | bytes | str | None = None, debug: DebugLevelType | int | bool | None = None, ) -> None: def close(self) -> None: class SubClient: ... Library
  29. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, **kwargs) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message | None: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library Demo Only!
  30. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, **kwargs) -> None: def close(self) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message | None: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library Demo Only! A void star-kwargs
  31. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, **kwargs) -> None: def close(self) -> None: before Library
  32. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, msg: Message, **kwargs) -> None: def close(self) -> None: before Library
  33. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: after Library
  34. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: after Library
  35. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: ... for message in msgs: self._queue.put(message, timeout=timeout) def close(self) -> None: after Library
  36. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message | None: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: before Library
  37. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message | None: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: before Library
  38. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message | None: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: before Library
  39. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library
  40. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: ... try: return self._queue.get(timeout=timeout) except queue.Empty: raise ChaosEmptyError("Subscription queue is empty!") def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library
  41. class Message: def __init__(self, id: str, data: str, published_at: str)

    -> None: self.id = id self.data = data self.published_at = published_at before Library
  42. OptStr = str | None class Message: def __init__( self,

    data: str, id: OptStr = None, published_at: OptStr = None ): self.data = data self.id = id or str(uuid.uuid4()) self.published_at = published_at or datetime.datetime.now().isoformat() after Library
  43. OptStr = str | None class Message: def __init__( self,

    data: str, id: OptStr = None, published_at: OptStr = None ): self.data = data self.id = id or str(uuid.uuid4()) self.published_at = published_at or datetime.datetime.now().isoformat() def __repr__(self) -> str: return f"Message(id={self.id}, published_at={self.published_at})" after Library
  44. from dataclasses import dataclass, field @dataclass class Message: data: str

    = field(repr=False) id: str = field(default=None) published_at: str = field(default=None) def __post_init__(self): self.id = self.id or str(uuid.uuid4()) self.published_at = self.published_at or datetime.datetime.now().isoformat option 2 after Library
  45. from attrs import define, field @define class Message: data: str

    = field(repr=False) id: str = field() published_at: str = field() @id.default def set_id_default(self): return str(uuid.uuid4()) @published_at.default def set_published_at_default(self): return datetime.datetime.now().isoformat() after Library option 3
  46. Use domain nomenclature Naming shouldn’t 
 be hard Provide symmetry

    Provide sane defaults Minimize repetition Be predictable and precise Let users be lazy Flexible Intuitive Tenet 3…
  47. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: before Library
  48. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: before Library
  49. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, msg_id: str, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: before Library
  50. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, msg: Message, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library
  51. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, msg: Message, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library
  52. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, *msgs: Message, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library
  53. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, *msgs: Message, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: before Library
  54. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def ack(self, *msgs: Message, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: before Library
  55. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def iter(self, **kwargs) -> Iterator[Message]: ... while not self._queue.empty(): message = self._queue.get(timeout=timeout) yield message def ack(self, *msgs: Message, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library
  56. class SubClient: def __init__(self, subscription: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def iter(self, **kwargs) -> Iterator[Message]: ... while not self._queue.empty(): message = self._queue.get(timeout=timeout) yield message raise StopIteration def ack(self, *msgs: Message, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: after Library
  57. before class PubClient: def __init__(self, topic: str) -> None: def

    create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: Library
  58. before class PubClient: def __init__(self, topic: str) -> None: def

    create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: Library
  59. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: def __enter__(self) -> Self: return self def __exit__(self, *args) -> None: self.close() after Library
  60. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: def __enter__(self) -> Self: return self def __exit__(self, *args) -> None: self.close() after Library
  61. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: def __enter__(self) -> Self: return self def __exit__(self, *args) -> None: self.close() Library before
  62. class PubClient: def __init__(self, topic: str) -> None: def create(self)

    -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: def __enter__(self) -> Self: return self def __exit__(self, *args) -> None: self.close() Library before
  63. class PubClient: def __init__(self, topic: str) -> None: def create(self,

    exist_ok: bool = False) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: def __enter__(self) -> Self: return self def __exit__(self, *args) -> None: self.close() Library after Library
  64. class PubClient: def __init__(self, topic: str) -> None: def create(self,

    exist_ok: bool = False) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: def __enter__(self) -> Self: return self def __exit__(self, *args) -> None: self.close() Library after Library
  65. ## Installation $ pip install chaos-queue ## Get Started import

    chaos_queue messages = [chaos_queue.Message(str(i)) for i in range(5)] pub_client = chaos_queue.PubClient(TOPIC) with pub_client.create(): pub_client.publish(*messages, timeout=1, retry=False) README.md
  66. ## Installation $ pip install chaos-queue ## Get Started import

    chaos_queue messages = [chaos_queue.Message(str(i)) for i in range(5)] pub_client = chaos_queue.PubClient(TOPIC) with pub_client.create(): pub_client.publish(*messages, timeout=1, retry=False) ## Learn more Just go to chaos-queue.readthedocs.io for documentation and more examples! README.md
  67. Use domain nomenclature Naming shouldn’t 
 be hard Provide symmetry

    Provide sane defaults Minimize repetition Be predictable and precise Let users be lazy Provide composable functions Leverage language idioms Provide convenience Flexible Intuitive Simple
  68. before class Message: def __init__(self, id: str, data: str, published_at:

    str) -> None: self.id = id self.data = data self.published_at = published_at class PubSubClient: def __init__(self, topic: str, subscription: str) -> None: def create_topic(self) -> None: def create_subscription(self) -> None: def add_message(self, msg: Message, timeout: int, retries: int) -> None: def get_message(self, timeout: int, retries: int) -> Message | None: def mark_message_done(self, msg_id: str, timeout: int, retries: int) -> None: def clear_message_queue(self) -> None: pass def close_client(self) -> None: pass Library
  69. after @define class Message: data: str = field(repr=False) id: str

    = field() published_at: str = field() @id.default def set_id_default(self): return str(uuid.uuid4()) @published_at.default def set_published_at_default(self): return datetime.datetime.now().isoformat() class PubClient: def __init__(self, topic: str) -> None: def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def publish(self, *msgs: Message, **kwargs) -> None: def close(self) -> None: def __enter__(self) -> Self: def __exit__(self, *args) -> None: class SubClient: def __init__(self, subscription: str) -> None: def create(self) -> None: def delete(self) -> None: def update(self, config: dict) -> None: def pull(self, **kwargs) -> Message: def iter(self, **kwargs) -> Iterable[Message]: def ack(self, *msgs: Message, **kwargs) -> None: def drain(self) -> None: def close(self) -> None: def __enter__(self) -> Self: def __exit__(self, *args) -> None: Library
  70. before client = chaos_queue.PubSubClient(TOPIC, SUBSCRIPTION) try: client.create_topic() except chaos_queue.TopicExists: pass

    try: client.create_subscription() except chaos_queue.SubscriptionExists: pass for i in range(5): message = chaos_queue.Message( id=str(uuid.uuid4()), data=f"hello {i}", published_at=datetime.datetime.now().isoformat() ) try: client.add_message(message, 1, False) except (chaos_queue.TimeoutError, queue.Full): pass while True: message = client.get_message(2, True) if not message: break print(message) process_data(message.data) client.mark_message_done(message, None, False) client.close_client() User
  71. after messages = [chaos_queue.Message(data=f"hello {i}") for i in range(5)] with

    chaos_queue.PubClient(TOPIC) as pub_client: pub_client.create(exist_ok=True) try: pub_client.publish(messages, timeout=1) except chaos_queue.TimeoutError: pass with chaos_queue.SubClient(SUBSCRIPTION) as sub_client: sub_client.create(exist_ok=True) for message in sub_client.iter(): print(message) process_data(message.data) sub_client.ack(message) User
  72. Use domain nomenclature Naming shouldn’t 
 be hard Provide symmetry

    Provide sane defaults Minimize repetition Be predictable and precise Let users be lazy Provide composable functions Leverage language idioms Provide convenience Flexible Intuitive Simple