Архитектура RESTful API на Pyramid — приемы проектирования...
-
Upload
it-people -
Category
Technology
-
view
632 -
download
2
Transcript of Архитектура RESTful API на Pyramid — приемы проектирования...
Архитектура RESTful API на Pyramid
приёмы проектирования
Типичный проект
API
Mobile AppsAdmin PanelWeb Site
BackgroundWorkers
Data Sources
Типичный проект
API
Mobile AppsAdmin PanelWeb Site
BackgroundWorkers
Data Sources
Слои
Данные
Бизнес логика
Представление
Слои
Данные
Бизнес логика
Представление
Model
Controller
View
Слои
Данные
Бизнес логика
Представление
Model
Controller
View
Store
Resource
View
Resource
Resource
―Унифицированный интерфейс к данным
Resource
―Унифицированный интерфейс к данным
―Унифицированные исключения
Resource
―Унифицированный интерфейс к данным
―Унифицированные исключения
―Независимость от представления
Traversal
Traversal
child = parent['child']assert child.__name__ == 'child'assert child.__parent__ is parent
Traversal
child = parent['child']assert child.__name__ == 'child'assert child.__parent__ is parent
GET /users/1/
root['users']['1']
Traversal
child = parent['child']assert child.__name__ == 'child'assert child.__parent__ is parent
GET /users/1/ -> 404 Not Found
root['users']['1'] -> KeyError
Traversal
class Root(object): """ API root resource """
def __getitem__(self, name): if name == 'users': return Users(name, self) raise KeyError(name)
class Users(object): """ Collection of users """
def __init__(self, name, parent): self.__name__ = name self.__parent__ = parent
TraversalKit
from traversalkit import Resource, DEC_ID
class Root(Resource): # / """ API root resource """
@Root.mount('users') # /users/class Users(Resource): """ Collection of users """
@Users.mount_set(DEC_ID) # /users/{^[0-9]+$}/class User(Resource): """ Particular user """
Коллекции
class Articles(Resource):
def get_list(self, offset=0, limit=10): pass
def get_map(self, ids): pass
def create(self, title, body): pass
Элементы
@Articles.mount_set(DEC_ID)class Article(Resource):
def update(self, title, body): pass
def delete(self): pass
Загрузка элемента
from ..store import db
@Articles.mount_set(DEC_ID)class Article(Resource):
__not_exist__ = db.NoResultFound
def on_init(self, data=None): if data is None: # Created via __getitem__ session = db.Session() data = session \ .query(db.Article) \ .filter(db.Article.id == int(self.__name__)) \ .one() self.data = data
Загрузка списка
from ..store import db
class Articles(Resource):
def get_list(self, offset=0, limit=50): session = db.Session() query = session \ .query(db.Article) \ .limit(limit) \ .offset(offset) \ .order_by(db.Article.created_at)
return [ self.get(str(item.id), item) for item in query.all() ]
Иерархия
@Root.mount('articles') # All articlesclass Articles(Resource): """ Collection of articles """
@Article.mount('comments') # Article's commentsclass Comments(Resource): """ Collection of comments """
Иерархия
@Root.mount('articles') # All [email protected]('articles') # User's articlesclass Articles(Resource): """ Collection of articles """
@Article.mount('comments') # Article's [email protected]('comments') # User's commentsclass Comments(Resource): """ Collection of comments """
ACL
from pyramid.security import Allow, Everyone, Authenticated
class Articles(Resource):
__acl__ = [ (Allow, Everyone, 'read'), (Allow, Authenticated, 'create'), (Allow, 'group:moderators', ('update', 'delete')), ]
@Articles.mount_set(DEC_ID)class Article(Resource):
def __acl__(self): user_id = str(self.data.user_id) return [(Allow, user_id, ('update', 'delete'))]
ACL
from pyramid.security import \ Allow, Everyone, Authenticated, DENY_ALL
@Article.mount('comments')class Comments(Resource):
__acl__ = [ (Allow, Everyone, 'read'), (Allow, Authenticated, 'create'), (Allow, 'group:moderators', ('update', 'delete')), DENY_ALL, # Stop inheritance ]
Валидация
from .. import validation
class Articles(Resource, validation.Mixin):
@validation.set_schema(validation.schema.Article) def create(self, title, body): pass
def create_article(context, request): article = context.validated.create(request.json)
Views
from pyramid.view import view_config
from ..resources import Articles
@view_config(context=Articles, request_method='GET', permission='read')def list_articles(context, request): articles = context.validated.get_list(request.GET) return { 'articles': articles, }
Views
from pyramid.view import view_config
from ..resources import Articles
@view_config(context=Articles, request_method='GET', permission='read')def list_articles(context, request): articles = context.validated.get_list(request.GET) users = request.root['users'].get_map( set(a.data.user_id for a in articles) ) return { 'articles': articles, 'users': users, }
Сериализация
class Article(Resource):
def __json__(self, *_): return { 'id': self.data.id, 'title': self.data.title, 'body': self.data.body, }
Обработка ошибок
from pyramid.view import view_configfrom pyramid.security import NO_PERMISSION_REQUIRED
from ..resources import ResourceExceptionfrom ..validation import Invalid
@view_config(context=ResourceException, permission=NO_PERMISSION_REQUIRED)def resource_exception(context, request): request.response.status_int = 400 return {'error': context}
@view_config(context=Invalid, permission=NO_PERMISSION_REQUIRED)def validation_exception(context, request): request.response.status_int = 422 return {'error': context}
Обработка ошибок
400422
Ошибка бизнес логикиНевалидные данные
Обработка ошибок
400422403404
Ошибка бизнес логикиНевалидные данныеНет прав доступаРесурса не существует
Обработка ошибок
400422403404405
Ошибка бизнес логикиНевалидные данныеНет прав доступаРесурса не существуетОшибка контекста
Обработка ошибок
400422403404405500
Ошибка бизнес логикиНевалидные данныеНет прав доступаРесурса не существуетОшибка контекстаВсё сломалось
Тесты
ResourceViewWSGI
Тесты
ResourceViewWSGI
Бизнес логика, иерархия
Тесты
ResourceViewWSGI
Бизнес логика, иерархияВалидация, связанные данные, сессия
Тесты
ResourceViewWSGI
Бизнес логика, иерархияВалидация, связанные данные, сессияИнтеграция, ACL
Вопросы?