Pythonic способ написать функцию-обертку REST, которая принимает необязательные аргументы - PullRequest
0 голосов
/ 30 июня 2018

У меня есть REST API, и я хочу написать обертку вокруг него на Python, чтобы другие могли его использовать. Это поисковый API, и каждый параметр рассматривается как AND

Пример:

api/search/v1/search_parameters[words]=cat cute fluffy&search_parameters[author_id]=12345&search_parameters[filter][orientation]=horizontal

Какой самый Pythonic способ написать функцию, которая принимает все эти аргументы, должен указывать хотя бы один search_parameters string и value.

Моя функция-обертка будет выглядеть примерно так ниже, но я заблудился из-за того, как пользователь может ввести несколько параметров поиска для этого поискового вызова API:

def search(self):
    url = BASE_URL + search_param_url
    response = self.session.get(url)
    return response.json()

В конце концов, пользователи должны иметь возможность просто вызвать что-то вроде api.search()

1 Ответ

0 голосов
/ 30 июня 2018

Отказ от ответственности: такие вопросы, как самый питонский (лучший / самый красивый) способ, могут вызвать ненужные дискуссии (и отвлечь внимание) и привести к неубедительным результатам. Моя личная рекомендация, касающаяся повторного использования рекомендаций от определенной части сообщества, будет превыше всего: быть последовательной в вашем коде и в том, как вы разрабатываете свои интерфейсы. Подумайте о тех, кто будет их использовать (включая вас самих через 12 месяцев). Как и «Лучшее» решение, как правило, является функцией целевого назначения и не обязательно является универсальной константой (даже если могут быть более или менее рекомендуемые способы). Тем не менее.

Если я правильно понимаю, ваши параметры имеют key=value парный характер (и вы расширите их в URL как search_parameters[key]=value). Даже если filter и orientation в вашем примере отбросят меня ... если не соответствует действительности, пожалуйста, опишите немного больше, и я могу вернуться к моему предложению. Для этого словарь кажется хорошим выбором. Чтобы получить один, ваш метод может быть:

def search(self, search_kwargs):
    ...

И вы ожидаете, что ваш пользователь передаст набор параметров (args_dict = {'string': 'xxx', ...}; c.search(args_dict)). Или:

def search(self, **kwargs):
    ...

И вы ожидаете, что ваш пользователь передаст пары ключ / значение в качестве аргументов ключевого слова метода (c.search(string='xxx')). Я, вероятно, предпочел бы первый вариант. Dict является гибким, когда вы готовите параметры (и да, вы можете также передать dict в последнем случае, но этот вид превосходит цель расширения аргументов ключевого слова; всегда выбирайте более простой вариант, достигающий той же цели).

В любом случае вы можете просто взять диктовку (my_args обозначает любой из двух приведенных выше). Убедитесь, что у вас есть хотя бы один из обязательных ключей:

not ('string' in my_args or 'value' in my_args):
    raise SearchParamsError("Require 'string' or 'value'.")

Выполните любые другие проверки вменяемости. Подготовьте параметры для добавления к URL:

url_params = '&'.join(('{}={}'.format(k, my_dict[k]) for k in my_dict))

Это тривиальные вещи. Но в зависимости от ваших потребностей и использования вы можете ввести (например) SearchRequest класс, конструктор которого может принимать начальный набор параметров, аналогичный описанному выше методу, но у вас будет дополнительный метод (ы), позволяющий манипулировать поиском ( добавить больше параметров) перед его выполнением. И каждое добавление параметра уже может быть подвергнуто проверке достоверности. Вы можете сделать экземпляр вызываемым для выполнения самого поиска (соответствующий метод) или передать его в метод поиска, который принимает подготовленные запросы в качестве аргумента.


Обновлено на основе более глубокого понимания комментария.

Если ваш API фактически использует (произвольно) вложенные объекты отображения, словарь по-прежнему является хорошей структурой для хранения ваших параметров. Я бы выбрал один из двух вариантов.

Вы можете использовать вложенные словари, которые могут предоставить вам гибкость в описании запроса и могут более точно отражать то, как ваш REST API воспринимает его данные -> способ, которым вы формируете свой запрос, больше похож на то, как его описывает REST API. Однако использование аргументов ключевых слов, упомянутых выше, больше не вариант (или не без дополнительной работы, подобной следующей опции и некоторого дополнительного перевода). И структура данных может сделать (особенно простые случаи) использование этого менее удобным. E.g.:

my_dict = {'string': 'foo bar',
           'author_id': 12345,
           'filter': {'orientation': 'horizontal',
                      'other': 'baz'},
           'other': {'more': {'nested': 1,
                              'also': 2},
                     'less': 'flat'}}

def par_dict_format(in_dict, *, _pfx='search_parameters'):
    ret = []
    for key, value in in_dict.items():
        if isinstance(value, dict):
            ret.append(par_dict_format(value, _pfx='{}[{}]'.format(_pfx, key)))
        else:
            ret.append('{}[{}]={}'.format(_pfx, key, value))
    return '&'.join(ret)

Или вы можете выбрать структуру плоских пар ключ / значение, вводящих нотацию с использованием разумного и неконфликтующего разделителя для отдельных элементов. В зависимости от используемого разделителя, вы можете даже вернуть аргументы ключевых слов в игру (но не с . в моем примере, хотя). Одним из недостатков является то, что вы эффективно создаете новый / параллельный интерфейс и нотацию. E.g.:

my_dict = {'string': 'foo bar',
           'author_id': 12345,
           'filter.orientation': 'horizontal',
           'filter.other': 'baz',
           'other.more.nested': 1,
           'other.more.also': 2,
           'other.more.also': 2,
           'other.less': 'flat'}

def par_dict_format(in_dict):
    ret = []
    for key, value in in_dict.items():
        key_str = ''.join(('[{}]'.format(p) for p in key.split('.')))
        ret.append('{}={}'.format(key_str, value))
    return '&'.join(('search_parameters{}'.format(i) for i in ret))

Я бы взял эти два. Если бы я в основном строил запрос программно (например, используя разные методы для запуска разных запросов), я бы предпочел использовать словари. Если ожидаемое использование будет больше ориентировано на людей, пишущих запросы напрямую, вызывающих метод search или даже, возможно, выставляющих его через CLI, последняя (плоская) структура может быть проще для использования / записи для этого.

...