Полноценный REST API для перфекционистов за 5 минут

2f2e45c507a846ec88030b21a1d0610c.png

Привет Хабр! Меня зовут Владимир, мне 28 лет и я наркоман наркоман. Мой наркотик — простота. На простоту я подсел из-за своего перфекционизма, которым меня наградили при рождении. Врачи говорят, что это взаимосвязано, мол перфекционизм — это стремление к совершенству, а простота позволяет подобраться к этому мифическому совершенству. Чем проще решение, тем меньше ошибок можно допустить, вот я и подсел. Я не стал с ними спорить и вместо того, что бы искать виновников моей истории, решил с этим жить и постараться повысить качество этой самой жизни.

Мир вокруг не идеален, сложную вещь сделать простой — невероятно сложно, поэтому всё чрезмерно усложнено. Людям нравиться чувствовать себя профессионалами, поэтому они оперируют сложными терминами, когда в этом нет необходимости, так они ощущают свою значимость и заполняют пустоту, которая образовалась из-за страха потерянного времени.

Усложнить можно всё: ты/Вы/вы
Почему я должен постоянно вспоминать о том, общался я с этим человеком или нет? Мы уже перешли на ты? Я пишу какое-то коммерческое предложение и мне надо «Выкнуть» или это ответ на рядовое письмо и достаточно простого «вы». Почему мне постоянно надо расстраиваться, когда мне с порога совершенно незнакомый человек начинает «тыкать», как будто я его друг с курилки?

Я принципиально всем «выкаю» с маленькой буквы и я убеждён, что это имеет свою цену. Наверняка есть люди, которым не нравится, что их назвали с маленькой буквы и они не ответят на моё письмо.

Так зачем эти сложности?


Когда я упомянул про повышения качества жизни, я имел ввиду некоторые правила, которые помогают быстро принимать решения и не жалеть об этом. Например я не использую сложные вещи постоянно, не общаюсь с людьми, которым нравится всё усложнять. Если вещь, которой вы пользуетесь каждый день — сложна, то это значит только то, что ей не уделили достаточно внимания. Если человек вам не может объяснить даже трудную для понимания тему простым языком, значит он сам не до конца понимает о чём говорит.

Есть ещё кое-что, что имеет не менее важное значение — время. Время — бесценный ресурс, а выражение «Время — деньги» из уст умных людей вызывает у меня улыбку с разочарованием (выглядишь как идиот и чувствуешь себя так же). Я до сих пор не знаю ни одного миллиардера, которому деньги помогли прожить дольше других людей.

Введение


Речь пойдёт об инструменте, который позволит вам построить полноценный и простой в использовании REST API за минимальное количество времени. Называется он — Python Eve.

К сожалению в Интернете очень много инструкций на эту тему, но все они вводят в заблуждение. Начинающие разработчики, начитавшись подобных статей, думают, что REST API это GET/POST/PUT/DELETE. Заказчики думают, что это дело пары часов. А когда они встречаются вместе, происходят магия в виде Express.js/Mongoose/Passport и ещё кучи хлама, который течёт и временами блокирует event-loop. Всё это запускается с помощью какого-нибудь supervisor, потому что иногда падает и надо как-то перезапускать.

И всё бы ничего, но вчера у меня состоялся разговор с хабра-пользователем, который предложил воспользоваться »Express.js, MongoDB, Mongoose, Passport, отладчиком WebStorm’a и головой на плечах». Похожие разговоры случались часто, поэтому я решил написать эту статью и «отсылать» ссылкой на неё.

Полноценный REST API?


Речь не только о реализации архитектурного стиля REST API, но и о протоколе HTTP, о валидации и кэшировании, о HATEOAS (wikipedia), о котором, похоже, вообще предпочитают не вспоминать. Затем нам понадобится фильтрация результатов и сортировка, постраничная навигация и частичное обновление записей. Потом мы задумаемся о целостности данных и условных запросах. Наверняка нам понадобится аутентификация, возможно захотим отображать данные не только в JSON, но и в XML. Это ещё про версионность и вложенные записи я не упомянул. Затем, как это обычно бывает, какой-то #$%$%^ начнёт долбить в наш могучий API с тяжёлым запросом и нам понадобится ограничить частоту запросов.

Даже, если представить, что разработкой такого API займётся невероятно крутой разработчик с 3-мя мониторами, отладчиком WebStorm’a и головой на плечах, он затратит на это не просто много времени, а очень много. Поддержка кодовой базы будет обходиться дорого, а внедрение новых функций будет долгим.

07c1f0d590fe4b4fbee0345e2054427d.jpg

Но мы с вами простоту — любим, а время — уважаем. Так приступим же!

Установка


Это обычный python-пакет, поэтому устанавливается он стандартным способом:

$ pip install eve


Если вас интересуют альтернативные методы установки, можете заглянуть в официальную документацию.

Быстрый старт


Перед тем, как мы начнём «творить» магию, нам понадобится база данных MongoDB. Если у вас её нет, вы можете воспользоваться любым бесплатным сервисом, например MongoLab. Регистрация займёт не больше минуты. После регистрации создайте базу данных и пользователя для этой базы.

Теперь давайте напишем минимальную версию нашего REST API. Для начала создадим главный файл run.py со следующим содержимым:

from eve import Eve
app = Eve()

if __name__ == '__main__':
    app.run()


Теперь нам надо создать файл настроек settings.py:

# замените user, password, ds049945.mongolab.com, example на ваши данные доступа к БД.
MONGO_URI = "mongodb://user:password@ds049945.mongolab.com:49945/example"

# По умолчанию Eve запускает API в режиме "read-only" (т.е. поддерживаются только GET запросы),
# мы включаем поддержку методов POST, PUT, PATCH, DELETE.
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']

DOMAIN = {
    # Описываем ресурс `/users`
    'users': {
        # Здесь мы описываем модель данных. Для валидации используется модуль Cerberus от автора Eve.
        # Вы можете ознакомиться с ним в официальной документации модуля http://docs.python-cerberus.org/en/stable/.
        # Либо прочитать заметки в официальной документации EVE http://python-eve.org/validation.html#validation.
        'schema': {
            'username': {
                'type': 'string',
                'minlength': 5,
                'maxlength': 32,
                'required': True,
                # уникальное поле (индекс не создаётся, просто значение должно быть уникальным)
                'unique': True,
            },
            'firstname': {
                'type': 'string',
                'minlength': 1,
                'maxlength': 10,
                'required': True,
            },
            'lastname': {
                'type': 'string',
                'minlength': 1,
                'maxlength': 15,
                'required': True,
            },
            'role': {
                
                'type': 'list', # тип: список
                'allowed': ["author", "contributor"], # разрешаем использовать значения: "author", "contributor"
            },
            'location': {
                'type': 'dict', # тип: словарь
                # описываем "схему" словаря
                'schema': {
                    'address': {'type': 'string'},
                    'city': {'type': 'string'}
                },
            },
            'born': {
                'type': 'datetime',
            },
            'active': {
                'type': 'boolean',
                'default': True
            }
        }
    },

    # Описываем ресурс `/groups`
    'groups': {
        # Описываем модель данных (см. выше).
        'schema': {
            'title': {
                'type': 'string',
                'minlength': 5,
                'maxlength': 32,
                'required': True,
                'unique': True
            },
            'users': {
                'type': 'list',  # тип: список
                'default': [],   # по умолчанию: пустой список
                # описываем "схему" списка
                'schema': { 
                    'type': 'objectid', # тип данных: objectid
                    # ссылаемся на запись в другой коллекции
                    'data_relation': {
                        'resource': 'users',  # на ресурс `users` (который мы описали выше)
                        'field': '_id',  # на поле `_id`
                        'embeddable': True
                    }
                }
            }
        }
    }
}


На мой взгляд здесь всё достаточно просто и вопросов возникнуть не должно. Если это не так — добро пожаловать в комментарии. Полный список параметров конфигурации вы можете глянуть в документации.

Всё готово, запускаем:

$ python3.5 run.py
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Прелюдия


Вы уже подумали, что сейчас мы начнём безбожно »curlить», но я вынужден вас разочаровать. Мы, со свойственным нам перфекционизмом, воспользуемся инструментом автора, который обладает чувством прекрасного:

98b5e65a30c9467fae795626e25a3e8b.png
Инструмент называется HTTPie и ставится в один клик одну команду:

$ pip install httpie


Игрища и забавы


HTTPie вызывается командой »http». Для того, что бы отправить GET запрос к нашему API, выполним:

$ http http://0.0.0.0:5000/
HTTP/1.0 200 OK
Content-Length: 99
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:13:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_links": {
        "child": [
            {
                "href": "users",
                "title": "users"
            },
            {
                "href": "groups",
                "title": "groups"
            }
        ]
    }
}


Благодаря HATEOAS мы видим, что у нас есть 2 ресурса: users и groups. Заглянем внутрь:

» http http://0.0.0.0:5000/users
HTTP/1.0 200 OK
Content-Length: 166
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:20:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 0

{
    "_items": [],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users",
            "title": "users"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 0
    }
}


Давайте создадим пользователя johndoe:

$ http http://0.0.0.0:5000/users username=johndoe
HTTP/1.0 422 UNPROCESSABLE ENTITY
Content-Length: 184
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:22:44 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_error": {
        "code": 422,
        "message": "Insertion failure: 1 document(s) contain(s) error(s)"
    },
    "_issues": {
        "firstname": "required field",
        "lastname": "required field"
    },
    "_status": "ERR"
}


Первое, на что стоит обратить внимание, это на нашу команду:

$ http http://0.0.0.0:5000/users username=johndoe


HTTPie увидел, что мы отправляем параметр username и превратил его в JSON:

{
    "username": "johndoe"
}


Затем отправил нашему API методом POST. Давайте обратим внимание на ошибки:

"_issues": {
    "firstname": "required field",
    "lastname": "required field"
}


Мы видим сразу весь список ошибок валидации и это здорово. Исправим их и выполним запрос повторно:

$ http http://0.0.0.0:5000/users username=johndoe firstname=John lastname=Doe
HTTP/1.0 201 CREATED
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:34:42 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
    "_etag": "24509359443095dd05dece6d0eb7d98cce70b076",
    "_id": "56b78e41cf7b35255aa5a1e6",
    "_links": {
        "self": {
            "href": "users/56b78e41cf7b35255aa5a1e6",
            "title": "User"
        }
    },
    "_status": "OK",
    "_updated": "Sun, 07 Feb 2016 18:34:41 GMT"
}


Ну вот, мы только что успешно создали нового пользователя. Давайте проверим так ли это:

$ http http://0.0.0.0:5000/users
HTTP/1.0 200 OK
Content-Length: 504
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:36:00 GMT
Last-Modified: Sun, 07 Feb 2016 18:34:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
            "_etag": "24509359443095dd05dece6d0eb7d98cce70b076",
            "_id": "56b78e41cf7b35255aa5a1e6",
            "_links": {
                "self": {
                    "href": "users/56b78e41cf7b35255aa5a1e6",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 18:34:41 GMT",
            "active": true,
            "firstname": "John",
            "lastname": "Doe",
            "username": "johndoe"
        }
    ],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users",
            "title": "users"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 1
    }
}


Нет никаких сомнений, что это так. Настало время попробовать перезаписать (обратите внимание, не отредактировать, а перезаписать) пользователя. Делается это с помощью метода PUT, который надо указать явно (если не указать, будет выполнен POST):

$ http put http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 username=janedoe firstname="Jane" lastname="Doe"
HTTP/1.0 403 FORBIDDEN
Content-Length: 101
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:43:04 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_error": {
        "code": 403,
        "message": "An etag must be provided to edit a document"
    },
    "_status": "ERR"
}


Упс, ошибка. Я выше упоминал о целостности данных. Дело в том, что может произойти ситуация, в которой кто-то уже изменил запись, которую хотите поменять вы. И когда вы её отредактируете, то будете думать, что изменили одну запись, но на самом деле уже совсем другую.

Для того, что бы мы в такой ситуации не оказались, используется идентификатор ETag. В двух словах — это уникальный идентификатор, который генерируется Eve при каждом изменении записи. Используя этот идентификатор мы можем сказать нашему API, что хотим изменить запись только определённой версии и если она с тех была отредактирована, то наши изменения выполнены не будут. Делается это с помощью условного запроса с HTTP заголовком »If-Match»:

$ http put http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 "If-Match":"24509359443095dd05dece6d0eb7d98cce70b076" username=janedoe firstname="Jane" lastname="Doe"
HTTP/1.0 200 OK
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:46:56 GMT
ETag: 0138d193174528c205827ba9af25b7b8fb93940e
Last-Modified: Sun, 07 Feb 2016 18:46:56 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
    "_etag": "0138d193174528c205827ba9af25b7b8fb93940e",
    "_id": "56b78e41cf7b35255aa5a1e6",
    "_links": {
        "self": {
            "href": "users/56b78e41cf7b35255aa5a1e6",
            "title": "User"
        }
    },
    "_status": "OK",
    "_updated": "Sun, 07 Feb 2016 18:46:56 GMT"
}


Обратите внимание каким образом мы передали HTTP заголовок HTTPie. Это не единственное, для чего может использоваться идентификатор ETag и условные запросы. Я здесь не буду останавливаться на этом, но советую вам ознакомиться с этой темой, если вы этого ещё не сделали.

Настало время создать новую группу. Для начала попробуем создать группу с несуществующим пользователем:

$ http http://0.0.0.0:5000/groups title="Friends" users:='["56b77466cf7b352414deb451"]'
HTTP/1.0 422 UNPROCESSABLE ENTITY
Content-Length: 220
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:14:31 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_error": {
        "code": 422,
        "message": "Insertion failure: 1 document(s) contain(s) error(s)"
    },
    "_issues": {
        "users": {
            "0": "value '56b77466cf7b352414deb451' must exist in resource 'users', field '_id'."
        }
    },
    "_status": "ERR"
}


И действительно, пользователя с таким _id не существует. Обратите внимание как мы передаём список пользователей:

users:='["56b77466cf7b352414deb451"]'


Подробнее вы можете почитать в официальной документации к HTTPie, которая такая же качественная, как и сам инструмент.

На этот раз мы укажем правильный _id:

» http http://0.0.0.0:5000/groups title="Friends" users:='["56b78e41cf7b35255aa5a1e6"]'
HTTP/1.0 201 CREATED
Content-Length: 278
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:21:42 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 19:21:41 GMT",
    "_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
    "_id": "56b79945cf7b35255aa5a1e7",
    "_links": {
        "self": {
            "href": "groups/56b79945cf7b35255aa5a1e7",
            "title": "Group"
        }
    },
    "_status": "OK",
    "_updated": "Sun, 07 Feb 2016 19:21:41 GMT"
}


«Усё добра», как говорила моя прабабушка. Проверим:

$ http http://0.0.0.0:5000/groups
HTTP/1.0 200 OK
Content-Length: 488
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:24:50 GMT
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 19:21:41 GMT",
            "_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
            "_id": "56b79945cf7b35255aa5a1e7",
            "_links": {
                "self": {
                    "href": "groups/56b79945cf7b35255aa5a1e7",
                    "title": "Group"
                }
            },
            "_updated": "Sun, 07 Feb 2016 19:21:41 GMT",
            "title": "Friends",
            "users": [
                "56b78e41cf7b35255aa5a1e6"
            ]
        }
    ],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "groups",
            "title": "groups"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 1
    }
}


Прабабушка оказалась бы права. На этом можно было бы закончить, но мы поступим иначе.

Получим группу с пользователями, которые в неё входят, в развёрнутом виде:

$ http http://0.0.0.0:5000/groups/56b79945cf7b35255aa5a1e7/\?embedded='{"users":1}'
HTTP/1.0 200 OK
Content-Length: 646
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:38:27 GMT
ETag: c6fc02a0bd4bae92a1310be0748ff8bc971ff209
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 19:21:41 GMT",
    "_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
    "_id": "56b79945cf7b35255aa5a1e7",
    "_links": {
        "collection": {
            "href": "groups",
            "title": "groups"
        },
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "groups/56b79945cf7b35255aa5a1e7",
            "title": "Group"
        }
    },
    "_updated": "Sun, 07 Feb 2016 19:21:41 GMT",
    "title": "Friends",
    "users": [
        {
            "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
            "_etag": "0138d193174528c205827ba9af25b7b8fb93940e",
            "_id": "56b78e41cf7b35255aa5a1e6",
            "_updated": "Sun, 07 Feb 2016 18:46:56 GMT",
            "active": true,
            "firstname": "Jane",
            "lastname": "Doe",
            "username": "janedoe"
        }
    ]
}


Попросим тоже самое в XML:

$ http http://0.0.0.0:5000/groups/56b79945cf7b35255aa5a1e7/\?embedded='{"users":1}' "Accept":"application/xml"
HTTP/1.0 200 OK
Content-Length: 690
Content-Type: application/xml; charset=utf-8
Date: Sun, 07 Feb 2016 19:43:36 GMT
ETag: c6fc02a0bd4bae92a1310be0748ff8bc971ff209
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0


    
    
    <_created>Sun, 07 Feb 2016 19:21:41 GMT
    <_etag>c6fc02a0bd4bae92a1310be0748ff8bc971ff209
    <_id>56b79945cf7b35255aa5a1e7
    <_updated>Sun, 07 Feb 2016 19:21:41 GMT
    Friends
    
        <_created>Sun, 07 Feb 2016 18:34:41 GMT
        <_etag>0138d193174528c205827ba9af25b7b8fb93940e
        <_id>56b78e41cf7b35255aa5a1e6
        <_updated>Sun, 07 Feb 2016 18:46:56 GMT
        True
        Jane
        Doe
        janedoe
    



Попробуем изменить только имя нашего пользователя (без полной перезаписи). Для этого воспользуемся HTTP методом PATCH:

$ http patch http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 firstname=John "If-Match":"0138d193174528c205827ba9af25b7b8fb93940e"
HTTP/1.0 200 OK
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:46:48 GMT
ETag: 86f3495cf1d6edf301e25563099844bd816c5a3c
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
    "_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
    "_id": "56b78e41cf7b35255aa5a1e6",
    "_links": {
        "self": {
            "href": "users/56b78e41cf7b35255aa5a1e6",
            "title": "User"
        }
    },
    "_status": "OK",
    "_updated": "Sun, 07 Feb 2016 19:46:47 GMT"
}


Что сказала бы бабуля?

» http http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6
HTTP/1.0 200 OK
Content-Length: 431
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:50:06 GMT
ETag: 86f3495cf1d6edf301e25563099844bd816c5a3c
Last-Modified: Sun, 07 Feb 2016 19:46:47 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
    "_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
    "_id": "56b78e41cf7b35255aa5a1e6",
    "_links": {
        "collection": {
            "href": "users",
            "title": "users"
        },
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users/56b78e41cf7b35255aa5a1e6",
            "title": "User"
        }
    },
    "_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
    "active": true,
    "firstname": "John",
    "lastname": "Doe",
    "username": "janedoe"
}


«Усё добра». Мне так понравилось, что я бы создавал пользователей пачками:

$ echo '[{"username": "userone", "firstname": "First", "lastname":"Last"},{"username":"usertwo", "firstname":"First", "lastname":"Last"}]' | http http://0.0.0.0:5000/users
HTTP/1.0 201 CREATED
Content-Length: 585
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:01:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 20:01:33 GMT",
            "_etag": "6f397b570ef12769d372c902fa6149bb7e9eaf89",
            "_id": "56b7a29dcf7b35255aa5a1e8",
            "_links": {
                "self": {
                    "href": "users/56b7a29dcf7b35255aa5a1e8",
                    "title": "User"
                }
            },
            "_status": "OK",
            "_updated": "Sun, 07 Feb 2016 20:01:33 GMT"
        },
        {
            "_created": "Sun, 07 Feb 2016 20:01:33 GMT",
            "_etag": "378f30b37724139c213a85079185226ab2b209f3",
            "_id": "56b7a29dcf7b35255aa5a1e9",
            "_links": {
                "self": {
                    "href": "users/56b7a29dcf7b35255aa5a1e9",
                    "title": "User"
                }
            },
            "_status": "OK",
            "_updated": "Sun, 07 Feb 2016 20:01:33 GMT"
        }
    ],
    "_status": "OK"
}


Найдём пользователя »John Doe» по его имени:

$ http http://0.0.0.0:5000/users\?where='{"firstname":"John"}'
HTTP/1.0 200 OK
Content-Length: 535
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:05:28 GMT
Last-Modified: Sun, 07 Feb 2016 19:46:47 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
            "_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
            "_id": "56b78e41cf7b35255aa5a1e6",
            "_links": {
                "self": {
                    "href": "users/56b78e41cf7b35255aa5a1e6",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
            "active": true,
            "firstname": "John",
            "lastname": "Doe",
            "username": "janedoe"
        }
    ],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users?where={\"firstname\":\"John\"}",
            "title": "users"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 1
    }
}


Отсортируем пользователей по их логину в обратном порядке:

$ http http://0.0.0.0:5000/users\?sort\=-username
HTTP/1.0 200 OK
Content-Length: 1203
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:08:08 GMT
Last-Modified: Sun, 07 Feb 2016 20:01:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 3

{
    "_items": [
        {
            "_created": "Sun, 07 Feb 2016 20:01:33 GMT",
            "_etag": "378f30b37724139c213a85079185226ab2b209f3",
            "_id": "56b7a29dcf7b35255aa5a1e9",
            "_links": {
                "self": {
                    "href": "users/56b7a29dcf7b35255aa5a1e9",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 20:01:33 GMT",
            "active": true,
            "firstname": "First",
            "lastname": "Last",
            "username": "usertwo"
        },
        {
            "_created": "Sun, 07 Feb 2016 20:01:33 GMT",
            "_etag": "6f397b570ef12769d372c902fa6149bb7e9eaf89",
            "_id": "56b7a29dcf7b35255aa5a1e8",
            "_links": {
                "self": {
                    "href": "users/56b7a29dcf7b35255aa5a1e8",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 20:01:33 GMT",
            "active": true,
            "firstname": "First",
            "lastname": "Last",
            "username": "userone"
        },
        {
            "_created": "Sun, 07 Feb 2016 18:34:41 GMT",
            "_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
            "_id": "56b78e41cf7b35255aa5a1e6",
            "_links": {
                "self": {
                    "href": "users/56b78e41cf7b35255aa5a1e6",
                    "title": "User"
                }
            },
            "_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
            "active": true,
            "firstname": "John",
            "lastname": "Doe",
            "username": "janedoe"
        }
    ],
    "_links": {
        "parent": {
            "href": "/",
            "title": "home"
        },
        "self": {
            "href": "users?sort=-username",
            "title": "users"
        }
    },
    "_meta": {
        "max_results": 25,
        "page": 1,
        "total": 3
    }
}


Ну и наконец-то удалим всех пользователей:

$ http delete http://0.0.0.0:5000/users
HTTP/1.0 204 NO CONTENT
Content-Length: 0
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:10:06 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0


Я бы не стал включать данную возможность в production. Указывается это в параметре RESOURCE_METHODS (стоит убрать из списка DELETE):

RESOURCE_METHODS = ['GET', 'POST', 'DELETE']


Заключение


Мы рассмотрели не все возможности Eve, но даже c учётом этого — получили законченный вариант REST API.

В данной статье я хотел обратить внимание на то, что настоящий RESTful сервис это гораздо больше, чем пара модулей для Node.js и в большинстве случаев нет необходимости разрабатывать такие вещи с нуля, а тем более писать статьи о том, как это сделать за один час. Достаточно взглянуть на историю развития проекта, что бы лишний раз убедиться в том, что пары часов/дней/недель недостаточно даже для хорошей команды.

Nicola Iarocci, автор Eve, создал прекрасный инструмент для быстрого развёртывания REST API, уделил этому достаточно внимания и сохранил простоту использования.

Ссылки


Подписывайтесь на меня в Twitter, я рассказываю о работе в стартапе, своих ошибках и правильных решениях, о python и всём, что касается веб-разработки.

P.S. Я ищу разработчиков в стартап, подробности у меня в профиле.

© Habrahabr.ru