Why? •Think about how many times you’ve started with someone else’s library... •Used it some... •Then wanted to strangle them for the hoops they made you jump through
Approaches On Design •Bottom-up sucks. •Sure, you built little pieces that work. •But do they work well together? •Likely not. •HOW IS BABBY PHP FORMED
Small Components class DoEverything(object): # ... def update_cache(self, timeout=600): # Stuff... def post_to_twitter(self, user): # More stuff... def make_me_a_sandwich(self, sudo=False): # Even more stuff...
Composition >= Inheritance class CachedView(View): def get(self, request, **kwargs): # Cache the output & serve. class GhettoAPIView(View): def get(self, request, **kwargs): # Return either JSON or HTML. # Now try to use both. :/ class MyView(CachedView, GhettoAPIView): # Oh crap, they stomp on each other. pass
Broad Familiarity class SearchQuerySet(object): # Make it look like ``QuerySet``. def __getitem__(self, key): # Handle the slice here. def filter(self, **kwargs): # Apply filters. def raw(self, query): # Do a raw query. # Usage: sqs = SearchQuerySet().filter(text='banana') results = sqs[20:20]
Protocol class CoolKidBackend(object): # Whatever, as if we care. pass class RedisBackend(CoolKidBackend): def get(self, key): return self.conn.hget(key) class RiakBackend(CoolKidBackend): def get_hash(self, bucket, key): return self._conn.get(bucket, key) # You can't transparently switch backends. # Welp, time to go rewrite everything.
Protocol class CoolKidBackend(object): def __init__(self, conn_string): # Parse the bucket out of # the conn_string, if present. def get(self, key): raise NotImplementedError('...') class RedisBackend(CoolKidBackend): def get(self, key): return self.conn.hget(key) class RiakBackend(CoolKidBackend): def get(self, key): return self._conn.get(...)
Stop At A Low Level def get_raw_page(socket, timeout=60): # Do the actual fetch. return headers, content def get_page(url, timeout=None, fetcher=get_raw_page): # Validate the URL. # Open a socket. head, content = fetcher(my_socket) if head.status == 500: raise OhNoes('...') # ...
Wildly Different Return Values def query(engine_url, query, limit=10): # Make the user deal with the raw data. results = [res for res in results if res] return results, total def raw_query(engine_url, query, limit=10): # Make the user deal with the raw data. return { 'search_results': results, 'count': total, }
Very Little Global State # Only defaults or things that absolute # have to be module-level. BASE_URL = 'http://w1.weather.gov/obhistory/' TIMEOUT = 10 # Make them defaults but allow passing # different in their place. def fetch_page(self, url=BASE_URL, timeout=TIMEOUT): return requests.get( url, timeout=timeout ).content
Decrease Reliance On ``self`` class Weather(object): def build_url(self, station): # Make a URL. def fetch_page(self, url): # Get a page def parse_page(self, content): # Use the page content. def current_temperature(self, station): url = self.build_url(station) content = self.fetch_page(url) data = self.parse_page(content) # You could be using ``self.`` on # these, but by allowing data to be passed # in, you make it easier to test.
So... •Use the Golden Rule. •Consistency is key. •Plan for the worst & include sweet shortcuts for the best. •Write a thing you’d to use... •...then make it even better.