<commit_before># -*- coding: utf8 -*-
# This file is part of PyBossa.
#
# Copyright (C) 2013 SF Isle of Man Limited
#
# PyBossa 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.
#
# PyBossa 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 PyBossa.  If not, see <http://www.gnu.org/licenses/>.
"""
PyBossa api module for exposing domain objects via an API.

This package adds GET, POST, PUT and DELETE methods for any class:
    * projects,
    * tasks,
    * task_runs,
    * users,
    * etc.

"""
import json
from flask import request, abort, Response
from flask.views import MethodView
from werkzeug.exceptions import NotFound, Unauthorized, Forbidden
from pybossa.util import jsonpify, crossdomain
from pybossa.core import ratelimits
from pybossa.auth import require
from pybossa.hateoas import Hateoas
from pybossa.ratelimit import ratelimit
from pybossa.error import ErrorStatus

from pybossa.core import project_repo, user_repo, task_repo

repos = {'Task'   : {'repo': task_repo, 'filter': 'filter_tasks_by',
                     'get': 'get_task', 'save': 'save', 'update': 'update',
                     'delete': 'delete'},
        'TaskRun' : {'repo': task_repo, 'filter': 'filter_task_runs_by',
                     'get': 'get_task_run',  'save': 'save', 'update': 'update',
                     'delete': 'delete'},
        'User'    : {'repo': user_repo, 'filter': 'filter_by', 'get': 'get',
                     'save': 'save', 'update': 'update'},
        'App'     : {'repo': project_repo, 'filter': 'filter_by', 'get': 'get',
                     'save': 'save', 'update': 'update', 'delete': 'delete'},
        'Category': {'repo': project_repo, 'filter': 'filter_categories_by',
                     'get': 'get_category', 'save': 'save_category',
                     'update': 'update_category', 'delete': 'delete_category'}
        }


cors_headers = ['Content-Type', 'Authorization']

error = ErrorStatus()


class APIBase(MethodView):

    """Class to create CRUD methods."""

    hateoas = Hateoas()

    def valid_args(self):
        """Check if the domain object args are valid."""
        for k in request.args.keys():
            if k not in ['api_key']:
                getattr(self.__class__, k)

    @crossdomain(origin='*', headers=cors_headers)
    def options(self):  # pragma: no cover
        """Return '' for Options method."""
        return ''

    @jsonpify
    @crossdomain(origin='*', headers=cors_headers)
    @ratelimit(limit=ratelimits.get('LIMIT'), per=ratelimits.get('PER'))
    def get(self, id):
        """Get an object.

        Returns an item from the DB with the request.data JSON object or all
        the items if id == None

        :arg self: The class of the object to be retrieved
        :arg integer id: the ID of the object in the DB
        :returns: The JSON item/s stored in the DB

        """
        try:
            getattr(require, self.__class__.__name__.lower()).read()
            query = self._db_query(id)
            json_response = self._create_json_response(query, id)
            return Response(json_response, mimetype='application/json')
        except Exception as e:
            return error.format_exception(
                e,
                target=self.__class__.__name__.lower(),
                action='GET')

    def _create_json_response(self, query_result, id):
        if len (query_result) == 1 and query_result[0] is None:
            raise abort(404)
        items = []
        for item in query_result:
            try:
                items.append(self._create_dict_from_model(item))
                getattr(require, self.__class__.__name__.lower()).read(item)
            except (Forbidden, Unauthorized):
                # Remove last added item, as it is 401 or 403
                items.pop() 
            except: # pragma: no cover
                raise
        if id:
            getattr(require, self.__class__.__name__.lower()).read(query_result[0])
            items = items[0]
        return json.dumps(items)

    def _create_dict_from_model(self, model):
        return self._select_attributes(self._add_hateoas_links(model))

    def _add_hateoas_links(self, item):
        obj = item.dictize()
        links, link = self.hateoas.create_links(item)
        if links:
            obj['links'] = links
        if link:
            obj['link'] = link
        return obj

    def _db_query(self, id):
        """Returns a list with the results of the query"""
        repo_info = repos[self.__class__.__name__]
        if id is None:
            limit, offset = self._set_limit_and_offset()
            results = self._filter_query(repo_info, limit, offset)
        else:
            repo = repo_info['repo']
            query_func = repo_info['get']
            results = [getattr(repo, query_func)(id)]
        return results

    def _filter_query(self, repo_info, limit, offset):
        filters = {}
        for k in request.args.keys():
            if k not in ['limit', 'offset', 'api_key']:
                # Raise an error if the k arg is not a column
                getattr(self.__class__, k)
                filters[k] = request.args[k]
        repo = repo_info['repo']
        query_func = repo_info['filter']
        filters = self._custom_filter(filters)
        results = getattr(repo, query_func)(**filters)
        return self._format_query_result(results, limit, offset)

    def _format_query_result(self, results, limit, offset):
        results = sorted(results, key=lambda item: item.id)
        results = results[offset:offset+limit]
        return results

    def _set_limit_and_offset(self):
        try:
            limit = min(100, int(request.args.get('limit')))
        except (ValueError, TypeError):
            limit = 20<commit_msg>Use limit and offset in repository method calls in API<commit_after># -*- coding: utf8 -*-
# This file is part of PyBossa.
#
# Copyright (C) 2013 SF Isle of Man Limited
#
# PyBossa 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.
#
# PyBossa 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 PyBossa.  If not, see <http://www.gnu.org/licenses/>.
"""
PyBossa api module for exposing domain objects via an API.

This package adds GET, POST, PUT and DELETE methods for any class:
    * projects,
    * tasks,
    * task_runs,
    * users,
    * etc.

"""
import json
from flask import request, abort, Response
from flask.views import MethodView
from werkzeug.exceptions import NotFound, Unauthorized, Forbidden
from pybossa.util import jsonpify, crossdomain
from pybossa.core import ratelimits
from pybossa.auth import require
from pybossa.hateoas import Hateoas
from pybossa.ratelimit import ratelimit
from pybossa.error import ErrorStatus

from pybossa.core import project_repo, user_repo, task_repo

repos = {'Task'   : {'repo': task_repo, 'filter': 'filter_tasks_by',
                     'get': 'get_task', 'save': 'save', 'update': 'update',
                     'delete': 'delete'},
        'TaskRun' : {'repo': task_repo, 'filter': 'filter_task_runs_by',
                     'get': 'get_task_run',  'save': 'save', 'update': 'update',
                     'delete': 'delete'},
        'User'    : {'repo': user_repo, 'filter': 'filter_by', 'get': 'get',
                     'save': 'save', 'update': 'update'},
        'App'     : {'repo': project_repo, 'filter': 'filter_by', 'get': 'get',
                     'save': 'save', 'update': 'update', 'delete': 'delete'},
        'Category': {'repo': project_repo, 'filter': 'filter_categories_by',
                     'get': 'get_category', 'save': 'save_category',
                     'update': 'update_category', 'delete': 'delete_category'}
        }


cors_headers = ['Content-Type', 'Authorization']

error = ErrorStatus()


class APIBase(MethodView):

    """Class to create CRUD methods."""

    hateoas = Hateoas()

    def valid_args(self):
        """Check if the domain object args are valid."""
        for k in request.args.keys():
            if k not in ['api_key']:
                getattr(self.__class__, k)

    @crossdomain(origin='*', headers=cors_headers)
    def options(self):  # pragma: no cover
        """Return '' for Options method."""
        return ''

    @jsonpify
    @crossdomain(origin='*', headers=cors_headers)
    @ratelimit(limit=ratelimits.get('LIMIT'), per=ratelimits.get('PER'))
    def get(self, id):
        """Get an object.

        Returns an item from the DB with the request.data JSON object or all
        the items if id == None

        :arg self: The class of the object to be retrieved
        :arg integer id: the ID of the object in the DB
        :returns: The JSON item/s stored in the DB

        """
        try:
            getattr(require, self.__class__.__name__.lower()).read()
            query = self._db_query(id)
            json_response = self._create_json_response(query, id)
            return Response(json_response, mimetype='application/json')
        except Exception as e:
            return error.format_exception(
                e,
                target=self.__class__.__name__.lower(),
                action='GET')

    def _create_json_response(self, query_result, id):
        if len (query_result) == 1 and query_result[0] is None:
            raise abort(404)
        items = []
        for item in query_result:
            try:
                items.append(self._create_dict_from_model(item))
                getattr(require, self.__class__.__name__.lower()).read(item)
            except (Forbidden, Unauthorized):
                # Remove last added item, as it is 401 or 403
                items.pop() 
            except: # pragma: no cover
                raise
        if id:
            getattr(require, self.__class__.__name__.lower()).read(query_result[0])
            items = items[0]
        return json.dumps(items)

    def _create_dict_from_model(self, model):
        return self._select_attributes(self._add_hateoas_links(model))

    def _add_hateoas_links(self, item):
        obj = item.dictize()
        links, link = self.hateoas.create_links(item)
        if links:
            obj['links'] = links
        if link:
            obj['link'] = link
        return obj

    def _db_query(self, id):
        """Returns a list with the results of the query"""
        repo_info = repos[self.__class__.__name__]
        if id is None:
            limit, offset = self._set_limit_and_offset()
            results = self._filter_query(repo_info, limit, offset)
        else:
            repo = repo_info['repo']
            query_func = repo_info['get']
            results = [getattr(repo, query_func)(id)]
        return results

    def _filter_query(self, repo_info, limit, offset):
        filters = {}
        for k in request.args.keys():
            if k not in ['limit', 'offset', 'api_key']:
                # Raise an error if the k arg is not a column
                getattr(self.__class__, k)
                filters[k] = request.args[k]
        repo = repo_info['repo']
        query_func = repo_info['filter']
        filters = self._custom_filter(filters)
        results = getattr(repo, query_func)(limit=limit, offset=offset, **filters)
        return results

    def _set_limit_and_offset(self):
        try:
            limit = min(100, int(request.args.get('limit')))
        except (ValueError, TypeError):
            limit = 20
{'fraction_non_alphanumeric': 0.11142671644213602, 'fraction_numerical': 0.003895471514364551, 'mean_word_length': 2.416412531189354, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2230250', 'n_tokens_mistral': 3617, 'n_tokens_neox': 3473, 'n_words': 1112}