Skip to content
Snippets Groups Projects
models.py 62.3 KiB
Newer Older
  • Learn to ignore specific revisions
  •                     total=int(r.headers['Content-Length'])
                    )
                    pbar.clear()
                    with open(model.path, 'wb') as f:
                        for chunk in r.iter_content(chunk_size=1024):
                            if chunk:  # filter out keep-alive new chunks
                                pbar.update(len(chunk))
                                f.write(chunk)
                        pbar.close()
    
            db.session.commit()
    
    
        def delete(self):
            try:
                os.remove(self.path)
            except OSError as e:
                current_app.logger.error(e)
            db.session.delete(self)
    
    
        def to_json_serializeable(self, backrefs=False, relationships=False):
            json_serializeable = {
    
                'id': self.hashid,
                'compatible_service_versions': self.compatible_service_versions,
                'description': self.description,
                'publisher': self.publisher,
                'publisher_url': self.publisher_url,
                'publishing_url': self.publishing_url,
                'publishing_year': self.publishing_year,
    
                'title': self.title,
    
                'version': self.version,
    
                **self.file_mixin_to_json_serializeable()
    
                json_serializeable['user'] = \
                    self.user.to_json_serializeable(backrefs=True)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            if relationships:
                pass
    
            return json_serializeable
    
    
    
    class SpaCyNLPPipelineModel(FileMixin, HashidMixin, db.Model):
        __tablename__ = 'spacy_nlp_pipeline_models'
        # Primary key
        id = db.Column(db.Integer, primary_key=True)
        # Foreign keys
        user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
        # Fields
        title = db.Column(db.String(64))
        description = db.Column(db.String(255))
        version = db.Column(db.String(16))
        compatible_service_versions = db.Column(ContainerColumn(list, 255))
        publisher = db.Column(db.String(128))
        publisher_url = db.Column(db.String(512))
        publishing_url = db.Column(db.String(512))
        publishing_year = db.Column(db.Integer)
    
        pipeline_name = db.Column(db.String(64))
    
        is_public = db.Column(db.Boolean, default=False)
    
        # Relationships
        user = db.relationship('User', back_populates='spacy_nlp_pipeline_models')
    
    
        @property
        def path(self):
            return os.path.join(
                self.user.path,
                'spacy_nlp_pipeline_models',
                str(self.id)
            )
    
    
        @property
        def jsonpatch_path(self):
            return f'{self.user.jsonpatch_path}/spacy_nlp_pipeline_models/{self.hashid}'
    
    
        @property
        def url(self):
            return url_for(
                'contributions.spacy_nlp_pipeline_model',
                spacy_nlp_pipeline_model_id=self.id
            )
    
    
        @property
        def user_hashid(self):
            return self.user.hashid
    
    
        @staticmethod
    
        def insert_defaults(force_download=False):
    
            nopaque_user = User.query.filter_by(username='nopaque').first()
            defaults_file = os.path.join(
                os.path.dirname(os.path.abspath(__file__)),
                'SpaCyNLPPipelineModel.defaults.yml'
            )
            with open(defaults_file, 'r') as f:
                defaults = yaml.safe_load(f)
            for m in defaults:
                model = SpaCyNLPPipelineModel.query.filter_by(title=m['title'], version=m['version']).first()  # noqa
                if model is not None:
                    model.compatible_service_versions = m['compatible_service_versions']
                    model.description = m['description']
    
                    model.filename = m['url'].split('/')[-1]
    
                    model.publisher = m['publisher']
                    model.publisher_url = m['publisher_url']
                    model.publishing_url = m['publishing_url']
                    model.publishing_year = m['publishing_year']
    
                    model.title = m['title']
                    model.version = m['version']
    
                    model.pipeline_name = m['pipeline_name']
    
                else:
                    model = SpaCyNLPPipelineModel(
                        compatible_service_versions=m['compatible_service_versions'],
                        description=m['description'],
                        filename=m['url'].split('/')[-1],
                        publisher=m['publisher'],
                        publisher_url=m['publisher_url'],
                        publishing_url=m['publishing_url'],
                        publishing_year=m['publishing_year'],
                        is_public=True,
                        title=m['title'],
                        user=nopaque_user,
                        version=m['version'],
                        pipeline_name=m['pipeline_name']
                    )
                    db.session.add(model)
                    db.session.flush(objects=[model])
                    db.session.refresh(model)
                if not os.path.exists(model.path) or force_download:
                    r = requests.get(m['url'], stream=True)
                    pbar = tqdm(
                        desc=f'{model.title} ({model.filename})',
                        unit="B",
                        unit_scale=True,
                        unit_divisor=1024,
                        total=int(r.headers['Content-Length'])
                    )
                    pbar.clear()
                    with open(model.path, 'wb') as f:
                        for chunk in r.iter_content(chunk_size=1024):
                            if chunk:  # filter out keep-alive new chunks
                                pbar.update(len(chunk))
                                f.write(chunk)
                        pbar.close()
    
        
        def delete(self):
            try:
                os.remove(self.path)
            except OSError as e:
                current_app.logger.error(e)
            db.session.delete(self)
    
        def to_json_serializeable(self, backrefs=False, relationships=False):
            json_serializeable = {
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'id': self.hashid,
                'compatible_service_versions': self.compatible_service_versions,
                'description': self.description,
                'publisher': self.publisher,
                'publisher_url': self.publisher_url,
                'publishing_url': self.publishing_url,
                'publishing_year': self.publishing_year,
    
                'pipeline_name': self.pipeline_name,
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'title': self.title,
    
                'version': self.version,
    
                **self.file_mixin_to_json_serializeable()
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            }
            if backrefs:
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                json_serializeable['user'] = \
                    self.user.to_json_serializeable(backrefs=True)
            if relationships:
                pass
    
            return json_serializeable
    
    class JobInput(FileMixin, HashidMixin, db.Model):
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        __tablename__ = 'job_inputs'
        # Primary key
        id = db.Column(db.Integer, primary_key=True)
    
        # Foreign keys
        job_id = db.Column(db.Integer, db.ForeignKey('jobs.id'))
    
        # Relationships
        job = db.relationship(
            'Job',
            back_populates='inputs'
        )
    
    
        def __repr__(self):
            return f'<JobInput {self.filename}>'
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        def content_url(self):
    
            return url_for(
                'jobs.download_job_input',
                job_id=self.job.id,
                job_input_id=self.id
            )
    
            return f'{self.job.jsonpatch_path}/inputs/{self.hashid}'
    
            return os.path.join(self.job.path, 'inputs', str(self.id))
    
        @property
        def url(self):
    
            return url_for(
                'jobs.job',
                job_id=self.job_id,
                _anchor=f'job-{self.job.hashid}-input-{self.hashid}'
            )
    
        @property
        def user_hashid(self):
            return self.job.user.hashid
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            return self.job.user.id
    
        def to_json_serializeable(self, backrefs=False, relationships=False):
            json_serializeable = {
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'id': self.hashid,
    
                **self.file_mixin_to_json_serializeable()
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            }
            if backrefs:
    
                json_serializeable['job'] = \
                    self.job.to_json_serializeable(backrefs=True)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            if relationships:
                pass
    
            return json_serializeable
    
    class JobResult(FileMixin, HashidMixin, db.Model):
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        __tablename__ = 'job_results'
        # Primary key
        id = db.Column(db.Integer, primary_key=True)
    
        # Foreign keys
        job_id = db.Column(db.Integer, db.ForeignKey('jobs.id'))
    
        # Relationships
        job = db.relationship(
            'Job',
            back_populates='results'
        )
    
    
        def __repr__(self):
            return f'<JobResult {self.filename}>'
    
        @property
        def download_url(self):
    
            return url_for(
                'jobs.download_job_result',
                job_id=self.job_id,
                job_result_id=self.id
            )
    
            return f'{self.job.jsonpatch_path}/results/{self.hashid}'
    
            return os.path.join(self.job.path, 'results', str(self.id))
    
        @property
        def url(self):
    
            return url_for(
                'jobs.job',
                job_id=self.job_id,
                _anchor=f'job-{self.job.hashid}-result-{self.hashid}'
            )
    
        @property
        def user_hashid(self):
            return self.job.user.hashid
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            return self.job.user.id
    
        def to_json_serializeable(self, backrefs=False, relationships=False):
            json_serializeable = {
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'id': self.hashid,
                'description': self.description,
    
                **self.file_mixin_to_json_serializeable(
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                    backrefs=backrefs,
                    relationships=relationships
                )
            }
            if backrefs:
    
                json_serializeable['job'] = \
                    self.job.to_json_serializeable(backrefs=True)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            if relationships:
                pass
    
            return json_serializeable
    
    class Job(HashidMixin, db.Model):
    
    Stephan Porada's avatar
    Stephan Porada committed
        Class to define Jobs.
    
    Stephan Porada's avatar
    Stephan Porada committed
        __tablename__ = 'jobs'
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        # Primary key
    
    Stephan Porada's avatar
    Stephan Porada committed
        id = db.Column(db.Integer, primary_key=True)
    
        # Foreign keys
        user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    
    Stephan Porada's avatar
    Stephan Porada committed
        # Fields
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        creation_date = \
            db.Column(db.DateTime(), default=datetime.utcnow)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        description = db.Column(db.String(255))
    
        end_date = db.Column(db.DateTime())
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        service = db.Column(db.String(64))
    
        service_args = db.Column(ContainerColumn(dict, 255))
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        service_version = db.Column(db.String(16))
    
            IntEnumColumn(JobStatus),
    
            default=JobStatus.INITIALIZING
        )
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        title = db.Column(db.String(32))
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        # Relationships
    
        inputs = db.relationship(
            'JobInput',
    
            back_populates='job',
    
            cascade='all, delete-orphan',
            lazy='dynamic'
        )
        results = db.relationship(
            'JobResult',
    
            back_populates='job',
    
            cascade='all, delete-orphan',
            lazy='dynamic'
        )
    
        user = db.relationship(
            'User',
            back_populates='jobs'
        )
    
    
        def __repr__(self):
            return f'<Job {self.title}>'
    
            return f'{self.user.jsonpatch_path}/jobs/{self.hashid}'
    
            return os.path.join(self.user.path, 'jobs', str(self.id))
    
    Stephan Porada's avatar
    Stephan Porada committed
    
    
        def url(self):
            return url_for('jobs.job', job_id=self.id)
    
        @property
        def user_hashid(self):
            return self.user.hashid
    
    Stephan Porada's avatar
    Stephan Porada committed
    
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        @staticmethod
        def create(**kwargs):
            job = Job(**kwargs)
            db.session.add(job)
            db.session.flush(objects=[job])
            db.session.refresh(job)
            try:
                os.mkdir(job.path)
                os.mkdir(os.path.join(job.path, 'inputs'))
                os.mkdir(os.path.join(job.path, 'pipeline_data'))
                os.mkdir(os.path.join(job.path, 'results'))
            except OSError as e:
                current_app.logger.error(e)
                db.session.rollback()
                raise e
            return job
    
    
        def delete(self):
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            ''' Delete the job and its inputs and results from the database. '''
    
            if self.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:  # noqa
                self.status = JobStatus.CANCELING
    
                    # In case the daemon handled a job in any way
    
                    if self.status != JobStatus.CANCELING:
                        self.status = JobStatus.CANCELING
    
                        db.session.commit()
                    sleep(1)
                    db.session.refresh(self)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            try:
                shutil.rmtree(self.path)
            except OSError as e:
                current_app.logger.error(e)
                db.session.rollback()
                raise e
    
            db.session.delete(self)
    
    
        def restart(self):
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            ''' Restart a job - only if the status is failed '''
            if self.status != JobStatus.FAILED:
                raise Exception('Job status is not "failed"')
    
            shutil.rmtree(os.path.join(self.path, 'results'), ignore_errors=True)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            shutil.rmtree(os.path.join(self.path, 'pyflow.data'), ignore_errors=True)
    
            for result in self.results:
                db.session.delete(result)
    
            self.end_date = None
    
        def to_json_serializeable(self, backrefs=False, relationships=False):
            json_serializeable = {
    
                'id': self.hashid,
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'creation_date': f'{self.creation_date.isoformat()}Z',
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'end_date': (
                    None if self.end_date is None
                    else f'{self.end_date.isoformat()}Z'
                ),
    
                'service_version': self.service_version,
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'title': self.title
    
            if backrefs:
    
                json_serializeable['user'] = \
                    self.user.to_json_serializeable(backrefs=True)
    
            if relationships:
    
                json_serializeable['inputs'] = {
                    x.hashid: x.to_json_serializeable(relationships=True)
    
                    for x in self.inputs
                }
    
                json_serializeable['results'] = {
                    x.hashid: x.to_json_serializeable(relationships=True)
    
                    for x in self.results
                }
    
            return json_serializeable
    
    Stephan Porada's avatar
    Stephan Porada committed
    
    
    class CorpusFile(FileMixin, HashidMixin, db.Model):
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        __tablename__ = 'corpus_files'
        # Primary key
        id = db.Column(db.Integer, primary_key=True)
    
        # Foreign keys
        corpus_id = db.Column(db.Integer, db.ForeignKey('corpora.id'))
    
    Stephan Porada's avatar
    Stephan Porada committed
        # Fields
    
        author = db.Column(db.String(255))
    
        description = db.Column(db.String(255))
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        publishing_year = db.Column(db.Integer)
        title = db.Column(db.String(255))
        address = db.Column(db.String(255))
    
        booktitle = db.Column(db.String(255))
        chapter = db.Column(db.String(255))
        editor = db.Column(db.String(255))
        institution = db.Column(db.String(255))
        journal = db.Column(db.String(255))
        pages = db.Column(db.String(255))
        publisher = db.Column(db.String(255))
        school = db.Column(db.String(255))
    
        # Relationships
        corpus = db.relationship(
            'Corpus',
            back_populates='files'
        )
    
        @property
        def download_url(self):
    
            return url_for(
                'corpora.download_corpus_file',
                corpus_id=self.corpus_id,
                corpus_file_id=self.id
            )
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            return f'{self.corpus.jsonpatch_path}/files/{self.hashid}'
    
            return os.path.join(self.corpus.path, 'files', str(self.id))
    
        @property
        def url(self):
    
            return url_for(
                'corpora.corpus_file',
                corpus_id=self.corpus_id,
                corpus_file_id=self.id
            )
    
        @property
        def user_hashid(self):
            return self.corpus.user.hashid
    
        @property
        def user_id(self):
            return self.corpus.user_id
    
    
        def delete(self):
    
            except OSError as e:
                current_app.logger.error(e)
    
            db.session.delete(self)
    
            self.corpus.status = CorpusStatus.UNPREPARED
    
        def to_json_serializeable(self, backrefs=False, relationships=False):
            json_serializeable = {
    
                'id': self.hashid,
                'address': self.address,
                'author': self.author,
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'description': self.description,
    
                'booktitle': self.booktitle,
                'chapter': self.chapter,
                'editor': self.editor,
                'institution': self.institution,
                'journal': self.journal,
                'pages': self.pages,
                'publisher': self.publisher,
                'publishing_year': self.publishing_year,
                'school': self.school,
                'title': self.title,
    
                **self.file_mixin_to_json_serializeable(
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                    backrefs=backrefs,
                    relationships=relationships
                )
    
            }
            if backrefs:
    
                json_serializeable['corpus'] = \
                    self.corpus.to_json_serializeable(backrefs=True)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            if relationships:
                pass
    
            return json_serializeable
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
    
    
    class Corpus(HashidMixin, db.Model):
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        Class to define a corpus.
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        __tablename__ = 'corpora'
        # Primary key
        id = db.Column(db.Integer, primary_key=True)
    
        # Foreign keys
        user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    
    Stephan Porada's avatar
    Stephan Porada committed
        # Fields
    
        creation_date = db.Column(db.DateTime(), default=datetime.utcnow)
    
        description = db.Column(db.String(255))
    
            IntEnumColumn(CorpusStatus),
    
            default=CorpusStatus.UNPREPARED
        )
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        title = db.Column(db.String(32))
    
        num_analysis_sessions = db.Column(db.Integer, default=0)
        num_tokens = db.Column(db.Integer, default=0)
    
        is_public = db.Column(db.Boolean, default=False)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        # Relationships
    
        files = db.relationship(
            'CorpusFile',
    
            back_populates='corpus',
    
            lazy='dynamic',
            cascade='all, delete-orphan'
        )
    
        corpus_follower_associations = db.relationship(
    
            'CorpusFollowerAssociation',
    
            back_populates='corpus',
    
            cascade='all, delete-orphan'
    
        followers = association_proxy(
            'corpus_follower_associations',
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
            'follower',
            creator=lambda u: CorpusFollowerAssociation(follower=u)
    
        )
        user = db.relationship('User', back_populates='corpora')
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        max_num_tokens = 2_147_483_647
    
        def __repr__(self):
            return f'<Corpus {self.title}>'
    
    
        @property
        def analysis_url(self):
    
    Inga Kirschnick's avatar
    Inga Kirschnick committed
            return url_for('corpora.analysis', corpus_id=self.id)
    
            return f'{self.user.jsonpatch_path}/corpora/{self.hashid}'
    
            return os.path.join(self.user.path, 'corpora', str(self.id))
    
        @property
        def url(self):
            return url_for('corpora.corpus', corpus_id=self.id)
    
    
        @property
        def user_hashid(self):
            return self.user.hashid
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
        @staticmethod
        def create(**kwargs):
            corpus = Corpus(**kwargs)
            db.session.add(corpus)
            db.session.flush(objects=[corpus])
            db.session.refresh(corpus)
            try:
                os.mkdir(corpus.path)
                os.mkdir(os.path.join(corpus.path, 'files'))
                os.mkdir(os.path.join(corpus.path, 'cwb'))
                os.mkdir(os.path.join(corpus.path, 'cwb', 'data'))
                os.mkdir(os.path.join(corpus.path, 'cwb', 'registry'))
            except OSError as e:
                current_app.logger.error(e)
                db.session.rollback()
                raise e
            return corpus
    
    
        def build(self):
    
            build_dir = os.path.join(self.path, 'cwb')
            shutil.rmtree(build_dir, ignore_errors=True)
            os.mkdir(build_dir)
            os.mkdir(os.path.join(build_dir, 'data'))
            os.mkdir(os.path.join(build_dir, 'registry'))
    
            corpus_element = ET.fromstring('<corpus>\n</corpus>')
    
            for corpus_file in self.files:
    
                normalized_vrt_path = os.path.join(build_dir, f'{corpus_file.id}.norm.vrt')
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                try:
                    normalize_vrt_file(corpus_file.path, normalized_vrt_path)
                except:
                    self.status = CorpusStatus.FAILED
                    return
                element_tree = ET.parse(normalized_vrt_path)
    
                text_element = element_tree.getroot()
                text_element.set('author', corpus_file.author)
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                text_element.set('title', corpus_file.title)
                text_element.set(
                    'publishing_year',
                    f'{corpus_file.publishing_year}'
                )
                text_element.set('address', corpus_file.address or 'NULL')
    
                text_element.set('booktitle', corpus_file.booktitle or 'NULL')
                text_element.set('chapter', corpus_file.chapter or 'NULL')
                text_element.set('editor', corpus_file.editor or 'NULL')
                text_element.set('institution', corpus_file.institution or 'NULL')
                text_element.set('journal', corpus_file.journal or 'NULL')
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                text_element.set('pages', f'{corpus_file.pages}' or 'NULL')
    
                text_element.set('publisher', corpus_file.publisher or 'NULL')
                text_element.set('school', corpus_file.school or 'NULL')
    
                text_element.tail = '\n'
                # corpus_element.insert(1, text_element)
                corpus_element.append(text_element)
    
        def delete(self):
    
            shutil.rmtree(self.path, ignore_errors=True)
    
        def to_json_serializeable(self, backrefs=False, relationships=False):
            json_serializeable = {
    
                'id': self.hashid,
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
                'creation_date': f'{self.creation_date.isoformat()}Z',
    
                'description': self.description,
                'max_num_tokens': self.max_num_tokens,
                'num_analysis_sessions': self.num_analysis_sessions,
                'num_tokens': self.num_tokens,
    
                'title': self.title,
                'is_public': self.is_public
    
            }
            if backrefs:
    
                json_serializeable['user'] = \
                    self.user.to_json_serializeable(backrefs=True)
    
            if relationships:
    
                json_serializeable['corpus_follower_associations'] = {
    
                    x.hashid: x.to_json_serializeable()
    
                    for x in self.corpus_follower_associations
    
                json_serializeable['files'] = {
                    x.hashid: x.to_json_serializeable(relationships=True)
    
                    for x in self.files
                }
    
            return json_serializeable
    
    # endregion models
    
    
    ##############################################################################
    # event_handlers                                                             #
    ##############################################################################
    # region event_handlers
    @db.event.listens_for(Corpus, 'after_delete')
    @db.event.listens_for(CorpusFile, 'after_delete')
    @db.event.listens_for(Job, 'after_delete')
    @db.event.listens_for(JobInput, 'after_delete')
    @db.event.listens_for(JobResult, 'after_delete')
    
    @db.event.listens_for(SpaCyNLPPipelineModel, 'after_delete')
    @db.event.listens_for(TesseractOCRPipelineModel, 'after_delete')
    
    def resource_after_delete(mapper, connection, resource):
        jsonpatch = [
            {
                'op': 'remove',
                'path': resource.jsonpatch_path
            }
        ]
        room = f'/users/{resource.user_hashid}'
    
        socketio.emit('PATCH', jsonpatch, room=room)
    
    @db.event.listens_for(CorpusFollowerAssociation, 'after_delete')
    
    def cfa_after_delete_handler(mapper, connection, cfa):
        jsonpatch_path = f'/users/{cfa.corpus.user.hashid}/corpora/{cfa.corpus.hashid}/corpus_follower_associations/{cfa.hashid}'
        jsonpatch = [
            {
                'op': 'remove',
                'path': jsonpatch_path
            }
    
        room = f'/users/{cfa.corpus.user.hashid}'
        socketio.emit('PATCH', jsonpatch, room=room)
    
    @db.event.listens_for(Corpus, 'after_insert')
    @db.event.listens_for(CorpusFile, 'after_insert')
    @db.event.listens_for(Job, 'after_insert')
    @db.event.listens_for(JobInput, 'after_insert')
    @db.event.listens_for(JobResult, 'after_insert')
    
    @db.event.listens_for(SpaCyNLPPipelineModel, 'after_insert')
    @db.event.listens_for(TesseractOCRPipelineModel, 'after_insert')
    
    def resource_after_insert_handler(mapper, connection, resource):
        jsonpatch_value = resource.to_json_serializeable()
    
        for attr in mapper.relationships:
    
            jsonpatch_value[attr.key] = {}
    
            {
                'op': 'add',
                'path': resource.jsonpatch_path,
                'value': jsonpatch_value
            }
    
        room = f'/users/{resource.user_hashid}'
    
        socketio.emit('PATCH', jsonpatch, room=room)
    
    @db.event.listens_for(CorpusFollowerAssociation, 'after_insert')
    
    def cfa_after_insert_handler(mapper, connection, cfa):
        jsonpatch_value = cfa.to_json_serializeable()
        jsonpatch_path = f'/users/{cfa.corpus.user.hashid}/corpora/{cfa.corpus.hashid}/corpus_follower_associations/{cfa.hashid}'
        jsonpatch = [
            {
                'op': 'add',
                'path': jsonpatch_path,
                'value': jsonpatch_value
            }
    
        room = f'/users/{cfa.corpus.user.hashid}'
        socketio.emit('PATCH', jsonpatch, room=room)
    
    @db.event.listens_for(Corpus, 'after_update')
    @db.event.listens_for(CorpusFile, 'after_update')
    @db.event.listens_for(Job, 'after_update')
    @db.event.listens_for(JobInput, 'after_update')
    @db.event.listens_for(JobResult, 'after_update')
    
    @db.event.listens_for(SpaCyNLPPipelineModel, 'after_update')
    @db.event.listens_for(TesseractOCRPipelineModel, 'after_update')
    
    def resource_after_update_handler(mapper, connection, resource):
    
        for attr in db.inspect(resource).attrs:
    
            if attr.key in mapper.relationships:
                continue
            if not attr.load_history().has_changes():
                continue
    
            jsonpatch_path = f'{resource.jsonpatch_path}/{attr.key}'
    
            if isinstance(attr.value, datetime):
    
                jsonpatch_value = f'{attr.value.isoformat()}Z'
    
            elif isinstance(attr.value, Enum):
    
                jsonpatch_value = attr.value.name
    
                jsonpatch_value = attr.value
    
                    'path': jsonpatch_path,
                    'value': jsonpatch_value
    
            room = f'/users/{resource.user_hashid}'
    
            socketio.emit('PATCH', jsonpatch, room=room)
    
    
    
    @db.event.listens_for(Job, 'after_update')
    def job_after_update_handler(mapper, connection, job):
        for attr in db.inspect(job).attrs:
            if attr.key != 'status':
                continue
    
            if not attr.load_history().has_changes():
                return
    
            if job.user.setting_job_status_mail_notification_level == UserSettingJobStatusMailNotificationLevel.NONE:
                return
            if job.user.setting_job_status_mail_notification_level == UserSettingJobStatusMailNotificationLevel.END:
                if job.status not in [JobStatus.COMPLETED, JobStatus.FAILED]:
                    return
            msg = create_message(
                job.user.email,
                f'Status update for your Job "{job.title}"',
                'tasks/email/notification',
                job=job
            )
            mail.send(msg)
    # endregion event_handlers
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
    
    
    Patrick Jentsch's avatar
    Patrick Jentsch committed
    
    
    ##############################################################################
    # misc                                                                       #
    ##############################################################################
    # region misc
    
    def load_user(user_id):
        return User.query.get(int(user_id))