См.также
REST API
подразумевает под собой простые правила:
GET
возвращается описание этого
ресурсаPOST
добавляет новый ресурсPUT
изменяет ресурсDELETE
удаляет ресурсЭти правила предоставляют простой CRUD
интерфейс для других приложений,
взаимодействие с которым происходит через протокол HTTP
.
Соответствие CRUD
операций и HTTP
методов:
POST
GET
PUT
DELETE
REST API
интерфейс очень удобен для межпрограммного взаимодействия,
например мобильное приложение может выступать в роли клиента, который
манипулирует данными посредством REST
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | from wsgiref.simple_server import make_server
from pyramid.view import view_config, view_defaults
from pyramid.config import Configurator
@view_defaults(
route_name='rest_people',
renderer='json'
)
class RESTViewPeople(object):
def __init__(self, request):
self.request = request
@view_config(request_method='GET')
def get(self):
return {
'id': self.request.matchdict['id'],
'method': self.request.method,
'get': dict(self.request.GET)
}
@view_config(request_method='POST')
def post(self):
return {
'id': self.request.matchdict['id'],
'method': self.request.method,
'post': dict(self.request.POST)
}
@view_config(request_method='DELETE')
def delete(self):
return {'status': 'success'}
if __name__ == '__main__':
config = Configurator()
config.add_route('rest_people', '/api/v1/people/{id:\d+}')
config.add_view(RESTViewPeople, route_name='rest_people')
config.scan('.')
# make wsgi app
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever() |
Пример выше добавляет View с тремя методами, каждый из которых вызывается при
соответствующем GET
, POST
, DELETE
запросе.
Ресурсом здесь является конкретный человек, получить которого можно по URL
http://localhost:8080/api/v1/people/123
Результатом запроса будет:
{"get": {}, "id": "123", "method": "GET"}
Для отправки POST
запроса воспользуемся консольной утилитой curl:
$ curl -X POST -d 'param1=value1¶m2=value2' http://localhost:8080/api/v1/people/1
Результат запроса:
{"id": "1", "post": {"param1": "value1", "param2": "value2"}, "method": "POST"}
DELETE
запрос выполняется по аналогии:
$ curl -X DELETE http://localhost:8080/api/v1/people/1
Результат запроса:
{"status": "success"}
См.также
Метод URL диспетчеризации Traversal
В предыдущем примере показан только один ресурс - конкретный человек и в принципе все выглядит неплохо, пока не появится другой смежный ресурс, например список всех людей по адресу http://localhost:8080/api/v1/people
В этом случае, придется добавлять новый путь (rout), привязывать его к представлению (View) и самое неприятное менять само представление, или еще хуже писать новое. Таким образом с увеличением ресурсов, сложность REST API растет не пропорционально и в какой то момент код становится не читаемым из-за больших размеров и постоянно меняющейся логики во View.
Выход из данной ситуации - отделить ресурсы от представлений, тем самым вынести часть логики и сделать представления более универсальными.
Ресурсы могут выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 | class PeopleResource(object):
def __getitem__(self, people_id):
if str(people_id).isdigit():
return PersonResource(people_id)
def __json__(self, request):
return {
'params': request.matchdict,
'method': request.method,
} |
PeopleResource
представляет список всех людей и будет доступен по
адресу http://localhost:8080/api/v1/people.
PeopleResource
имеет метод __getitem__
, что делает его похожим на
словарь. При обращении к объекту ресурса как к словарю, он вызовет
эту функцию и передаст ключ в параметр people_id
, например:
foo = PeopleResource()
bar = foo[123] # Вернет объект PersonResource(123)
Метод __json__
определяет каким образом преобразовывать ресурс в json.
PersonResource
представляет конкретного человека и будет доступен по
адресу http://localhost:8080/api/v1/people/{id}. Здесь отличительной
особенностью является то, что метод __json__
наследует часть словаря из
класса PeopleResource
, при помощи конструкции super
:
1 2 3 4 5 6 7 8 9 10 | class PersonResource(PeopleResource):
def __init__(self, people_id):
self.id = people_id
def __json__(self, request):
return {
'id': self.id,
**super().__json__(request)
} |
Перепишем View таким образом, чтобы она возвращала только ресурс, а так-как
ресурс уже содержит в себе информацию как отдавать json, то это представление
будет универсальным как для PeopleResource
, так и для PersonResource
и возможно подойдет другим ресурсам которые мы будем писать в будущем.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @view_defaults(
route_name='rest_api',
renderer='json',
context=PeopleResource
)
class RESTViewPeople(object):
def __init__(self, context, request):
self.context = context
self.request = request
@view_config(request_method='GET')
def get(self):
return self.context
@view_config(request_method='POST')
def post(self):
return self.context
@view_config(request_method='DELETE')
def delete(self):
return {'status': 'success'} |
Рендерер json
по умолчанию ищет метод __json__
и если он есть то
возвращает его результат вызова.
Путь, в нашем случае, будет один, так-как вся структура вынесена в ресурсы
(метод __getitem__
).
config.add_route('rest_api', '/api/v1/*traverse', factory=rest_factory)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | from wsgiref.simple_server import make_server
from pyramid.view import view_config, view_defaults
from pyramid.config import Configurator
class PeopleResource(object):
def __getitem__(self, people_id):
if str(people_id).isdigit():
return PersonResource(people_id)
def __json__(self, request):
return {
'params': request.matchdict,
'method': request.method,
}
class PersonResource(PeopleResource):
def __init__(self, people_id):
self.id = people_id
def __json__(self, request):
return {
'id': self.id,
**super().__json__(request)
}
class AnimalsResource(object):
pass
@view_defaults(
route_name='rest_api',
renderer='json',
context=PeopleResource
)
class RESTViewPeople(object):
def __init__(self, context, request):
self.context = context
self.request = request
@view_config(request_method='GET')
def get(self):
return self.context
@view_config(request_method='POST')
def post(self):
return self.context
@view_config(request_method='DELETE')
def delete(self):
return {'status': 'success'}
def rest_factory(request):
return {
'people': PeopleResource(),
'animals': AnimalsResource(),
}
if __name__ == '__main__':
config = Configurator()
config.add_route('rest_api', '/api/v1/*traverse', factory=rest_factory)
config.add_view(RESTViewPeople, route_name='rest_api')
config.scan('.')
# make wsgi app
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever() |