<commit_before>import types

import bottle
from bottle import request, response
import sqlalchemy as sa

from bauble import app, API_ROOT
from bauble.middleware import basic_auth, filter_param, build_counts
from bauble.model import Taxon, VernacularName, get_relation  # TaxonNote


column_names = [col.name for col in sa.inspect(Taxon).columns]

def resolve_taxon(next):
    def _wrapped(*args, **kwargs):
        request.taxon = request.session.query(Taxon).get(request.args['taxon_id'])
        if not request.taxon:
            bottle.abort(404, "Taxon not found")
        return next(*args, **kwargs)
    return _wrapped


def build_embedded(embed, taxon):
    if embed == 'synonyms':
        return (embed, [obj.json() for obj in taxon.synonyms])

    data = get_relation(Taxon, taxon.id, embed, session=request.session)
    if isinstance(data, (list, types.GeneratorType)):
        return (embed, [obj.json() for obj in data])
    else:
        return (embed, data.json() if data else {})


@app.get(API_ROOT + "/taxon")
@basic_auth
@filter_param(Taxon, column_names)
def index_taxon():
    # TODO: we're not doing any sanitization or validation...see preggy or validate.py

    taxa = request.filter if request.filter else request.session.query(Taxon)
    return [taxon.json() for taxon in taxa]


@app.get(API_ROOT + "/taxon/<taxon_id:int>")
@basic_auth
@resolve_taxon
def get_taxon(taxon_id):

    json_data = request.taxon.json()

    if 'embed' in request.params:
        embed_list = request.params.embed if isinstance(request.params.embed, list) \
            else [request.params.embed]
        embedded = map(lambda embed: build_embedded(embed, request.taxon), embed_list)
        json_data.update(embedded)

    return json_data


@app.route(API_ROOT + "/taxon/<taxon_id:int>", method='PATCH')
@basic_auth
@resolve_taxon
def patch_taxon(taxon_id):

    if not request.taxon:
        bottle.abort(400, 'The request doesn\'t contain a request body')

    # create a copy of the request data with only the columns
    data = {col: request.json[col] for col in request.json.keys() if col in column_names}
    for key, value in data.items():
        setattr(request.taxon, key, data[key])
    request.session.commit()
    return request.taxon.json()


@app.post(API_ROOT + "/taxon")
@basic_auth
def post_taxon():

    if not request.json:
        bottle.abort(400, 'The request doesn\'t contain a request body')

    # TODO create a subset of the columns that we consider mutable
    mutable = []

    # create a copy of the request data with only the columns
    data = {col: request.json[col] for col in request.json.keys() if col in column_names}

    # if there isn't a genus_id look for a genus relation on the request data
    if not 'genus_id' in data and 'genus' in request.json and isinstance(request.json['genus'], dict) and 'id' in request.json['genus']:
        data['genus_id'] = request.json['genus']['id']

    # make a copy of the data for only those fields that are columns
    taxon = Taxon(**data)
    request.session.add(taxon)
    request.session.commit()
    response.status = 201
    return taxon.json()


@app.delete(API_ROOT + "/taxon/<taxon_id:int>")
@basic_auth
@resolve_taxon
def delete_taxon(taxon_id):
    request.session.delete(request.taxon)
    request.session.commit()


@app.get(API_ROOT + "/taxon/<taxon_id:int>/synonyms")
@basic_auth
@resolve_taxon
def list_synonyms(taxon_id):
    return request.taxon.synonyms


# @app.get(API_ROOT + "/taxon/<taxon_id:int>/synonyms/<synonym_id:int>")
# @basic_auth
# @resolve_taxon
# def get_synonym(taxon_id, synonym_id):
#     return request.taxon.synonyms


@app.post(API_ROOT + "/taxon/<taxon_id:int>/synonyms")
@basic_auth
@resolve_taxon
def add_synonym(taxon_id):
    synonym_json = request.json
    if 'id' not in synonym_json:
        bottle.abort(400, "No id in request body")
    syn_taxon = request.session.query(Taxon).get(synonym_json['id'])
    request.taxon.synonyms.append(syn_taxon)
    request.session.commit()
    response.status = 201


@app.delete(API_ROOT + "/taxon/<taxon_id:int>/synonyms/<synonym_id:int>")
@basic_auth
@resolve_taxon
def remove_synonym_(taxon_id, synonym_id):
    # synonym_id is the id of the taxon not the TaxonSynonym object
    syn_taxon = request.session.query(Taxon).get(synonym_id)
    request.taxon.synonyms.remove(syn_taxon)
    request.session.commit()


@app.get(API_ROOT + "/taxon/<taxon_id:int>/names")
@basic_auth
@resolve_taxon
def list_names(taxon_id):
    return [name.json() for name in request.taxon.vernacular_names]


# @app.get(API_ROOT + "/taxon/<taxon_id:int>/names/<synonym_id:int>")
# @basic_auth
# @resolve_taxon
# def get_synonym(taxon_id, synonym_id):
#     return request.taxon.names


@app.post(API_ROOT + "/taxon/<taxon_id:int>/names")
@basic_auth
@resolve_taxon
def add_name(taxon_id):
    name_json = request.json
    name = VernacularName(name=name_json['name'], language=name_json['language'])
    request.taxon.vernacular_names.append(name)
    if 'default' in name_json and name_json['default'] is True:
        request.taxon.default_vernacular_name = name
    request.session.commit()
    response.status = 201
    return name.json()


@app.delete(API_ROOT + "/taxon/<taxon_id:int>/names/<name_id:int>")
@basic_auth
@resolve_taxon
def remove_name(taxon_id, name_id):
    # synonym_id is the id of the taxon not the TaxonSynonym object
    name = request.session.query(VernacularName).get(request.args['name_id'])
    if not name:
        bottle.abort(404, "Name not found")
    request.taxon.vernacular_names.remove(name)
    request.session.commit()


@app.get(API_ROOT + "/taxon/<taxon_id:int>/count")
@basic_auth
@resolve_taxon
@build_counts(Taxon, 'taxon_id')
def count(taxon_id):
    return request.counts
<commit_msg>more work on taxon vernacular names route<commit_after>import types

import bottle
from bottle import request, response
import sqlalchemy as sa

from bauble import app, API_ROOT
from bauble.middleware import basic_auth, filter_param, build_counts
from bauble.model import Taxon, VernacularName, get_relation  # TaxonNote


taxon_column_names = [col.name for col in sa.inspect(Taxon).columns]
taxon_mutable = [col for col in taxon_column_names
                 if col not in ['id'] and not col.startswith('_')]

vn_column_names = [col.name for col in sa.inspect(VernacularName).columns]
vn_mutable = [col for col in vn_column_names
              if col not in ['id'] and not col.startswith('_')]

def resolve_taxon(next):
    def _wrapped(*args, **kwargs):
        request.taxon = request.session.query(Taxon).get(request.args['taxon_id'])
        if not request.taxon:
            bottle.abort(404, "Taxon not found")
        return next(*args, **kwargs)
    return _wrapped


def resolve_name(next):
    def _wrapped(*args, **kwargs):
        request.name = request.session.query(VernacularName).get(request.args['name_id'])
        if not request.name:
            bottle.abort(404, "Name not found")
        return next(*args, **kwargs)
    return _wrapped


def build_embedded(embed, taxon):
    if embed == 'synonyms':
        return (embed, [obj.json() for obj in taxon.synonyms])

    data = get_relation(Taxon, taxon.id, embed, session=request.session)
    if isinstance(data, (list, types.GeneratorType)):
        return (embed, [obj.json() for obj in data])
    else:
        return (embed, data.json() if data else {})


@app.get(API_ROOT + "/taxon")
@basic_auth
@filter_param(Taxon, column_names)
def index_taxon():
    # TODO: we're not doing any sanitization or validation...see preggy or validate.py

    taxa = request.filter if request.filter else request.session.query(Taxon)
    return [taxon.json() for taxon in taxa]


@app.get(API_ROOT + "/taxon/<taxon_id:int>")
@basic_auth
@resolve_taxon
def get_taxon(taxon_id):

    json_data = request.taxon.json()

    if 'embed' in request.params:
        embed_list = request.params.embed if isinstance(request.params.embed, list) \
            else [request.params.embed]
        embedded = map(lambda embed: build_embedded(embed, request.taxon), embed_list)
        json_data.update(embedded)

    return json_data


@app.route(API_ROOT + "/taxon/<taxon_id:int>", method='PATCH')
@basic_auth
@resolve_taxon
def patch_taxon(taxon_id):

    if not request.json:
        bottle.abort(400, 'The request doesn\'t contain a request body')

    # create a copy of the request data with only mutable
    data = {col: request.json[col] for col in request.json.keys()
            if col in taxon_mutable}
    for key, value in data.items():
        setattr(request.taxon, key, data[key])
    request.session.commit()
    return request.taxon.json()


@app.post(API_ROOT + "/taxon")
@basic_auth
def post_taxon():

    if not request.json:
        bottle.abort(400, 'The request doesn\'t contain a request body')

    # create a copy of the request data with only the columns
    data = {col: request.json[col] for col in request.json.keys()
            if col in taxon_mutable}

    # if there isn't a genus_id look for a genus relation on the request data
    if not 'genus_id' in data and 'genus' in request.json and isinstance(request.json['genus'], dict) and 'id' in request.json['genus']:
        data['genus_id'] = request.json['genus']['id']

    # make a copy of the data for only those fields that are columns
    taxon = Taxon(**data)
    request.session.add(taxon)
    request.session.commit()
    response.status = 201
    return taxon.json()


@app.delete(API_ROOT + "/taxon/<taxon_id:int>")
@basic_auth
@resolve_taxon
def delete_taxon(taxon_id):
    request.session.delete(request.taxon)
    request.session.commit()


@app.get(API_ROOT + "/taxon/<taxon_id:int>/synonyms")
@basic_auth
@resolve_taxon
def list_synonyms(taxon_id):
    return request.taxon.synonyms


# @app.get(API_ROOT + "/taxon/<taxon_id:int>/synonyms/<synonym_id:int>")
# @basic_auth
# @resolve_taxon
# def get_synonym(taxon_id, synonym_id):
#     return request.taxon.synonyms


@app.post(API_ROOT + "/taxon/<taxon_id:int>/synonyms")
@basic_auth
@resolve_taxon
def add_synonym(taxon_id):
    synonym_json = request.json
    if 'id' not in synonym_json:
        bottle.abort(400, "No id in request body")
    syn_taxon = request.session.query(Taxon).get(synonym_json['id'])
    request.taxon.synonyms.append(syn_taxon)
    request.session.commit()
    response.status = 201


@app.delete(API_ROOT + "/taxon/<taxon_id:int>/synonyms/<synonym_id:int>")
@basic_auth
@resolve_taxon
def remove_synonym_(taxon_id, synonym_id):
    # synonym_id is the id of the taxon not the TaxonSynonym object
    syn_taxon = request.session.query(Taxon).get(synonym_id)
    request.taxon.synonyms.remove(syn_taxon)
    request.session.commit()


@app.get(API_ROOT + "/taxon/<taxon_id:int>/names")
@basic_auth
@resolve_taxon
def list_names(taxon_id):
    return [name.json() for name in request.taxon.vernacular_names]


# @app.get(API_ROOT + "/taxon/<taxon_id:int>/names/<synonym_id:int>")
# @basic_auth
# @resolve_taxon
# def get_synonym(taxon_id, synonym_id):
#     return request.taxon.names


@app.post(API_ROOT + "/taxon/<taxon_id:int>/names")
@basic_auth
@resolve_taxon
def post_name(taxon_id):
    name_json = request.json
    name = VernacularName(name=name_json['name'], language=name_json['language']
                          if 'language' in name_json else None)
    request.taxon.vernacular_names.append(name)
    if 'default' in name_json and name_json['default'] is True:
        request.taxon.default_vernacular_name = name
    request.session.commit()
    response.status = 201
    return name.json()


@app.route(API_ROOT + "/taxon/<taxon_id:int>/names/<name_id:int>", method='PATCH')
@basic_auth
@resolve_taxon
@resolve_name
def patch_name(taxon_id, name_id):

    if not request.json:
        bottle.abort(400, 'The request doesn\'t contain a request body')

    # create a copy of the request data with only the columns
    data = {col: request.json[col] for col in request.json.keys()
            if col in vn_mutable}
    for key, value in data.items():
        setattr(request.name, key, data[key])

    request.session.commit()
    return request.taxon.json()


@app.delete(API_ROOT + "/taxon/<taxon_id:int>/names/<name_id:int>")
@basic_auth
@resolve_taxon
@resolve_name
def remove_name(taxon_id, name_id):
    request.taxon.vernacular_names.remove(request.name)
    request.session.commit()


@app.get(API_ROOT + "/taxon/<taxon_id:int>/count")
@basic_auth
@resolve_taxon
@build_counts(Taxon, 'taxon_id')
def count(taxon_id):
    return request.counts

{'fraction_non_alphanumeric': 0.12281399046104928, 'fraction_numerical': 0.004054054054054054, 'mean_word_length': 3.945361635220126, 'pattern_counts': {'":': 0, '<': 37, '<?xml version=': 0, '>': 37, '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': '732448', 'n_tokens_mistral': 4410, 'n_tokens_neox': 4331, 'n_words': 1012}