I am currently trying to create an API-wrapper. For that, I connect to the endpoint (for example https://host.example.com/api/accounts/open) with the request module. Since there are a bunch of endpoints, I need a way to sort them. Currently I have a constants file, where most of the endpoints are created like this:
HOST = 'https://wallet.shiftnrg.org'
START = ''.join([HOST, '/api'])
ACCOUNTS = ''.join([START, '/accounts'])
ACCOUNTS_OPEN = ''.join([ACCOUNTS, '/open'])
...
LOADER = ''.join([START, '/loader'])
LOADER_GET_STATUS = ''.join([LOADER, '/status'])
And I access them like this (one example):
def accounts_get_balance(address):
payload = {'address': address}
response = requests.get(constants.ACCOUNTS_GET_BALANCE,
params=payload,
timeout=constants.TIMEOUT
)
While this is working, I was wondering if there is a more efficient, pythonic way of doing this, since I am just a beginner (especially the constants.py, but if you find anything else, feel free to tell me as well!)
2 Answers 2
A tip on the second part, is looking to what Brendan Rhodes has to say about Clean Architecture and bring your IO out as much as possible. This will make testing everything a lot easier, and limit the number of God-methods that do everything from constructing the query, getting the response to parsing and persisting the result
For the first part, I would work with a dict
of destinations, that you can persist via json
or so
settings = {
'host': 'https://wallet.shiftnrg.org',
'entry-points':{
'start': ('api',),
'accounts': ('_start', 'accounts'),
'accounts_open': ('_accounts', 'open'),
'loader': ('_start', 'loader')
}
}
that you can dump to and load from json
For the entry-points, I would parse this somewhat like this:
def parse_entry_points(entry_points):
for name, path in entry_points.items():
yield name, list(expand_path(entry_points, name))
def expand_path(entry_points, item):
path = entry_points[item]
for item in path:
if item[0] != '_':
yield item
else:
yield from expand_path(entry_points, item[1:])
ENTRY_POINTS = dict(parse_entry_points(entry_points=settings['entry-points']))
Off course you can/should replace the _
-placeholder with a unicode symbol that will not figure in the eventual URLs
{'accounts': ['api', 'accounts'], 'accounts_open': ['api', 'accounts', 'open'], 'loader': ['api', 'loader'], 'start': ['api']}
assembling the URL can then be as simple as
from urllib.parse import urljoin
def assemble_url(enty_point):
parts = constants.ENTRY_POINTS[entry_point]
return urljoin(constants.HOST, parts)
If the API routes are consistent, I'd suggest making use of the __getattr__
metamethod.
A simple example would be
class APIRoutes:
_BASE = "https://wallet.shiftnrg.org/"
_BASE_API = _BASE + "api/"
def __getattr__(self, route):
return self._BASE_API + route.lower().replace('_', '/')
Now, in your code:
api_routes = APIRoutes()
.
.
...
def accounts_get_balance(address):
payload = {'address': address}
response = requests.get(api_routes.ACCOUNTS_GET_BALANCE,
params=payload,
timeout=constants.TIMEOUT
)
where api_routes.ACCOUNTS_GET_BALANCE
will generate
https://wallet.shiftnrg.org/api/accounts/get/balance
If for eg. you think that writing api_routes.A_B_C_D_E
is too cumbersome for a specific path, you can override that in the class itself to a shorter version.
class APIRoutes:
_BASE = "https://wallet.shiftnrg.org/"
_BASE_API = _BASE + "api/"
SHORT = _BASE_API + "/some/long/route/for/api"
def __getattr__(self, route):
return self._BASE_API + route.lower().replace('_', '/')
-
\$\begingroup\$ Could you clarify how I would override that in the class to a shorter version? \$\endgroup\$strontiux– strontiux2018年03月26日 13:59:56 +00:00Commented Mar 26, 2018 at 13:59
-
\$\begingroup\$ @strontiux updated \$\endgroup\$hjpotter92– hjpotter922018年03月26日 14:04:35 +00:00Commented Mar 26, 2018 at 14:04