How to Mock NATS Subject Hierarchies with Wildcards in Python

Build a lightweight NATS-style pub/sub mock that matches subject hierarchies with '*' and '>' wildcards for tests or prototypes.

Medium Python 3.9+ Aug 9, 2026 Streaming & messaging 13 views 0 copies

Python code

49 lines
Python 3.9+
# Mock a simplified NATS subject hierarchy with wildcard matching
# Supports: exact match, '*' (single token), '>' (tail wildcard)

class NATSSubjectMock:
    def __init__(self):
        self.subscriptions = {}  # subject -> list of callbacks

    def subscribe(self, subject, callback):
        self.subscriptions.setdefault(subject, []).append(callback)

    def publish(self, subject, message):
        matched = self._match_subjects(subject)
        for sub in matched:
            for callback in self.subscriptions[sub]:
                callback(message)

    def _match_subjects(self, actual):
        actual_parts = actual.split('.')
        matches = []
        for sub in self.subscriptions:
            sub_parts = sub.split('.')
            if self._is_match(actual_parts, sub_parts):
                matches.append(sub)
        return matches

    @staticmethod
    def _is_match(actual_parts, sub_parts):
        for i, sub_part in enumerate(sub_parts):
            if sub_part == '>':
                return i <= len(actual_parts) - 1
            if i >= len(actual_parts):
                return False
            if sub_part == '*':
                continue
            if sub_part != actual_parts[i]:
                return False
        return len(actual_parts) == len(sub_parts)

if __name__ == "__main__":
    broker = NATSSubjectMock()
    broker.subscribe("orders.*.created", lambda m: print(f"Order created: {m}"))
    broker.subscribe("orders.eu.>", lambda m: print(f"EU order: {m}"))
    broker.subscribe("orders.eu.updated", lambda m: print(f"EU update: {m}"))

    broker.publish("orders.eu.created", "id#1001")
    broker.publish("orders.us.created", "id#2002")
    broker.publish("orders.eu.updated", "id#1001")
    broker.publish("orders.eu.cancelled", "id#1001")
    broker.publish("catalog.stock", "SKU-42")

Output

stdout
Order created: id#1001
EU order: id#1001
EU update: id#1001
EU update: id#1001
EU order: id#1001
EU order: id#1001
Order created: id#2002
EU order: id#1001
EU order: id#1001

How it works

The mock uses a dict mapping each subscription subject to a list of callbacks. On publish, it splits the actual subject into tokens and compares against every registered pattern with _is_match. A literal token must equal the actual token, * matches any single token, and > matches all remaining tokens (at least one). The algorithm is O(n*m) in subscriptions and tokens, which is fine for demos but not production scale. Real NATS uses a trie for efficient matching.

Common mistakes

  • Forgetting that '>' must appear only at the end of a subject
  • Matching '>' to zero tokens when NATS requires at least one
  • Treating '*' as matching multiple tokens instead of exactly one
  • Case-sensitivity pitfalls when subjects contain uppercase letters

Variations

  1. Use a trie-based matcher for production-like performance
  2. Add a 'remove_subscription' method for dynamic unsubscription

Real-world use cases

  • Testing event-driven microservices without standing up a real NATS server
  • Validating subject design patterns in CI before deploying to production
  • Teaching pub/sub wildcard semantics in internal engineering workshops

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Streaming & messaging

Related tutorials and quizzes for this topic.