#!/usr/bin/env python
# vim: set fileencoding=utf-8 :
###############################################################################
# #
# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.web module of the BEAT platform. #
# #
# Commercial License Usage #
# Licensees holding valid commercial BEAT licenses may use this file in #
# accordance with the terms contained in a written agreement between you #
# and Idiap. For further information contact tto@idiap.ch #
# #
# Alternatively, this file may be used under the terms of the GNU Affero #
# Public License version 3 as published by the Free Software and appearing #
# in the file LICENSE.AGPL included in the packaging of this file. #
# The BEAT platform is distributed in the hope that it will be useful, but #
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
# or FITNESS FOR A PARTICULAR PURPOSE. #
# #
# You should have received a copy of the GNU Affero Public License along #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/. #
# #
###############################################################################
from django.db import models
from django.conf import settings
from django.core.urlresolvers import reverse
import beat.core.library
from ..common.storage import OverwriteStorage
from ..common.models import get_contribution_declaration_filename
from ..common.models import get_contribution_description_filename
from ..code.models import Code
from ..code.models import CodeManager
from ..code.models import get_contribution_source_code_filename
import simplejson
import collections
#----------------------------------------------------------
[docs]def validate_library(declaration):
"""Validates the declaration of a library code, returns wrapper"""
if not declaration:
raise SyntaxError('Library declaration cannot be empty')
if not(isinstance(declaration, dict)):
try:
declaration_dict = simplejson.loads(declaration, object_pairs_hook=collections.OrderedDict)
except Exception as e:
raise SyntaxError(str(e))
else:
declaration_dict = collections.OrderedDict(declaration)
library = beat.core.library.Library(settings.PREFIX, declaration_dict)
if not library.valid:
raise SyntaxError('\n * %s' % '\n * '.join(library.errors))
return library
#----------------------------------------------------------
[docs]class LibraryStorage(OverwriteStorage):
def __init__(self, *args, **kwargs):
kwargs['location'] = settings.LIBRARIES_ROOT
super(LibraryStorage, self).__init__(*args, **kwargs)
#----------------------------------------------------------
[docs]class LibraryManager(CodeManager):
[docs] def create_library(self, author, name, short_description='',
description='', declaration=None, code=None, version=1,
previous_version=None, fork_of=None):
default = beat.core.library.Library(settings.PREFIX, data=None)
return self.create_code(author, name, default, short_description,
description, declaration, code, version,
previous_version, fork_of)
#----------------------------------------------------------
[docs]class Library(Code):
#_____ Fields __________
declaration_file = models.FileField(
storage=LibraryStorage(),
upload_to=get_contribution_declaration_filename,
blank=True, null=True,
max_length=200,
db_column='declaration'
)
description_file = models.FileField(
storage=LibraryStorage(),
upload_to=get_contribution_description_filename,
blank=True, null=True,
max_length=200,
db_column='description'
)
source_code_file = models.FileField(
storage=LibraryStorage(),
upload_to=get_contribution_source_code_filename,
blank=True, null=True,
max_length=200,
db_column='source_code'
)
# Read-only parameters that are updated at every save(), if required
referenced_libraries = models.ManyToManyField('self',
blank=True, related_name='referencing',
symmetrical=False)
objects = LibraryManager()
#_____ Meta parameters __________
class Meta(Code.Meta):
verbose_name_plural = 'libraries'
#_____ Utilities __________
[docs] def get_absolute_url(self):
return reverse(
'libraries:view',
args=(self.author.username, self.name, self.version,),
)
[docs] def get_api_update_url(self):
'''Returns the endpoint to update this object'''
return reverse(
'api_libraries:object',
args=(self.author.username, self.name, self.version,),
)
[docs] def get_api_share_url(self):
'''Returns the endpoint to share this object'''
return reverse(
'api_libraries:share',
args=(self.author.username, self.name, self.version,),
)
#_____ Overrides __________
[docs] def save(self, *args, **kwargs):
wrapper = self._save_preprocessing()
super(Library, self).save(*args, **kwargs)
# reset referenced libraries
self.referenced_libraries.clear()
if wrapper.uses is not None:
for l in set(wrapper.uses.values()):
s = beat.core.library.Storage(settings.PREFIX, l)
library = Library.objects.get(author__username=s.username,
name=s.name,
version=s.version,
)
self.referenced_libraries.add(library)
#_____ Methods __________
[docs] def validate(self, declaration):
return validate_library(declaration)
[docs] def modifiable(self):
"""Can modify if nobody points at me"""
return super(Library, self).modifiable() and ((self.referencing.count() + self.used_by_algorithms.count()) == 0)
[docs] def deletable(self):
"""Can delete if nobody points at me"""
return super(Library, self).deletable() and ((self.referencing.count() + self.used_by_algorithms.count()) == 0)
[docs] def valid(self):
return True # A library (at least for now) is always implemented in Python,
# thus always valid
[docs] def core(self):
return validate_library(self.declaration)
[docs] def uses(self):
return self.core().uses
[docs] def environments(self):
'''Calculates environment usage for this library
Returns:
list: mapping environment to usage counts, determining how many times
a given algorithm has been successfuly used on that environment
'''
from django.db.models import Count, Q
from ..experiments.models import Block
from ..experiments.models import Experiment
from ..backend.models import Environment
# Tries to figure through a maximum if using algorithms have been
# successfuly used inside an environment.
# Case 1) The block is part of an experiment that was successful
# Case 2) The block is part of an experiment that is not successful
# (failed or other), but it is CACHED (if not cached, then we can't
# attest anything about the algorithm/environment relationship!)
envs = Environment.objects.filter(blocks__in=Block.objects.filter( \
algorithm__in=self.used_by_algorithms.all()).filter( \
Q(experiment__status=Experiment.DONE) | \
((~Q(experiment__status=Experiment.DONE)) & Q(status=Block.DONE))
)).annotate(itemcount=Count('id')).order_by('-creation_date' \
).distinct()
return [(k, k.itemcount) for k in envs]