Разобрать разделенные и вложенные имена полей из параметра URL для частичного ответа - PullRequest
1 голос
/ 16 марта 2020

В API на основе Flask-RESTful я хочу разрешить клиентам получать JSON ответ частично через параметр ?fields=.... В нем перечислены имена полей (ключи объекта JSON), которые будут использоваться для создания частичного представления большего оригинала.

В простейшей форме это может быть список, разделенный запятыми:

GET /v1/foobar?fields=name,id,date

Это можно сделать с помощью поля схемы DelimitedList webargs легко, и для меня это не проблема.


Но, чтобы разрешить вложенный ключей объектов для представления, список полей с разделителями может включать произвольно вложенные ключи, заключенные в соответствующие скобки:

GET /v1/foobar?fields=name,id,another(name,id),date
{
  "name": "",
  "id": "",
  "another": {
    "name": "",
    "id": ""
  },
  "date": ""
}
GET /v1/foobar?fields=id,one(id,two(id,three(id),date)),date
{
  "id": "",
  "one": {
    "id: "",
    "two": {
      "id": "",
      "three": {
        "id": ""
      },
      "date": ""
    }
  },
  "date": ""
}
GET /v1/foobar?fields=just(me)
{
  "just": {
    "me: ""
  }
}

У меня вопрос двоякий:

  1. Есть ли способ сделать это (проверить и десериализовать) с webargs и marshmallow изначально?

  2. Если нет, как бы я сделать это с помощью синтаксического анализа, например pyparsing? Любой намек на то, как должна выглядеть грамматика BNF, высоко ценится.

1 Ответ

1 голос
/ 16 марта 2020

Pyparsing имеет несколько полезных встроенных модулей, delimitedList и nestedExpr. Вот аннотированный фрагмент, который создает парсер для ваших значений. (Я также включил пример, где ваши элементы списка могут быть чем-то большим, чем просто алфавитные c слова):

import pyparsing as pp

# placeholder element that will be used recursively
item = pp.Forward()

# your basic item type - expand as needed to include other characters or types
word = pp.Word(pp.alphas + '_')
list_element = word

# for instance, add support for numeric values
list_element = word | pp.pyparsing_common.number

# retain x(y, z, etc.) groupings using Group
grouped_item = pp.Group(word + pp.nestedExpr(content=pp.delimitedList(item)))

# define content for placeholder; must use '<<=' operator here, not '='
item <<= grouped_item | list_element

# create parser
parser = pp.Suppress("GET /v1/foobar?fields=") + pp.delimitedList(item)

Вы можете проверить любое выражение переноса, используя runTests:

parser.runTests("""
    GET /v1/foobar?fields=name,id,date
    GET /v1/foobar?fields=name,id,another(name,id),date
    GET /v1/foobar?fields=id,one(id,two(id,three(id),date)),date
    GET /v1/foobar?fields=just(me)
    GET /v1/foobar?fields=numbers(1,2,3.7,-26e10)    
    """,  fullDump=False)

Дает:

GET /v1/foobar?fields=name,id,date
['name', 'id', 'date']
GET /v1/foobar?fields=name,id,another(name,id),date
['name', 'id', ['another', ['name', 'id']], 'date']
GET /v1/foobar?fields=id,one(id,two(id,three(id),date)),date
['id', ['one', ['id', ['two', ['id', ['three', ['id']], 'date']]]], 'date']
GET /v1/foobar?fields=just(me)
[['just', ['me']]]
GET /v1/foobar?fields=numbers(1,2,3.7,-26e10)
[['numbers', [1, 2, 3.7, -260000000000.0]]]
...