def export_taxonomy(taxonomy_code: str): # get cwd path cwd = pathlib.Path().absolute() path = str(cwd / f"{taxonomy_code}.csv") # get taxonomy flatten dicts (header and content) taxonomy = current_flask_taxonomies.get_taxonomy(taxonomy_code) first_level_list = current_flask_taxonomies.list_taxonomy(taxonomy, levels=1).all() taxonomy_header_dict = get_taxonomy_header_dict(taxonomy) taxonomy_terms_list = get_taxonomy_terms_list(first_level_list) csv_path = convert_to_csv(path, taxonomy_terms_list) excel_path = convert_csv_to_excel(path, taxonomy_header_dict) return csv_path, excel_path
def test_get_taxonomy_terms_list(app, db): file_path = pathlib.Path(__file__).parent.absolute() data_path = file_path / "data" / "licenses_v2.xlsx" str_path = str(data_path) import_taxonomy(str_path) taxonomy = current_flask_taxonomies.get_taxonomy("licenses") first_level = current_flask_taxonomies.list_taxonomy(taxonomy, levels=1).all() res = get_taxonomy_terms_list(first_level) assert res[0]["level"] == 1 assert res[1]["level"] == 2 assert res[2]["level"] == 3 assert res[3]["level"] == 3 assert res[8]["level"] == 2
def get_taxonomy_json( code=None, slug=None, prefer: Representation = Representation("representation"), page=None, size=None, status_code=200, q=None, request=None): taxonomy = current_flask_taxonomies.get_taxonomy(code) prefer = taxonomy.merge_select(prefer) if request: current_flask_taxonomies.permissions.taxonomy_term_read.enforce( request=request, taxonomy=taxonomy, slug=slug) if INCLUDE_DELETED in prefer: status_cond = sqlalchemy.sql.true() else: status_cond = TaxonomyTerm.status == TermStatusEnum.alive return_descendants = INCLUDE_DESCENDANTS in prefer if return_descendants: query = current_flask_taxonomies.descendants_or_self( TermIdentification(taxonomy=code, slug=slug), levels=prefer.options.get('levels', None), status_cond=status_cond, return_descendants_count=INCLUDE_DESCENDANTS_COUNT in prefer, return_descendants_busy_count=INCLUDE_STATUS in prefer) else: query = current_flask_taxonomies.filter_term( TermIdentification(taxonomy=code, slug=slug), status_cond=status_cond, return_descendants_count=INCLUDE_DESCENDANTS_COUNT in prefer, return_descendants_busy_count=INCLUDE_STATUS in prefer) if q: query = current_flask_taxonomies.apply_term_query(query, q, code) paginator = Paginator(prefer, query, page if return_descendants else None, size if return_descendants else None, json_converter=lambda data: build_descendants( data, prefer, root_slug=None), allow_empty=INCLUDE_SELF not in prefer, single_result=INCLUDE_SELF in prefer, has_query=q is not None) return paginator
def patch_taxonomy_term(code=None, slug=None, prefer=None, page=None, size=None, q=None): if q: json_abort( 422, { 'message': 'Query not appropriate when creating or updating term', 'reason': 'search-query-not-allowed' }) taxonomy = current_flask_taxonomies.get_taxonomy(code, fail=False) if not taxonomy: json_abort(404, {}) prefer = taxonomy.merge_select(prefer) if INCLUDE_DELETED in prefer: status_cond = sqlalchemy.sql.true() else: status_cond = TaxonomyTerm.status == TermStatusEnum.alive ti = TermIdentification(taxonomy=code, slug=slug) term = current_flask_taxonomies.filter_term( ti, status_cond=status_cond).one_or_none() if not term: abort(404) current_flask_taxonomies.permissions.taxonomy_term_update.enforce( request=request, taxonomy=taxonomy, term=term) current_flask_taxonomies.update_term( term, status_cond=status_cond, extra_data=request.json, patch=True, status=TermStatusEnum.alive # make it alive if it was deleted ) current_flask_taxonomies.commit() return get_taxonomy_term(code=code, slug=slug, prefer=prefer, page=page, size=size)
def import_countries(db): # with db.session.begin_nested(): try: Base.metadata.create_all(db.engine) except: pass tax = current_flask_taxonomies.get_taxonomy(code='country', fail=False) if tax: return tax = current_flask_taxonomies.create_taxonomy( code='country', extra_data={'title': 'List of countries'}, url='https://www.kaggle.com/nikitagrec/world-capitals-gps/data') continents = {} with open(os.path.join(os.path.dirname(__file__), 'countries.csv'), 'r') as f: rdr = csv.DictReader(f) for row in rdr: try: continent = row['ContinentName'] country = row['CountryCode'] if continent not in continents: print('Creating continent', continent.lower()) continent_term = current_flask_taxonomies.create_term( TermIdentification(taxonomy=tax, slug=continent.lower().replace( ' ', '-')), ) continents[continent] = continent_term slug = '%s/%s' % (continent.lower(), country.lower()) slug = slug.replace(' ', '-') print('Importing', slug) current_flask_taxonomies.create_term(TermIdentification( taxonomy=tax, slug=slug), extra_data=row) except: traceback.print_exc() return try: db.session.commit() except: traceback.print_exc() sys.exit(1)
def delete_taxonomy(code=None): """ Deletes a taxonomy. Note: this call is destructive in a sense that all its terms, regardless if used or not, are deleted as well. A tight user permissions should be employed. """ try: tax = current_flask_taxonomies.get_taxonomy(code=code) except NoResultFound: json_abort(404, {}) return # make pycharm happy current_flask_taxonomies.permissions.taxonomy_delete.enforce( request=request, code=code) current_flask_taxonomies.delete_taxonomy(tax) current_flask_taxonomies.commit() return Response(status=204)
def create_update_taxonomy(data, drop) -> Taxonomy: tax_dict = convert_data_to_dict(data)[0] if 'code' not in tax_dict: raise ValueError('Taxonomy does not contain "code"') code = tax_dict.pop('code') taxonomy = current_flask_taxonomies.get_taxonomy(code, fail=False) if taxonomy and drop: current_flask_taxonomies.delete_taxonomy(taxonomy) taxonomy = None if taxonomy: merged_dict = taxonomy.extra_data merged_dict.update(tax_dict) current_flask_taxonomies.update_taxonomy(taxonomy, merged_dict) else: taxonomy = current_flask_taxonomies.create_taxonomy(code, extra_data=tax_dict) db.session.commit() return taxonomy
def create_update_taxonomy(code=None, prefer=None, page=None, size=None, q=None): if q: json_abort( 422, { 'message': 'Query not appropriate when creating or updating taxonomy', 'reason': 'search-query-not-allowed' }) tax = current_flask_taxonomies.get_taxonomy(code=code, fail=False) if tax: current_flask_taxonomies.permissions.taxonomy_update.enforce( request=request, taxonomy=tax) else: current_flask_taxonomies.permissions.taxonomy_create.enforce( request=request, code=code) data = request.json url = data.pop('url', None) select = data.pop('select', None) if not tax: current_flask_taxonomies.create_taxonomy(code=code, extra_data=request.json, url=url, select=select) status_code = 201 else: current_flask_taxonomies.update_taxonomy(tax, extra_data=request.json, url=url, select=select) status_code = 200 current_flask_taxonomies.commit() return get_taxonomy(code, prefer=prefer, page=page, size=size, status_code=status_code)
def test_taxonomy_term_moved(app, db, taxonomy_tree, test_record): taxonomy = current_flask_taxonomies.get_taxonomy("test_taxonomy") terms = current_flask_taxonomies.list_taxonomy(taxonomy).all() old_record = Record.get_record(id_=test_record.id) old_taxonomy = old_record["taxonomy"] assert old_taxonomy == [{ 'is_ancestor': True, 'level': 1, 'links': { 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a' }, 'test': 'extra_data' }, { 'is_ancestor': True, 'level': 2, 'links': { 'parent': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a', 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b' }, 'test': 'extra_data' }, { 'is_ancestor': False, 'level': 3, 'links': { 'parent': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b', 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b/c' }, 'test': 'extra_data' }] ti = TermIdentification(term=terms[2]) current_flask_taxonomies.move_term(ti, new_parent=terms[0], remove_after_delete=False) db.session.commit() new_record = Record.get_record(id_=test_record.id) new_taxonomy = new_record["taxonomy"] new_terms = current_flask_taxonomies.list_taxonomy(taxonomy).all() assert new_terms[-1].parent_id == 1
def taxonomy_move_term(code=None, slug=None, prefer=None, page=None, size=None, destination='', rename='', q=None): """Move term into a new parent or rename it.""" if q: json_abort( 422, { 'message': 'Query not appropriate when moving term', 'reason': 'search-query-not-allowed' }) try: taxonomy = current_flask_taxonomies.get_taxonomy(code) ti = TermIdentification(taxonomy=taxonomy, slug=slug) term = current_flask_taxonomies.filter_term(ti).one() current_flask_taxonomies.permissions.taxonomy_term_move.enforce( request=request, taxonomy=taxonomy, term=term, destination=destination, rename=rename) except NoResultFound as e: return json_abort(404, {}) if destination: if destination.startswith('http'): destination_path = urlparse(destination).path url_prefix = current_app.config['FLASK_TAXONOMIES_URL_PREFIX'] if not destination_path.startswith(url_prefix): abort( 400, 'Destination not part of this server as it ' 'does not start with config.FLASK_TAXONOMIES_URL_PREFIX') destination_path = destination_path[len(url_prefix):] destination_path = destination_path.split('/', maxsplit=1) if len(destination_path) > 1: destination_taxonomy, destination_slug = destination_path else: destination_taxonomy = destination_path[0] destination_slug = None else: destination_taxonomy = code destination_slug = destination if destination_slug.startswith('/'): destination_slug = destination_slug[1:] if not current_flask_taxonomies.filter_term( TermIdentification(taxonomy=code, slug=slug)).count(): abort(404, 'Term %s/%s does not exist' % (code, slug)) try: old_term, new_term = current_flask_taxonomies.move_term( TermIdentification(taxonomy=code, slug=slug), new_parent=TermIdentification(taxonomy=destination_taxonomy, slug=destination_slug) if destination_slug else '', remove_after_delete=False ) # do not remove the original node from the database, # just mark it as deleted except TaxonomyTermBusyError as e: return json_abort(412, {'message': str(e), 'reason': 'term-busy'}) elif rename: new_slug = slug if new_slug.endswith('/'): new_slug = new_slug[:-1] if '/' in new_slug: new_slug = new_slug.rsplit('/')[0] new_slug = new_slug + '/' + rename else: new_slug = rename try: old_term, new_term = current_flask_taxonomies.rename_term( TermIdentification(taxonomy=code, slug=slug), new_slug=new_slug, remove_after_delete=False ) # do not remove the original node from the database, just mark it as deleted except TaxonomyTermBusyError as e: return json_abort(412, {'message': str(e), 'reason': 'term-busy'}) destination_taxonomy = code else: abort(400, 'Pass either `destination` or `rename` parameters ') return # just to make pycharm happy current_flask_taxonomies.commit() return get_taxonomy_term(code=destination_taxonomy, slug=new_term.slug, prefer=prefer, page=page, size=size)
def get_taxonomy_term(code=None, slug=None, prefer=None, page=None, size=None, status_code=200, q=None): try: taxonomy = current_flask_taxonomies.get_taxonomy(code) prefer = taxonomy.merge_select(prefer) current_flask_taxonomies.permissions.taxonomy_term_read.enforce( request=request, taxonomy=taxonomy, slug=slug) if INCLUDE_DELETED in prefer: status_cond = sqlalchemy.sql.true() else: status_cond = TaxonomyTerm.status == TermStatusEnum.alive return_descendants = INCLUDE_DESCENDANTS in prefer if return_descendants: query = current_flask_taxonomies.descendants_or_self( TermIdentification(taxonomy=code, slug=slug), levels=prefer.options.get('levels', None), status_cond=status_cond, return_descendants_count=INCLUDE_DESCENDANTS_COUNT in prefer, return_descendants_busy_count=INCLUDE_STATUS in prefer) else: query = current_flask_taxonomies.filter_term( TermIdentification(taxonomy=code, slug=slug), status_cond=status_cond, return_descendants_count=INCLUDE_DESCENDANTS_COUNT in prefer, return_descendants_busy_count=INCLUDE_STATUS in prefer) if q: query = current_flask_taxonomies.apply_term_query(query, q, code) paginator = Paginator(prefer, query, page if return_descendants else None, size if return_descendants else None, json_converter=lambda data: build_descendants( data, prefer, root_slug=None), allow_empty=INCLUDE_SELF not in prefer, single_result=INCLUDE_SELF in prefer, has_query=q is not None) return paginator.jsonify(status_code=status_code) except NoResultFound: term = current_flask_taxonomies.filter_term( TermIdentification(taxonomy=code, slug=slug), status_cond=sqlalchemy.sql.true()).one_or_none() if not term: json_abort( 404, { "message": "%s was not found on the server" % request.url, "reason": "does-not-exist" }) elif term.obsoleted_by_id: obsoleted_by = term.obsoleted_by obsoleted_by_links = obsoleted_by.links() return Response( json.dumps({ 'links': term.links(representation=prefer).envelope, 'status': 'moved' }), status=301, headers={ 'Location': obsoleted_by_links.headers['self'], 'Link': str( LinkHeader([ Link(v, rel=k) for k, v in term.links( representation=prefer).envelope.items() ])) }, content_type='application/json') else: json_abort( 410, { "message": "%s was not found on the server" % request.url, "reason": "deleted" }) except: traceback.print_exc() raise
def _create_update_taxonomy_term_internal(code, slug, prefer, page, size, extra_data, if_none_match=False, if_match=False): try: taxonomy = current_flask_taxonomies.get_taxonomy(code) prefer = taxonomy.merge_select(prefer) if INCLUDE_DELETED in prefer: status_cond = sqlalchemy.sql.true() else: status_cond = TaxonomyTerm.status == TermStatusEnum.alive slug = '/'.join(slugify(x) for x in slug.split('/')) ti = TermIdentification(taxonomy=code, slug=slug) term = original_term = current_flask_taxonomies.filter_term( ti, status_cond=sqlalchemy.sql.true()).one_or_none() if term and INCLUDE_DELETED not in prefer: if term.status != TermStatusEnum.alive: term = None if if_none_match and term: json_abort( 412, { 'message': 'The taxonomy already contains a term on this slug. ' + 'As If-None-Match: \'*\' has been requested, not modifying the term', 'reason': 'term-exists' }) if if_match and not term: json_abort( 412, { 'message': 'The taxonomy does not contain a term on this slug. ' + 'As If-Match: \'*\' has been requested, not creating a new term', 'reason': 'term-does-not-exist' }) if term: current_flask_taxonomies.permissions.taxonomy_term_update.enforce( request=request, taxonomy=taxonomy, term=term) current_flask_taxonomies.update_term(term, status_cond=status_cond, extra_data=extra_data) status_code = 200 else: if original_term: # there is a deleted term, so return a 409 Conflict json_abort( 409, { 'message': 'The taxonomy already contains a deleted term on this slug. ' 'To reuse the term, repeat the operation with `del` in ' 'representation:include.', 'reason': 'deleted-term-exists' }) current_flask_taxonomies.permissions.taxonomy_term_create.enforce( request=request, taxonomy=taxonomy, slug=slug) current_flask_taxonomies.create_term(ti, extra_data=extra_data) status_code = 201 current_flask_taxonomies.commit() return get_taxonomy_term(code=code, slug=slug, prefer=prefer, page=page, size=size, status_code=status_code) except NoResultFound: json_abort(404, {}) except: traceback.print_exc() raise
def test_taxonomy_term_delete(app, db, taxonomy_tree): taxonomy = current_flask_taxonomies.get_taxonomy("test_taxonomy") terms = current_flask_taxonomies.list_taxonomy(taxonomy).all() term = terms[1] ti = TermIdentification(term=term) current_flask_taxonomies.delete_term(ti)
def test_taxonomy_term_update(app, db, taxonomy_tree, test_record): taxonomy = current_flask_taxonomies.get_taxonomy("test_taxonomy") terms = current_flask_taxonomies.list_taxonomy(taxonomy).all() old_record = Record.get_record(id_=test_record.id) assert old_record == { 'pid': 1, 'taxonomy': [{ 'is_ancestor': True, 'level': 1, 'links': {'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a'}, 'test': 'extra_data' }, { 'is_ancestor': True, 'level': 2, 'links': { 'parent': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a', 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b' }, 'test': 'extra_data' }, { 'is_ancestor': False, 'level': 3, 'links': { 'parent': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b', 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b/c' }, 'test': 'extra_data' }], 'title': 'record 1' } term = terms[-1] current_flask_taxonomies.update_term(term, extra_data={"new_data": "changed extra data"}) new_record = Record.get_record(id_=test_record.id) assert new_record == { 'pid': 1, 'taxonomy': [{ 'is_ancestor': True, 'level': 1, 'links': {'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a'}, 'test': 'extra_data' }, { 'is_ancestor': True, 'level': 2, 'links': { 'parent': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a', 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b' }, 'test': 'extra_data' }, { 'is_ancestor': False, 'level': 3, 'links': { 'parent': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b', 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b/c' }, 'new_data': 'changed extra data', 'test': 'extra_data' }], 'title': 'record 1' }