Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
376: The Proxy Pattern
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
WeatherInterfaceabstract base class with a methodget_temperature(city). - Implement a
RealWeatherAPIclass that inherits from the interface. To simulate a network call, makeget_temperatureprint "Connecting to remote server..." before returning a hardcoded temperature (e.g., 25 degrees). - Implement a
WeatherProxyclass 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.
There are no comments for now.