I'm teaching myself Python and wanted to share this problem as my current solution feels inelegant but I'm not sure of the best way to refactor. If this sort of question isn't appropriate, leave a comment and I'll remove it.
I'm writing a generic library to handle web API requests. The aim is to have a generic core that can be fed with URL templates and parameters - so each new API is just a wrapper rather than duplicating core logic like paging, parsing, etc.
Trouble is, each URL has a completely different structure:
- Facebook feeds have an ID variable in the URL endpoint -
/{id}/feed/
- Facebook insights require per-request parameters as well eg
/{id}/insights/page_fans?since={timestamp}&until={timestamp}
- Instagram user search requires the ID to be a query parameter -
/users/search?q={}
Then all URLs need additional query parameters like access_token
which is static.
I had started with a simple string.format()
to replace the variables, but this doesn't work with dynamic parameters.
New model is to build the URL from a mixture of endpoint, static parameters and dynamic parameters. I'm then using itertools.product
to work out the permutations and then encode the URLs.
Current thinking is to create a set of methods for each common problem (date ranges, etc) and just call each one as required during the setup wrapper.
Here's an example of a working wrapper - can you see any more Pythonic ways to handle this situation?
def insights_impressions_unique(ids, access_token, date_start=None,
date_end=None, period="day"):
if period not in ["day", "week", "days_28"]:
raise ValueError("Period must be 'day','week' or 'days_28'")
# Build basic URLs
_api_domain = "https://graph.facebook.com"
_api_endpoint = "/v2.2/{}/insights/page_impressions_unique"
url_template = "{}{}".format(_api_domain, _api_endpoint)
_formatted_urls = [url_template.format(_id) for _id in ids]
# Move on to paramaters
_base_params = [{"access_token": access_token,
"period": period}, ]
# Build date periods
_utcnow = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
if date_end is None:
date_end = _utcnow
if date_start is None:
date_start = _utcnow - datetime.datetime.timedelta(days=-7)
_date_windows = utils.api_call_time_windows(date_start, date_end, freq=7)
_date_params = [{'since': x[0], 'until': x[1]} for x in _date_windows]
_combined_params = product(_base_params, _date_params)
list_of_params = list()
for i in _combined_params:
params = {}
for d in i:
for k, v in d.iteritems():
params[k] = v
list_of_params.append(urllib.urlencode(params))
_components = product(_formatted_urls, list_of_params)
_finalised_urls = ["{0}?{1}".format(_u, _p) for _u, _p in _components]
manager = RequestManager(urls=_finalised_urls, pagination=False)
return manager
Full code on this dev branch on GitHub.