Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably run into situations where you have an object that's either too "expensive" to create every time, or too "dangerous" to give open access to. That's where the Proxy Pattern comes in. Essentially, a proxy is a stand-in. It looks and acts like the real object, but it adds a layer of logic—like security checks or caching—before it actually lets the request hit the real deal.

Defining the sensitive data store

Let's build a system for managing sensitive API keys. In a real app, this would hit a database or a vault, but for our example, we'll just use a dictionary. I want to make sure that only users with a specific "admin" clearance can actually retrieve these keys. First, we define the actual service that does the work.

class SecretStore:
    def __init__(self):
        self._secrets = {"AWS_KEY": "AKIA-SECRET-123", "STRIPE_KEY": "sk_test_456"}

    def get_secret(self, key):
        print(f"Fetching {key} from the secure vault...")
        return self._secrets.get(key, "Not found")

Adding the security gatekeeper

Now, I don't want to put the authentication logic inside the SecretStore itself. That violates the Single Responsibility Principle; the store should just store. Instead, I'll create a Proxy. This proxy will hold a reference to the real SecretStore and check the user's credentials before delegating the call.

class SecretProxy:
    def __init__(self, real_store, user_role):
        self._real_store = real_store
        self._user_role = user_role

    def get_secret(self, key):
        if self._user_role != "admin":
            raise PermissionError("Access Denied: You do not have admin privileges.")
        
        # If we get here, the proxy lets the request pass through to the real object
        return self._real_store.get_secret(key)

The "Interface" mistake

Here is where I usually mess up when I'm rushing a prototype. I just wrote the proxy as a separate class. But in a larger system, the code using this store shouldn't have to care if it's talking to the SecretStore or the SecretProxy. They should be interchangeable.

If I have a function that expects a SecretStore object and I pass it my SecretProxy, it might work because of Python's duck typing, but it's fragile. If I ever add type hinting or specific class checks, the whole thing breaks. I've seen this cause nightmare bugs in production when a developer assumes the proxy is the real object but it's missing a few methods from the original class.

The fix is simple: both the real object and the proxy should inherit from the same abstract base class. This guarantees they follow the same contract.

from abc import ABC, abstractmethod

class SecretInterface(ABC):
    @abstractmethod
    def get_secret(self, key):
        pass

class SecretStore(SecretInterface):
    def __init__(self):
        self._secrets = {"AWS_KEY": "AKIA-SECRET-123", "STRIPE_KEY": "sk_test_456"}

    def get_secret(self, key):
        print(f"Fetching {key} from the secure vault...")
        return self._secrets.get(key, "Not found")

class SecretProxy(SecretInterface):
    def __init__(self, real_store, user_role):
        self._real_store = real_store
        self._user_role = user_role

    def get_secret(self, key):
        if self._user_role != "admin":
            raise PermissionError("Access Denied: You do not have admin privileges.")
        return self._real_store.get_secret(key)

Testing the guardrail

Now we can swap these out seamlessly. The client code just knows it's dealing with something that can get_secret().

real_vault = SecretStore()

# Scenario 1: A guest user tries to access secrets
guest_proxy = SecretProxy(real_vault, "guest")
try:
    print(guest_proxy.get_secret("AWS_KEY"))
except PermissionError as e:
    print(e) # Output: Access Denied: You do not have admin privileges.

# Scenario 2: An admin user accesses secrets
admin_proxy = SecretProxy(real_vault, "admin")
print(admin_proxy.get_secret("AWS_KEY")) # Output: Fetching AWS_KEY... AKIA-SECRET-123

By using the proxy, I've successfully decoupled the "access control" logic from the "data retrieval" logic. I can now change how authentication works—maybe by adding a timeout or logging every access attempt—without ever touching the code inside the SecretStore.




📋 Practical Task

Exercise: Building a Caching Proxy for a Weather API

Imagine you are using a third-party Weather API that charges you money for every single request. To save costs, you need to implement a Caching Proxy.

Your task:

  • Create a WeatherInterface abstract base class with a method get_temperature(city).
  • Implement a RealWeatherAPI class that inherits from the interface. To simulate a network call, make get_temperature print "Connecting to remote server..." before returning a hardcoded temperature (e.g., 25 degrees).
  • Implement a WeatherProxy class that also inherits from the interface. This proxy should:
    • Maintain an internal dictionary to act as a cache.
    • Check if the temperature for the requested city is already in the cache.
    • If it is in the cache, return it immediately without calling the RealWeatherAPI.
    • If it is NOT in the cache, call the RealWeatherAPI, store the result in the cache, and then return it.
  • Test your implementation by requesting the temperature for "London" twice. The first time should print "Connecting to remote server...", but the second time should return the value instantly without printing the connection message.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.