7
\$\begingroup\$

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!)

\$\endgroup\$
8
\$\begingroup\$

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)
\$\endgroup\$
5
\$\begingroup\$

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('_', '/')
\$\endgroup\$
  • \$\begingroup\$ Could you clarify how I would override that in the class to a shorter version? \$\endgroup\$ – strontiux Mar 26 '18 at 13:59
  • \$\begingroup\$ @strontiux updated \$\endgroup\$ – hjpotter92 Mar 26 '18 at 14:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.