<commit_before>'''Tools for constructing a JSON-API from sqlalchemy models in Pyramid.'''
import json
#from sqlalchemy import inspect
import transaction
import sqlalchemy
from pyramid.view import view_config, notfound_view_config, forbidden_view_config
from pyramid.renderers import JSON
from pyramid.httpexceptions import exception_response, HTTPException, HTTPNotFound, HTTPForbidden, HTTPUnauthorized, HTTPClientError, HTTPBadRequest, HTTPConflict, HTTPUnsupportedMediaType, HTTPNotAcceptable, HTTPNotImplemented, HTTPError, HTTPFailedDependency
import pyramid
import sys
import inspect
import re
from collections import namedtuple
import psycopg2
import pprint
import functools
import types
import importlib

from zope.sqlalchemy import ZopeTransactionExtension
from sqlalchemy.orm import sessionmaker, scoped_session, load_only
from sqlalchemy.exc import DBAPIError
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from sqlalchemy.orm.relationships import RelationshipProperty
from sqlalchemy.ext.declarative.api import DeclarativeMeta

ONETOMANY = sqlalchemy.orm.interfaces.ONETOMANY
MANYTOMANY = sqlalchemy.orm.interfaces.MANYTOMANY
MANYTOONE = sqlalchemy.orm.interfaces.MANYTOONE

#DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
route_prefix = 'jsonapi'
view_classes = {}

def error(e, request):
    request.response.content_type = 'application/vnd.api+json'
    request.response.status_code = e.code
    return {
        'errors': [
            {
                'code': str(e.code),
                'detail': e.detail,
                'title': e.title,
            }
        ]
    }

class DebugView:
    '''Pyramid view class defining a debug API.

    These are available as ``/debug/{action}`` if
    ``jsonapi.debug.debug_endpoints == 'true'``.

    Attributes:
        engine: sqlalchemy engine with connection to the db.
        metadata: sqlalchemy model metadata
        test_data: module with an ``add_to_db()`` method which will populate the
            database
    '''
    def __init__(self, request):
        self.request = request

    def drop(self):
        '''Drop all tables from the database!!!
        '''
        self.metadata.drop_all(self.engine)
        return 'dropped'

    def populate(self):
        '''Create tables and populate with test data.
        '''
        # Create or update tables and schema. Safe if tables already exist.
        self.metadata.create_all(self.engine)
        # Add test data. Safe if test data already exists.
        self.test_data.add_to_db()
        return 'populated'

    def reset(self):
        '''The same as 'drop' and then 'populate'.
        '''
        self.drop()
        self.populate()
        return "reset"

def create_jsonapi(config, models, get_dbsession,
    engine = None, test_data = None):
    '''Auto-create jsonapi from module with sqlAlchemy models.

    Arguments:
        models (iterable): an iterable (or module) of model classes derived from DeclarativeMeta.
    '''

    config.add_notfound_view(error, renderer='json')
    config.add_forbidden_view(error, renderer='json')
    config.add_view(error, context=HTTPError, renderer='json')

    if isinstance(models, types.ModuleType):
        model_list = []
        for attr in models.__dict__.values():
            if isinstance(attr, DeclarativeMeta):
                try:
                    keycols = sqlalchemy.inspect(attr).primary_key
                except sqlalchemy.exc.NoInspectionAvailable:
                    # Trying to inspect the declarative_base() raises this
                    # exception. We don't want to add it to the API.
                    continue
                model_list.append(attr)
    else:
        model_list = list(models)

    settings = config.registry.settings
    if settings.get('jsonapi.debug.debug_endpoints', 'false') == 'true':
        if engine is None:
            DebugView.engine = model_list[0].metadata.bind
        else:
            DebugView.engine = engine
        DebugView.metadata = model_list[0].metadata
        if test_data is None:
            test_data = importlib.import_module(
                settings.get('jsonapi.debug.test_data_module', 'test_data')
            )
        DebugView.test_data = test_data
        config.add_route('debug', '/debug/{action}')
        config.add_view(DebugView, attr='drop',
            route_name='debug', match_param='action=drop', renderer='json')
        config.add_view(DebugView, attr='populate',
            route_name='debug', match_param='action=populate', renderer='json')
        config.add_view(DebugView, attr='reset',
            route_name='debug', match_param='action=reset', renderer='json')

    # Loop through the models module looking for declaratively defined model
    # classes (inherit DeclarativeMeta). Create resource endpoints for these and
    # any relationships found.
    for model_class in model_list:
        create_resource(config, model_class, get_dbsession = get_dbsession)

create_jsonapi_using_magic_and_pixie_dust = create_jsonapi

def create_resource(config, model, get_dbsession,
        collection_name = None,
        allowed_fields = None,
    ):
    '''Produce a set of resource endpoints.

    Arguments:
        collectiona_name (str): name of collection. Defaults to table name from model.
        allowed_fields (set): set of allowed field names.
    '''

    try:
        keycols = sqlalchemy.inspect(model).primary_key
    except sqlalchemy.exc.NoInspectionAvailable:
        # Trying to inspect the declarative_base() raises this exception. We
        # don't want to add it to the API.
        return

    # Only deal with one primary key column.
    if len(keycols) > 1:
        raise Exception(
            'Model {} has more than one primary key.'.format(model_class.__name__)
        )
    model._jsonapi_id = getattr(model, keycols[0].name)

    # Figure out what table model is from
    info = ModelInfo.construct(model)

    if collection_name is None:
        collection_name = info.table_name

    view = CollectionViewFactory(model, get_dbsession, collection_name,<commit_msg>docstrings and comments for create_resource and create_jsonapi<commit_after>'''Tools for constructing a JSON-API from sqlalchemy models in Pyramid.'''
import json
#from sqlalchemy import inspect
import transaction
import sqlalchemy
from pyramid.view import view_config, notfound_view_config, forbidden_view_config
from pyramid.renderers import JSON
from pyramid.httpexceptions import exception_response, HTTPException, HTTPNotFound, HTTPForbidden, HTTPUnauthorized, HTTPClientError, HTTPBadRequest, HTTPConflict, HTTPUnsupportedMediaType, HTTPNotAcceptable, HTTPNotImplemented, HTTPError, HTTPFailedDependency
import pyramid
import sys
import inspect
import re
from collections import namedtuple
import psycopg2
import pprint
import functools
import types
import importlib

from zope.sqlalchemy import ZopeTransactionExtension
from sqlalchemy.orm import sessionmaker, scoped_session, load_only
from sqlalchemy.exc import DBAPIError
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from sqlalchemy.orm.relationships import RelationshipProperty
from sqlalchemy.ext.declarative.api import DeclarativeMeta

ONETOMANY = sqlalchemy.orm.interfaces.ONETOMANY
MANYTOMANY = sqlalchemy.orm.interfaces.MANYTOMANY
MANYTOONE = sqlalchemy.orm.interfaces.MANYTOONE

#DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
route_prefix = 'jsonapi'
view_classes = {}

def error(e, request):
    request.response.content_type = 'application/vnd.api+json'
    request.response.status_code = e.code
    return {
        'errors': [
            {
                'code': str(e.code),
                'detail': e.detail,
                'title': e.title,
            }
        ]
    }

class DebugView:
    '''Pyramid view class defining a debug API.

    These are available as ``/debug/{action}`` if
    ``jsonapi.debug.debug_endpoints == 'true'``.

    Attributes:
        engine: sqlalchemy engine with connection to the db.
        metadata: sqlalchemy model metadata
        test_data: module with an ``add_to_db()`` method which will populate the
            database
    '''
    def __init__(self, request):
        self.request = request

    def drop(self):
        '''Drop all tables from the database!!!
        '''
        self.metadata.drop_all(self.engine)
        return 'dropped'

    def populate(self):
        '''Create tables and populate with test data.
        '''
        # Create or update tables and schema. Safe if tables already exist.
        self.metadata.create_all(self.engine)
        # Add test data. Safe if test data already exists.
        self.test_data.add_to_db()
        return 'populated'

    def reset(self):
        '''The same as 'drop' and then 'populate'.
        '''
        self.drop()
        self.populate()
        return "reset"

def create_jsonapi(config, models, get_dbsession,
    engine = None, test_data = None):
    '''Auto-create jsonapi from module with sqlAlchemy models.

    Arguments:
        config: ``pyramid.config.Configurator`` object from current app.
        models: an iterable (or module) of model classes derived
            from DeclarativeMeta.
        get_dbsession: a callable shich returns a
            sqlalchemy.orm.session.Session or equivalent.

    Keyword Args:
        engine: a sqlalchemy.engine.Engine instance. Only required if using the
            debug view.
        test_data: a module with an ``add_to_db()`` method which will populate
            the database.
    '''

    config.add_notfound_view(error, renderer='json')
    config.add_forbidden_view(error, renderer='json')
    config.add_view(error, context=HTTPError, renderer='json')

    # Build a list of declarative models to add as collections.
    if isinstance(models, types.ModuleType):
        model_list = []
        for attr in models.__dict__.values():
            if isinstance(attr, DeclarativeMeta):
                try:
                    keycols = sqlalchemy.inspect(attr).primary_key
                except sqlalchemy.exc.NoInspectionAvailable:
                    # Trying to inspect the declarative_base() raises this
                    # exception. We don't want to add it to the API.
                    continue
                model_list.append(attr)
    else:
        model_list = list(models)

    settings = config.registry.settings

    # Add the debug endpoints if required.
    if settings.get('jsonapi.debug.debug_endpoints', 'false') == 'true':
        if engine is None:
            DebugView.engine = model_list[0].metadata.bind
        else:
            DebugView.engine = engine
        DebugView.metadata = model_list[0].metadata
        if test_data is None:
            test_data = importlib.import_module(
                settings.get('jsonapi.debug.test_data_module', 'test_data')
            )
        DebugView.test_data = test_data
        config.add_route('debug', '/debug/{action}')
        config.add_view(DebugView, attr='drop',
            route_name='debug', match_param='action=drop', renderer='json')
        config.add_view(DebugView, attr='populate',
            route_name='debug', match_param='action=populate', renderer='json')
        config.add_view(DebugView, attr='reset',
            route_name='debug', match_param='action=reset', renderer='json')

    # Loop through the models list. Create resource endpoints for these and
    # any relationships found.
    for model_class in model_list:
        create_resource(config, model_class, get_dbsession = get_dbsession)

create_jsonapi_using_magic_and_pixie_dust = create_jsonapi

def create_resource(config, model, get_dbsession,
        collection_name = None,
        allowed_fields = None,
    ):
    '''Produce a set of resource endpoints.

    Arguments:
        config: ``pyramid.config.Configurator`` object from current app.
        model: a model class derived from DeclarativeMeta.
        get_dbsession: a callable shich returns a
            sqlalchemy.orm.session.Session or equivalent.

    Keyword Args:
        collection_name: string name of collection. Passed through to
            ``CollectionViewFactory()``
        allowed_fields: set of allowed field names. Passed through to
            ``CollectionViewFactory()``
    '''

    # Find the primary key column from the model and add it as _jsonapi_id.
    try:
        keycols = sqlalchemy.inspect(model).primary_key
    except sqlalchemy.exc.NoInspectionAvailable:
        # Trying to inspect the declarative_base() raises this exception. We
        # don't want to add it to the API.
        return
    # Only deal with one primary key column.
    if len(keycols) > 1:
        raise Exception(
            'Model {} has more than one primary key.'.format(
                model_class.__name__
            )
        )
    model._jsonapi_id = getattr(model, keycols[0].name)

    # Figure out what table model is from
    info = ModelInfo.construct(model)

    if collection_name is None:
        collection_name = info.table_name

    # Create a view class for use in the various add_view() calls below.
    view = CollectionViewFactory(model, get_dbsession, collection_name,
{'fraction_non_alphanumeric': 0.08357826512924862, 'fraction_numerical': 0.0007536362951239731, 'mean_word_length': 2.8869361452841242, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '3175179', 'n_tokens_mistral': 3757, 'n_tokens_neox': 3618, 'n_words': 1043}