1. Чем не устраивают обозначенные варианты? Если токен должен переживать перезапуск, храни его в конфигурации. Если не должен, храни его только в памяти - в глобальной переменной или в атрибуте класса.
2. Конечно стоит. Просто для того, чтобы при истечении токена на полпути его можно было перезапросить прозрачно для клиентского кода. Например, декоратор вида:
def requires_token(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs): #оборачиваем метод, а не функцию
try:
result = func(self, *args, **kwargs) # пробуем вызвать метод как есть
except InvalidToken: # кастомное исключение, которое должны выбрасывать методы
self._acquire_new_token() # получаем новый токен
result = func(self, *args, **kwargs) # пробуем еще раз
return result
return wrapper
Тогда любой декорированный метод, который выбрасывает исключение InvalidToken, спровоцирует автоматический запрос нового токена и одну повторную попытку выполнения операции.
Но можно делать это и явно, в каждом методе.
3. Под закрытием сессии ты понимаешь инвалидацию токена, т.е. невозможность его дальнейшего использования? Ну так это тебя нужно спрашивать, нужно ли закрывать сессию и требует ли используемое API явного закрытия сессии. Планируешь ли ты выполнять дальнейшие операции в рамках этой сессии? Если точно нет, можешь закрывать - например, при выходе из скрипта. Заодно снимется вопрос о хранении токена. Если не хочешь дёргать получение токена понапрасну, тогда храни токен, но не закрывай.
Делать это в КАЖДОМ методе точно бессмысленно.