aboutsummaryrefslogtreecommitdiff
path: root/pyaggr3g470r/views/api
diff options
context:
space:
mode:
Diffstat (limited to 'pyaggr3g470r/views/api')
-rw-r--r--pyaggr3g470r/views/api/article.py107
-rw-r--r--pyaggr3g470r/views/api/common.py122
-rw-r--r--pyaggr3g470r/views/api/feed.py120
3 files changed, 161 insertions, 188 deletions
diff --git a/pyaggr3g470r/views/api/article.py b/pyaggr3g470r/views/api/article.py
index 3642cda9..ebda6247 100644
--- a/pyaggr3g470r/views/api/article.py
+++ b/pyaggr3g470r/views/api/article.py
@@ -1,97 +1,36 @@
-import re
-import dateutil.parser
+from flask import g
-from flask import request, g
-from flask.ext.restful import Resource, reqparse
-
-from pyaggr3g470r.models import Article, Feed
from pyaggr3g470r.controllers import ArticleController
-from pyaggr3g470r.views.api.common import authenticate, to_response, \
- PyAggResource
-
+from pyaggr3g470r.views.api.common import PyAggResourceNew, \
+ PyAggResourceExisting, \
+ PyAggResourceMulti
-class ArticleListAPI(Resource):
- """
- Defines a RESTful API for Article elements.
- """
- method_decorators = [authenticate, to_response]
- def __init__(self):
- self.reqparse = reqparse.RequestParser()
- self.reqparse.add_argument('title', type=unicode, location='json')
- self.reqparse.add_argument('content', type=unicode, location='json')
- self.reqparse.add_argument('link', type=unicode, location='json')
- self.reqparse.add_argument('date', type=str, location='json')
- self.reqparse.add_argument('feed_id', type=int, location='json')
- super(ArticleListAPI, self).__init__()
+ARTICLE_ATTRS = {'title': {'type': str},
+ 'content': {'type': str},
+ 'link': {'type': str},
+ 'date': {'type': str},
+ 'feed_id': {'type': int},
+ 'like': {'type': bool},
+ 'readed': {'type': bool}}
- def get(self):
- """
- Returns a list of articles.
- """
- feeds = {feed.id: feed.title for feed in g.user.feeds if feed.enabled}
- articles = Article.query.filter(Article.feed_id.in_(feeds.keys()),
- Article.user_id == g.user.id)
- filter_ = request.args.get('filter_', 'unread')
- feed_id = int(request.args.get('feed', 0))
- limit = request.args.get('limit', 1000)
- if filter_ != 'all':
- articles = articles.filter(Article.readed == (filter_ == 'read'))
- if feed_id:
- articles = articles.filter(Article.feed_id == feed_id)
- articles = articles.order_by(Article.date.desc())
- if limit != 'all':
- limit = int(limit)
- articles = articles.limit(limit)
+class ArticleNewAPI(PyAggResourceNew):
+ controller_cls = ArticleController
+ attrs = ARTICLE_ATTRS
- return {'result': [article.dump() for article in articles]}
- def post(self):
- """
- POST method - Create a new article.
- """
- args = self.reqparse.parse_args()
- article_dict = {}
- for k, v in args.iteritems():
- if v != None:
- article_dict[k] = v
- else:
- return {"message": "Missing argument: %s." % (k,)}, 400
- article_date = None
- try:
- article_date = dateutil.parser.parse(article_dict["date"], dayfirst=True)
- except:
- try: # trying to clean date field from letters
- article_date = dateutil.parser.parse(re.sub('[A-z]', '', article_dict["date"], dayfirst=True))
- except:
- return {"message": "Bad format for the date."}, 400
- article = Article(link=article_dict["link"], title=article_dict["title"],
- content=article_dict["content"], readed=False, like=False,
- date=article_date, user_id=g.user.id,
- feed_id=article_dict["feed_id"])
- feed = Feed.query.filter(Feed.id == article_dict["feed_id"], Feed.user_id == g.user.id).first()
- feed.articles.append(article)
- try:
- g.db.session.commit()
- return {"message": "ok"}, 201
- except:
- return {"message": "Impossible to create the article."}, 500
+class ArticleAPI(PyAggResourceExisting):
+ controller_cls = ArticleController
+ attrs = ARTICLE_ATTRS
-class ArticleAPI(PyAggResource):
- "Defines a RESTful API for Article elements."
- method_decorators = [authenticate, to_response]
+class ArticlesAPI(PyAggResourceMulti):
controller_cls = ArticleController
- editable_attrs = ['like', 'readed']
-
- def __init__(self):
- self.reqparse = reqparse.RequestParser()
- self.reqparse.add_argument('like', type=bool, location='json')
- self.reqparse.add_argument('readed', type=bool, location= 'json')
- super(ArticleAPI, self).__init__()
+ attrs = ARTICLE_ATTRS
-g.api.add_resource(ArticleListAPI, '/articles', endpoint='articles.json')
-g.api.add_resource(ArticleAPI, '/articles/<int:obj_id>',
- endpoint='article.json')
+g.api.add_resource(ArticleNewAPI, '/article', endpoint='article_new.json')
+g.api.add_resource(ArticleAPI, '/article/<int:obj_id>',
+ endpoint='article.json')
+g.api.add_resource(ArticlesAPI, '/articles', endpoint='articles.json')
diff --git a/pyaggr3g470r/views/api/common.py b/pyaggr3g470r/views/api/common.py
index edf560da..c0759c03 100644
--- a/pyaggr3g470r/views/api/common.py
+++ b/pyaggr3g470r/views/api/common.py
@@ -1,6 +1,8 @@
+import json
+import types
from functools import wraps
-from flask import request, g, session, Response, jsonify
-from flask.ext.restful import Resource
+from flask import request, g, session, Response
+from flask.ext.restful import Resource, reqparse
from pyaggr3g470r.models import User
from pyaggr3g470r.lib.exceptions import PyAggError
@@ -35,42 +37,122 @@ def authenticate(func):
return wrapper
+def default_handler(obj):
+ """JSON handler for default query formatting"""
+ if hasattr(obj, 'isoformat'):
+ return obj.isoformat()
+ if hasattr(obj, 'dump'):
+ return obj.dump()
+ if isinstance(obj, (set, frozenset, types.GeneratorType)):
+ return list(obj)
+ raise TypeError("Object of type %s with value of %r "
+ "is not JSON serializable" % (type(obj), obj))
+
+
def to_response(func):
def wrapper(*args, **kwargs):
try:
- res = func(*args, **kwargs)
- except PyAggError, error:
- response = jsonify(**error.message)
+ result = func(*args, **kwargs)
+ except PyAggError as error:
+ response = Response(json.dumps(result[0], default=default_handler))
response.status_code = error.status_code
return response
- if isinstance(res, tuple):
- response = jsonify(**res[0])
- if len(res) > 1:
- response.status_code = res[1]
- return response
- return res
+ status_code = 200
+ if isinstance(result, tuple):
+ result, status_code = result
+ response = Response(json.dumps(result, default=default_handler),
+ status=status_code)
+ return response
return wrapper
-class PyAggResource(Resource):
+class PyAggAbstractResource(Resource):
method_decorators = [authenticate, to_response]
- controller_cls = None
- editable_attrs = []
def __init__(self, *args, **kwargs):
self.controller = self.controller_cls(g.user.id)
- super(PyAggResource, self).__init__(*args, **kwargs)
+ super(PyAggAbstractResource, self).__init__(*args, **kwargs)
+
+ def reqparse_args(self, strict=False, default=True):
+ """
+ strict: bool
+ if True will throw 400 error if args are defined and not in request
+ default: bool
+ if True, won't return defaults
+
+ """
+ parser = reqparse.RequestParser()
+ for attr_name, attrs in self.attrs.items():
+ if not default and attr_name not in request.args:
+ continue
+ parser.add_argument(attr_name, location='json', **attrs)
+ return parser.parse_args(strict=strict)
+
+
+class PyAggResourceNew(PyAggAbstractResource):
+
+ def post(self):
+ return self.controller.create(**self.reqparse_args()), 201
+
+
+class PyAggResourceExisting(PyAggAbstractResource):
def get(self, obj_id=None):
- return {'result': [self.controller.get(id=obj_id).dump()]}
+ return self.controller.get(id=obj_id).dump()
def put(self, obj_id=None):
- args = self.reqparse.parse_args()
+ args = self.reqparse_args()
new_values = {key: args[key] for key in
- set(args).intersection(self.editable_attrs)}
+ set(args).intersection(self.attrs)}
self.controller.update(obj_id, **new_values)
- return {"message": "ok"}
def delete(self, obj_id=None):
self.controller.delete(obj_id)
- return {"message": "ok"}, 204
+ return None, 204
+
+
+class PyAggResourceMulti(PyAggAbstractResource):
+
+ def get(self):
+ filters = self.reqparse_args(default=False)
+ return [res.dump() for res in self.controller.read(**filters).all()]
+
+ def post(self):
+ status = 201
+ results = []
+ args = [] # FIXME
+ for arg in args:
+ try:
+ results.append(self.controller.create(**arg).id)
+ except Exception as error:
+ status = 206
+ results.append(error)
+ return results, status
+
+ def put(self):
+ status = 200
+ results = []
+ args = {} # FIXME
+ for obj_id, attrs in args.items():
+ try:
+ new_values = {key: args[key] for key in
+ set(attrs).intersection(self.editable_attrs)}
+ self.controller.update(obj_id, **new_values)
+ results.append('ok')
+ except Exception as error:
+ status = 206
+ results.append(error)
+ return results, status
+
+ def delete(self):
+ status = 204
+ results = []
+ obj_ids = [] # FIXME extract some real ids
+ for obj_id in obj_ids:
+ try:
+ self.controller.delete(obj_id)
+ results.append('ok')
+ except Exception as error:
+ status = 206
+ results.append(error)
+ return results, status
diff --git a/pyaggr3g470r/views/api/feed.py b/pyaggr3g470r/views/api/feed.py
index 94a5a433..e6f74cfd 100644
--- a/pyaggr3g470r/views/api/feed.py
+++ b/pyaggr3g470r/views/api/feed.py
@@ -1,92 +1,42 @@
+from datetime import datetime
from flask import g
from flask.ext.restful import Resource, reqparse
-from pyaggr3g470r.controllers.feed import FeedController, \
- DEFAULT_MAX_ERROR, DEFAULT_LIMIT
-from pyaggr3g470r.models import Feed
+from pyaggr3g470r.controllers.feed import FeedController, \
+ DEFAULT_MAX_ERROR, DEFAULT_LIMIT
-from pyaggr3g470r.views.api.common import authenticate, to_response, \
- PyAggResource
+from pyaggr3g470r.views.api.common import PyAggResourceNew, \
+ PyAggResourceExisting, \
+ PyAggResourceMulti
-class FeedListAPI(Resource):
- """
- Defines a RESTful API for Feed elements.
- """
- method_decorators = [authenticate, to_response]
+FEED_ATTRS = {'title': {'type': str},
+ 'description': {'type': str},
+ 'link': {'type': str},
+ 'site_link': {'type': str},
+ 'email_notification': {'type': bool, 'default': False},
+ 'enabled': {'type': bool, 'default': True},
+ 'etag': {'type': str, 'default': None},
+ 'last_modified': {'type': datetime},
+ 'last_error': {'type': datetime},
+ 'error_count': {'type': int, 'default': 0}}
- def __init__(self):
- self.reqparse = reqparse.RequestParser()
- self.reqparse.add_argument('title',
- type=unicode, default="", location='json')
- self.reqparse.add_argument('description',
- type=unicode, default="", location='json')
- self.reqparse.add_argument('link', type=unicode, location='json')
- self.reqparse.add_argument('site_link',
- type=unicode, default="", location='json')
- self.reqparse.add_argument('email_notification',
- type=bool, default=False, location='json')
- self.reqparse.add_argument('enabled',
- type=bool, default=True, location='json')
- super(FeedListAPI, self).__init__()
- def get(self):
- """
- Returns a list of feeds.
- """
- return {'result': [{"id": feed.id,
- "title": feed.title,
- "description": feed.description,
- "link": feed.link,
- "site_link": feed.site_link,
- "email_notification": feed.email_notification,
- "enabled": feed.enabled,
- "created_date": feed.created_date,
- } for feed in g.user.feeds]}
-
- def post(self):
- """
- POST method - Create a new feed.
- """
- args = self.reqparse.parse_args()
- feed_dict = {}
- for k, v in args.iteritems():
- if v != None:
- feed_dict[k] = v
- else:
- return {'message': 'missing argument: %s' % (k,)}, 400
- new_feed = Feed(title=feed_dict["title"],
- description=feed_dict["description"],
- link=feed_dict["link"],
- site_link=feed_dict["site_link"],
- email_notification=feed_dict["email_notification"],
- enabled=feed_dict["enabled"])
- g.user.feeds.append(new_feed)
- try:
- g.db.session.commit()
- return {"message": "ok"}
- except:
- return {'message': 'Impossible to create the feed.'}, 500
-
-
-class FeedAPI(PyAggResource):
- "Defines a RESTful API for Feed elements."
+class FeedNewAPI(PyAggResourceNew):
controller_cls = FeedController
- editable_attrs = ['title', 'description', 'link', 'site_link',
- 'email_notification', 'enabled', 'last_refreshed',
- 'last_error', 'error_count']
+ attrs = FEED_ATTRS
- def __init__(self):
- self.reqparse = reqparse.RequestParser()
- self.reqparse.add_argument('title', type=unicode, location='json')
- self.reqparse.add_argument('description',
- type=unicode, location='json')
- self.reqparse.add_argument('link', type=unicode, location='json')
- self.reqparse.add_argument('site_link', type=unicode, location='json')
- self.reqparse.add_argument('email_notification',
- type=bool, location='json')
- self.reqparse.add_argument('enabled', type=bool ,location='json')
- super(FeedAPI, self).__init__()
+
+class FeedAPI(PyAggResourceExisting):
+ pass
+ controller_cls = FeedController
+ attrs = FEED_ATTRS
+
+
+class FeedsAPI(PyAggResourceMulti):
+ pass
+ controller_cls = FeedController
+ attrs = FEED_ATTRS
class FetchableFeedAPI(Resource):
@@ -102,10 +52,12 @@ class FetchableFeedAPI(Resource):
def get(self):
args = self.reqparse.parse_args()
controller = FeedController(g.user.id)
- return {'result': [feed.dump() for feed in controller.list_fetchable(
- max_error=args['max_error'], limit=args['limit'])]}
+ return [feed for feed in controller.list_fetchable(
+ max_error=args['max_error'], limit=args['limit'])]
-g.api.add_resource(FeedListAPI, '/feeds', endpoint='feeds.json')
-g.api.add_resource(FeedAPI, '/feeds/<int:obj_id>', endpoint='feed.json')
-g.api.add_resource(FetchableFeedAPI, '/feeds/fetchable', endpoint='fetchable_feed.json')
+g.api.add_resource(FeedNewAPI, '/feed', endpoint='feed_new.json')
+g.api.add_resource(FeedAPI, '/feed/<int:obj_id>', endpoint='feed.json')
+g.api.add_resource(FeedsAPI, '/feeds', endpoint='feeds.json')
+g.api.add_resource(FetchableFeedAPI, '/feeds/fetchable',
+ endpoint='fetchable_feed.json')
bgstack15