diff options
author | Cédric Bonhomme <cedric@cedricbonhomme.org> | 2015-11-25 22:45:43 +0100 |
---|---|---|
committer | Cédric Bonhomme <cedric@cedricbonhomme.org> | 2015-11-25 22:45:43 +0100 |
commit | b2618e9404b84cc62d4becb02436233a0d53b375 (patch) | |
tree | a31f2dc76d23967fa0243374cf475923a4b7e451 /pyaggr3g470r/views/api | |
parent | Updated default platform URL (for Heroku...). (diff) | |
download | newspipe-b2618e9404b84cc62d4becb02436233a0d53b375.tar.gz newspipe-b2618e9404b84cc62d4becb02436233a0d53b375.tar.bz2 newspipe-b2618e9404b84cc62d4becb02436233a0d53b375.zip |
Rfactorization. Just a start...
Diffstat (limited to 'pyaggr3g470r/views/api')
-rw-r--r-- | pyaggr3g470r/views/api/__init__.py | 31 | ||||
-rw-r--r-- | pyaggr3g470r/views/api/article.py | 63 | ||||
-rw-r--r-- | pyaggr3g470r/views/api/common.py | 245 | ||||
-rw-r--r-- | pyaggr3g470r/views/api/feed.py | 69 |
4 files changed, 0 insertions, 408 deletions
diff --git a/pyaggr3g470r/views/api/__init__.py b/pyaggr3g470r/views/api/__init__.py deleted file mode 100644 index e11cdd95..00000000 --- a/pyaggr3g470r/views/api/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# pyAggr3g470r - A Web based news aggregator. -# Copyright (C) 2010-2015 Cédric Bonhomme - http://cedricbonhomme.org/ -# -# For more information : https://bitbucket.org/cedricbonhomme/pyaggr3g470r/ -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.2 $" -__date__ = "$Date: 2014/06/18 $" -__revision__ = "$Date: 2014/07/05 $" -__copyright__ = "Copyright (c) Cedric Bonhomme" -__license__ = "AGPLv3" - -from pyaggr3g470r.views.api import article, feed - -__all__ = ['article', 'feed'] diff --git a/pyaggr3g470r/views/api/article.py b/pyaggr3g470r/views/api/article.py deleted file mode 100644 index d2969cb0..00000000 --- a/pyaggr3g470r/views/api/article.py +++ /dev/null @@ -1,63 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 - - -from flask import g -import dateutil.parser - -from pyaggr3g470r.controllers import ArticleController -from pyaggr3g470r.views.api.common import PyAggAbstractResource,\ - PyAggResourceNew, \ - PyAggResourceExisting, \ - PyAggResourceMulti - - -ARTICLE_ATTRS = {'user_id': {'type': int}, - 'feed_id': {'type': int}, - 'entry_id': {'type': str}, - 'link': {'type': str}, - 'title': {'type': str}, - 'readed': {'type': bool}, 'like': {'type': bool}, - 'content': {'type': str}, - 'date': {'type': str}, 'retrieved_date': {'type': str}} - - -class ArticleNewAPI(PyAggResourceNew): - controller_cls = ArticleController - attrs = ARTICLE_ATTRS - to_date = ['date', 'retrieved_date'] - - -class ArticleAPI(PyAggResourceExisting): - controller_cls = ArticleController - attrs = ARTICLE_ATTRS - to_date = ['date', 'retrieved_date'] - - -class ArticlesAPI(PyAggResourceMulti): - controller_cls = ArticleController - attrs = ARTICLE_ATTRS - to_date = ['date', 'retrieved_date'] - - -class ArticlesChallenge(PyAggAbstractResource): - controller_cls = ArticleController - attrs = {'ids': {'type': list, 'default': []}} - to_date = ['date', 'retrieved_date'] - - def get(self): - parsed_args = self.reqparse_args() - for id_dict in parsed_args['ids']: - for key in self.to_date: - if key in id_dict: - id_dict[key] = dateutil.parser.parse(id_dict[key]) - - result = list(self.wider_controller.challenge(parsed_args['ids'])) - return result or None, 200 if result else 204 - - -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') -g.api.add_resource(ArticlesChallenge, '/articles/challenge', - endpoint='articles_challenge.json') diff --git a/pyaggr3g470r/views/api/common.py b/pyaggr3g470r/views/api/common.py deleted file mode 100644 index 6da93a3b..00000000 --- a/pyaggr3g470r/views/api/common.py +++ /dev/null @@ -1,245 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 - - -"""For a given resources, classes in the module intend to create the following -routes : - GET resource/<id> - -> to retrieve one - POST resource - -> to create one - PUT resource/<id> - -> to update one - DELETE resource/<id> - -> to delete one - - GET resources - -> to retrieve several - POST resources - -> to create several - PUT resources - -> to update several - DELETE resources - -> to delete several -""" -import ast -import json -import logging -import dateutil.parser -from functools import wraps -from werkzeug.exceptions import Unauthorized, BadRequest -from flask import request, g, session, Response -from flask.ext.restful import Resource, reqparse - -from pyaggr3g470r.lib.utils import default_handler -from pyaggr3g470r.models import User - -logger = logging.getLogger(__name__) - - -def authenticate(func): - """ - Decorator for the authentication to the web services. - """ - @wraps(func) - def wrapper(*args, **kwargs): - logged_in = False - if not getattr(func, 'authenticated', True): - logged_in = True - # authentication based on the session (already logged on the site) - elif 'email' in session or g.user.is_authenticated: - logged_in = True - else: - # authentication via HTTP only - auth = request.authorization - if auth is not None: - user = User.query.filter( - User.nickname == auth.username).first() - if user and user.check_password(auth.password) \ - and user.activation_key == "": - g.user = user - logged_in = True - if logged_in: - return func(*args, **kwargs) - raise Unauthorized({'WWWAuthenticate': 'Basic realm="Login Required"'}) - return wrapper - - -def to_response(func): - """Will cast results of func as a result, and try to extract - a status_code for the Response object""" - def wrapper(*args, **kwargs): - status_code = 200 - result = func(*args, **kwargs) - if isinstance(result, Response): - return result - elif isinstance(result, tuple): - result, status_code = result - return Response(json.dumps(result, default=default_handler), - status=status_code) - return wrapper - - -class PyAggAbstractResource(Resource): - method_decorators = [authenticate, to_response] - attrs = {} - to_date = [] # list of fields to cast to datetime - - def __init__(self, *args, **kwargs): - super(PyAggAbstractResource, self).__init__(*args, **kwargs) - - @property - def controller(self): - return self.controller_cls(getattr(g.user, 'id', None)) - - @property - def wider_controller(self): - if g.user.is_admin(): - return self.controller_cls() - return self.controller_cls(getattr(g.user, 'id', None)) - - def reqparse_args(self, req=None, strict=False, default=True, args=None): - """ - strict: bool - if True will throw 400 error if args are defined and not in request - default: bool - if True, won't return defaults - args: dict - the args to parse, if None, self.attrs will be used - """ - parser = reqparse.RequestParser() - for attr_name, attrs in (args or self.attrs).items(): - if attrs.pop('force_default', False): - parser.add_argument(attr_name, location='json', **attrs) - elif not default and (not request.json - or request.json and attr_name not in request.json): - continue - else: - parser.add_argument(attr_name, location='json', **attrs) - parsed = parser.parse_args(strict=strict) if req is None \ - else parser.parse_args(req, strict=strict) - for field in self.to_date: - if parsed.get(field): - try: - parsed[field] = dateutil.parser.parse(parsed[field]) - except Exception: - logger.exception('failed to parse %r', parsed[field]) - return parsed - - -class PyAggResourceNew(PyAggAbstractResource): - - def post(self): - """Create a single new object""" - return self.controller.create(**self.reqparse_args()), 201 - - -class PyAggResourceExisting(PyAggAbstractResource): - - def get(self, obj_id=None): - """Retreive a single object""" - return self.controller.get(id=obj_id) - - def put(self, obj_id=None): - """update an object, new attrs should be passed in the payload""" - args = self.reqparse_args(default=False) - new_values = {key: args[key] for key in - set(args).intersection(self.attrs)} - if 'user_id' in new_values and g.user.is_admin(): - controller = self.wider_controller - else: - controller = self.controller - return controller.update({'id': obj_id}, new_values), 200 - - def delete(self, obj_id=None): - """delete a object""" - self.controller.delete(obj_id) - return None, 204 - - -class PyAggResourceMulti(PyAggAbstractResource): - - def get(self): - """retrieve several objects. filters can be set in the payload on the - different fields of the object, and a limit can be set in there as well - """ - try: - limit = request.json.pop('limit', 10) - order_by = request.json.pop('order_by', None) - query = self.controller.read(**request.json) - except: - args = {} - for k, v in request.args.items(): - if k in self.attrs.keys(): - if self.attrs[k]['type'] in [bool, int]: - args[k] = ast.literal_eval(v) - else: - args[k] = v - limit = request.args.get('limit', 10) - order_by = request.args.get('order_by', None) - query = self.controller.read(**args) - if order_by: - query = query.order_by(order_by) - if limit: - query = query.limit(limit) - return [res for res in query] - - def post(self): - """creating several objects. payload should be a list of dict. - """ - if 'application/json' not in request.headers.get('Content-Type'): - raise BadRequest("Content-Type must be application/json") - status = 201 - results = [] - for attrs in request.json: - try: - results.append(self.controller.create(**attrs).id) - except Exception as error: - status = 206 - results.append(str(error)) - # if no operation succeded, it's not partial anymore, returning err 500 - if status == 206 and results.count('ok') == 0: - status = 500 - return results, status - - def put(self): - """creating several objects. payload should be: - >>> payload - [[obj_id1, {attr1: val1, attr2: val2}] - [obj_id2, {attr1: val1, attr2: val2}]] - """ - if 'application/json' not in request.headers.get('Content-Type'): - raise BadRequest("Content-Type must be application/json") - status = 200 - results = [] - for obj_id, attrs in request.json: - try: - new_values = {key: attrs[key] for key in - set(attrs).intersection(self.attrs)} - self.controller.update({'id': obj_id}, new_values) - results.append('ok') - except Exception as error: - status = 206 - results.append(str(error)) - # if no operation succeded, it's not partial anymore, returning err 500 - if status == 206 and results.count('ok') == 0: - status = 500 - return results, status - - def delete(self): - """will delete several objects, - a list of their ids should be in the payload""" - if 'application/json' not in request.headers.get('Content-Type'): - raise BadRequest("Content-Type must be application/json") - status = 204 - results = [] - for obj_id in request.json: - try: - self.controller.delete(obj_id) - results.append('ok') - except Exception as error: - status = 206 - results.append(error) - # if no operation succeded, it's not partial anymore, returning err 500 - if status == 206 and results.count('ok') == 0: - status = 500 - return results, status diff --git a/pyaggr3g470r/views/api/feed.py b/pyaggr3g470r/views/api/feed.py deleted file mode 100644 index c80e9a9b..00000000 --- a/pyaggr3g470r/views/api/feed.py +++ /dev/null @@ -1,69 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 - - -from flask import g - -from pyaggr3g470r.controllers.feed import (FeedController, - DEFAULT_MAX_ERROR, - DEFAULT_LIMIT, - DEFAULT_REFRESH_RATE) - -from pyaggr3g470r.views.api.common import PyAggAbstractResource, \ - PyAggResourceNew, \ - PyAggResourceExisting, \ - PyAggResourceMulti - -FEED_ATTRS = {'title': {'type': str}, - 'description': {'type': str}, - 'link': {'type': str}, - 'user_id': {'type': int}, - 'site_link': {'type': str}, - 'enabled': {'type': bool, 'default': True}, - 'etag': {'type': str, 'default': ''}, - 'icon_url': {'type': str, 'default': ''}, - 'last_modified': {'type': str}, - 'last_retrieved': {'type': str}, - 'last_error': {'type': str}, - 'error_count': {'type': int, 'default': 0}} - -class FeedNewAPI(PyAggResourceNew): - controller_cls = FeedController - attrs = FEED_ATTRS - to_date = ['date', 'last_retrieved'] - -class FeedAPI(PyAggResourceExisting): - controller_cls = FeedController - attrs = FEED_ATTRS - to_date = ['date', 'last_retrieved'] - -class FeedsAPI(PyAggResourceMulti): - controller_cls = FeedController - attrs = FEED_ATTRS - to_date = ['date', 'last_retrieved'] - -class FetchableFeedAPI(PyAggAbstractResource): - controller_cls = FeedController - to_date = ['date', 'last_retrieved'] - attrs = {'max_error': {'type': int, 'default': DEFAULT_MAX_ERROR}, - 'limit': {'type': int, 'default': DEFAULT_LIMIT}, - 'refresh_rate': {'type': int, 'default': DEFAULT_REFRESH_RATE}, - 'retreive_all': {'type': bool, 'default': False}} - - def get(self): - args = self.reqparse_args() - if g.user.refresh_rate: - args['refresh_rate'] = g.user.refresh_rate - - if args.pop('retreive_all', False): - contr = self.wider_controller - else: - contr = self.controller - result = [feed for feed in contr.list_fetchable(**args)] - return result or None, 200 if result else 204 - - -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') |