aboutsummaryrefslogtreecommitdiff
path: root/pyaggr3g470r/views/api
diff options
context:
space:
mode:
authorCédric Bonhomme <cedric@cedricbonhomme.org>2015-11-25 22:45:43 +0100
committerCédric Bonhomme <cedric@cedricbonhomme.org>2015-11-25 22:45:43 +0100
commitb2618e9404b84cc62d4becb02436233a0d53b375 (patch)
treea31f2dc76d23967fa0243374cf475923a4b7e451 /pyaggr3g470r/views/api
parentUpdated default platform URL (for Heroku...). (diff)
downloadnewspipe-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__.py31
-rw-r--r--pyaggr3g470r/views/api/article.py63
-rw-r--r--pyaggr3g470r/views/api/common.py245
-rw-r--r--pyaggr3g470r/views/api/feed.py69
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')
bgstack15