REST API обычно меняет, создает и удаляет записи, которые хранятся в БД. Что бы не писать много кода на SQLAlchemy я использую заготовленные функции из sacrud
Итак поехали, представим сервис REST API для платежных карт с моделью типа:
class Card(Base): __tablename__ = 'card' id = Column(Integer, primary_key=True) number = Column(BigInteger, nullable=False, unique=True) uid = Column(BYTEA, nullable=False, unique=True) balance = Column(Numeric(10, 2), nullable=False, default=0) preference = Column(GUID()) def __json__(self): return {'id': self.id, 'number': self.number, 'uid': str(self.uid), 'balance': str(self.balance) }
Создадим отдельную папку в pyramid проекте с названием rest, где будут храниться функции api, и пропишем это в основном конфиге.
# REST API config.include("cornice") config.include("myapp.rest", route_prefix="/api")
В pyramid'е есть такая штука как config.scan() она смотрит все файлы в проекте с расширением *.py (от текущей директории) и ищет вьюхи обернутые декоратором @view_config. Я предпочитаю включать в основной конфиг папочки через инклуд как в примере выше, а локально в каждой папке уже вызывать config.scan()
Структура папки rest
rest/ ├── card.py ├── __init__.py └── validators.py
Файл __init__.py
def includeme(config): config.scan()
В card.py сама реализация REST API
GET
import json from cornice import Service from myapp.models import DBSession from myapp.models import Card from myapp.rest.validators import _400, _404, valid_hex from sacrud.action import CRUD from sqlalchemy.exc import DataError from sqlalchemy.orm.exc import NoResultFound card_api = Service(name='card', path='/card/{UID}', description="REST API for card") @card_api.get(validators=valid_hex) def get_card(request): """``GET``: возвращает данные о карте. .. code-block:: bash $ curl -D - http://0.0.0.0:6543/api/card/07a8e29d HTTP/1.1 200 OK Content-Length: 69 Content-Type: application/json; charset=UTF-8 Date: Sat, 02 Aug 2014 11:22:52 GMT Server: waitress {"uid": "07a8e29d", "balance": 507.05, "id": 3, "number": 8224548674} """ key = request.validated['UID'] card = DBSession.query(Card) if key: try: card = card.filter_by(uid=key).one() except NoResultFound: raise _404() except DataError: raise _400() return card.__json__()
Декоратор card_api превращает функцию get_card во вьюху, ожидающею HTTP метод GET, и config.scan() её автоматически подхватит. Внутри можно реализовывать все что угодно. Про метод validated ниже.
POST
@card_api.post(validators=valid_hex) def set_card(request): """``POST``: передает параметры для редактирования карты. .. code-block:: bash $ curl -H 'Accept: application/json'\\ -H 'Content-Type: application/json'\\ http://0.0.0.0:6543/api/card/07a8e29d\\ -d '{"number": "8224548674", "balance": "507.05"}' {"uid": "07a8e29d", "balance": 507.05, "id": 3, "number": 8224548674} Если карты нету, то создается новая .. code-block:: bash $ curl -H 'Accept: application/json'\\ -H 'Content-Type: application/json'\\ http://0.0.0.0:6543/api/card/550e8400\\ -d '{"balance": "100.11", "preference": "956bfc40"}' {"uid": "550e8400", "balance": 100.11, "id": 7, "number": 91328937986} """ key = request.validated['UID'] data = {'request': json.loads(request.body)} card = DBSession.query(Card).filter_by(uid=key).first() if card: data['pk'] = {'id': card.id} else: data['request']['uid'] = key try: CRUD(DBSession, Card, **data).add() except Exception as e: raise _400(msg=str(e.message)) card = DBSession.query(Card).filter_by(uid=key).one() return card.__json__()
Метод POST меняет параметры карты или создает новую если такой нету. Карта создается при помощи мега модуля sacrud. Подробнее о создании записей через sacrud здесь http://sacrud.readthedocs.org/en/latest/plain_usage.html#create-action
DELETE
@card_api.delete(validators=valid_hex) def del_card(request): """``DELETE``: удаляет карту по UID .. code-block:: bash $ curl -H 'Accept: application/json'\\ -H 'Content-Type: application/json'\\ http://0.0.0.0:6543/api/card/550e8400 -X DELETE {"Goodbye": "550e8400"} """ key = request.validated['UID'] card = DBSession.query(Card).filter_by(uid=key).first() if not card: raise _404() try: CRUD(DBSession, Card, pk={'id': card.id}).delete() except DataError as e: raise _400(msg=str(e.message)) return {'Goodbye': key}
Удаление происходит при вызове HTTP метода DELETE. sacrud опять же несколько упрощает эту операцию http://sacrud.readthedocs.org/en/latest/plain_usage.html#delete-action
В файле validators.py хранятся всякие исключения и сами проверки. Если в декораторе card_api указан валидатор, то валидные данные нужно будет выбирать не через request.matchdict а через request.validated
Пример файла validators.py
import json import string from webob import exc, Response class _404(exc.HTTPError): def __init__(self, msg='Not Found'): body = {'status': 404, 'message': msg} Response.__init__(self, json.dumps(body)) self.status = 404 self.content_type = 'application/json' class _400(exc.HTTPError): def __init__(self, msg='Bad Request'): body = {'status': 400, 'message': msg} Response.__init__(self, json.dumps(body)) self.status = 400 self.content_type = 'application/json' def valid_hex(request): key = request.matchdict['UID'] if not all(c in string.hexdigits for c in key): raise _400(msg="Not valid UID '%s'" % key) request.validated['UID'] = str(key)
Таким образом можно довольно просто создать API для вашего проекта на пирамиде. Cornice делает много за вас, создает вьюхи, пути, валидацию, может генерить автоматически Sphinx документацию, а SACRUD упрощает работу с БД.
Что насчет валидации входного json в POST? А colander не пробовали использовать?
ОтветитьУдалитьВалидатор по сути можно запилить как угодно подробнее здесь http://cornice.readthedocs.org/en/latest/validation.html , Colander с cornice не пробовал использовать вместе, но люди вроде про это пишут http://makina-corpus.com/blog/metier/2013/multi-format-restful-api-with-cornice
Удалить