编写带有可选参数的 REST 包装函数的 Pythonic 方法

Pythonic way to write REST wrapper function that takes optional arguments

我有一个 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 stringvalue.

我的包装函数看起来像下面这样,但我迷失了用户可以为此搜索输入多个搜索参数的方式 api 调用:

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

最后,用户应该可以调用 api.search()

免责声明: 诸如什么是最 Pythonic (best/prettiest) 方式之类的问题可能会引起不必要的讨论(并分散注意力),从而产生不确定的结果。我个人的建议是,重用来自社区特定部分的建议最重要的是:在你的代码和你设计界面的方式上保持一致。想想那些将使用它们的人(包括 12 个月后的你自己)。 "The Best" 解决方案通常是预期目的的函数,不一定是通用常数(即使可能有或多或少的推荐方法)。也就是说。

如果我没理解错的话,你的参数是 key=value 对的性质(你会把它们扩展成 URL 为 search_parameters[key]=value)。尽管您示例中的 filterorientation 事件让我失望了......如果不是真的,请描述更多,我可以重新审视我的建议。为此,字典似乎是一个不错的选择。要获得一个,您的方法可以是:

def search(self, search_kwargs):
    ...

并且您希望您的用户传递参数字典 (args_dict = {'string': 'xxx', ...}; c.search(args_dict))。或者:

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

并且您希望您的用户传递 key/value 对作为方法的关键字参数 (c.search(string='xxx'))。我可能会赞成前一种选择。准备参数时 Dict 很灵活(是的,在后一种情况下您也可以传递一个 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 class 其构造函数可以采用类似于上述方法的初始参数集,但您将有更多的方法允许在执行搜索之前操纵搜索(添加更多参数)。并且每个参数添加都可能已经过有效性检查。您可以使实例可调用以执行搜索本身(相应的方法)或将其传递给以准备好的请求作为参数的搜索方法。


根据评论中的更多见解进行了更新。

如果您的 API 实际上使用(任意)嵌套映射对象,字典仍然是保存参数的良好结构。我会选择两个选项之一。

您可以使用嵌套字典,这可能会为您提供描述请求的灵活性,并且可以更准确地反映您的 REST API 理解其数据的方式 -> 您形成请求的方式与 REST 的方式更相似API描述了它。然而,使用上面提到的关键字参数不再是一个选项(或者没有类似于下一个选项的额外工作和更多翻译)。并且数据的结构可能会使(尤其是简单的情况)使用起来不那么方便。例如:

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)

或者您可以选择扁平 key/value 对的结构,引入对单个元素使用合理且不冲突的分隔符的符号。根据使用的分隔符,您甚至可以让关键字参数重新发挥作用(尽管在我的示例中不是 .)。缺点之一是,您实际上创建了一个 new/parallel 界面和符号。例如:

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 公开它的人,则后者(平面)结构可能更容易 use/write。