from urllib.parse import urlencode, quote from flask import request from api.api_errors import BadRequestError, NotFoundError from tracker import artists from tracker.spidering import spider from tracker.spidering.spider import _source_infos from tracker.utils import null_safe_path from . import AuthenticatedResource, Resource from tracker.db import Session as db import tracker.db as tdb from .utils import execute_to_dicts from datetime import datetime, timedelta from .. import models class Artist(AuthenticatedResource): def post(self, source, id): # Spider! three_hours_ago = datetime.today() - timedelta(hours=3) if source == "a": artist = db.query(tdb.Artist).get(id) else: artist = self._get_artist_for_source_and_id(id, source) if artist and artist.last_spidered and artist.last_spidered > three_hours_ago: return self.get(source, id) if source == "a": (other_source, other_id) = db.execute( """ select source, identifier from artist_associations where arid=:arid """, params=dict(arid=id), ).fetchone() spider.spider_by_id(other_source, other_id) return self.get(source, id) else: try: spider.spider_by_id(source, id) except spider.SpiderException as e: db.rollback() artist = self._get_artist_for_source_and_id(id, source) if not artist: # ugly situation, the spider didn't work, we should create an arid so that it can be manually # edited artist_mixin: tdb.ArtistMixin = spider.get_object_for_source_and_id(id, source) from tracker.artists import create_single_artist_record artist = create_single_artist_record(artist_mixin) return self.get("a", artist.arid) def _get_artist_for_source_and_id(self, id, source): artist = ( db.query(tdb.Artist) .join(tdb.ArtistAssociation, tdb.ArtistAssociation.arid == tdb.Artist.arid) .filter( tdb.ArtistAssociation.source == source, tdb.ArtistAssociation.identifier == id, ) .first() ) return artist def get(self, source, id): artist = models.full_artist( source, id, self.user, include_alerts=True, include_scouts=True, include_media=True, include_stats=False, include_brains=False, ) if not artist: raise NotFoundError(f'No artist {source}/{id} found') return artist class ArtistBrains(AuthenticatedResource): def get(self, source, id): artist = models.full_artist( source, id, self.user, include_alerts=False, include_scouts=False, include_media=False, include_stats=False, include_brains=True, use_new_setf=request.args.get('ver') == 'new_setf', ) if not artist: raise NotFoundError(f'No artist {source}/{id} found') return artist class ManageArtistSources(AuthenticatedResource): def put(self, arid): source = request.json["source"] url = request.json["source_url"] new_id = request.json["source_id"] all_associations = ( db.query(tdb.ArtistAssociation).filter(tdb.ArtistAssociation.arid == arid).all() ) if not all_associations: raise BadRequestError("Artist arid:{} does not exist".format(arid)) this_source_record = self._get_or_create_record_for_source(new_id, source, url) if not this_source_record: raise BadRequestError("Could not create a record for {}/{}".format(source, url)) if this_source_record.arid == arid: # This is fine, just a noop in PUT land return render_basic_artist(this_source_record.artist) this_artists_existing_association = ( db.query(tdb.ArtistAssociation) .filter( tdb.ArtistAssociation.arid == arid, tdb.ArtistAssociation.source == source, tdb.ArtistAssociation.identifier != str(this_source_record.identifier), ) .first() ) if this_artists_existing_association: # we don't support multiple sources currently, so first we want to remove # this association db.delete(this_artists_existing_association) db.flush() any_other_source_record = [a for a in all_associations if a.source != source][ 0 ].artist_mixin artists.create_association_by_ids( **{ any_other_source_record.id_field: any_other_source_record.identifier, this_source_record.id_field: this_source_record.identifier, } ) db.commit() return render_basic_artist(db.query(tdb.Artist).get(arid)) def _get_or_create_record_for_source(self, new_id, source, url): if source == "tw": # shit, the twitter spider often uses eid, but we'll be sending twitter_id existing_source_record = ( db.query(tdb.TrackedEntity).filter_by(twitter_id=new_id).first() ) else: existing_source_record = spider.get_object_for_source_and_id(new_id, source) if not existing_source_record: creator = spider.InArtistInfoSource(True) if source == 'in' else _source_infos[source] existing_source_record = creator.find_on_the_web_and_create_record([url], '', '') return existing_source_record def render_basic_artist(artist: tdb.Artist): # todo: replace with setf_artist_source query def _association_to_source_dict(artist_association): return { "source": artist_association.source, "id": artist_association.identifier, "arid": artist_association.arid, "source_url": artist_association.artist_mixin.get_source_url(), } def render_association_links(arid): artist_associations = ( db.query(tdb.ArtistAssociation).filter(tdb.ArtistAssociation.arid == arid).all() ) links = [_association_to_source_dict(aa) for aa in artist_associations] return links return { "arid": artist.arid, "links": render_association_links(artist.arid), "lastSpidered": artist.last_spidered, } class ArtistManualCache(AuthenticatedResource): def post(self): source = request.json['source'] ids = request.json['ids'] if request.args.get('threaded'): from api import cache_model cache_model.executor.submit(cache_model.cache_artists, source, ids) return dict(status='PENDING') else: from api import cache_model cache_model.cache_artists(source, ids) return dict(status="DONE") def delete(self): source = request.json['source'] ids = request.json['ids'] from api import cache_model cache_model.cacher.del_artists(['{}/{}'.format(source, i) for i in ids]) return dict(status="OK") class ArtistByIds(AuthenticatedResource): def post(self): if request.json.get('keys'): source = request.json.get('keys')[0].split('/')[0] ids = [k.split('/')[1] for k in request.json.get('keys')] else: source = request.json['source'] ids = [str(i) for i in request.json['ids']] kwargs = {k: bool(v) for k, v in request.args.items() if k in ( 'include_alerts', 'include_media', 'include_stats', 'include_scouts', 'include_source_data', 'only_alerts_for_today', 'use_cache_only', 'log_explain_analyze', )} if request.args.get('profile_chunk_size'): kwargs['profile_chunk_size'] = int(request.args['profile_chunk_size']) if request.args.get('extra_data_chunk_size'): kwargs['extra_data_chunk_size'] = int(request.args['extra_data_chunk_size']) return {"artists": list(models.artists(source, ids, **kwargs))}