Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
beat
beat.editor
Commits
7749fc21
Commit
7749fc21
authored
Jul 02, 2019
by
Flavio TARSETTI
Browse files
Merge branch '225_check_prefix_on_startup' into 'v2'
Check prefix on startup See merge request
!98
parents
2db741c9
f71556e6
Pipeline
#31608
passed with stage
in 10 minutes and 17 seconds
Changes
16
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
beat/editor/decorators.py
0 → 100644
View file @
7749fc21
#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
# #
# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.editor 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/. #
# #
###############################################################################
"""
Decorators
"""
from
functools
import
wraps
def
frozen
(
cls
):
"""
Don't allow new attributes to be added outside of init
Based on https://stackoverflow.com/a/29368642/5843716
"""
cls
.
__frozen
=
False
def
frozensetattr
(
self
,
key
,
value
):
"""Don't allow attributes to be added outside of __init__"""
if
self
.
__frozen
and
not
hasattr
(
self
,
key
):
raise
RuntimeError
(
"Class {} is frozen. Cannot set {} = {}"
.
format
(
cls
.
__name__
,
key
,
value
)
)
else
:
object
.
__setattr__
(
self
,
key
,
value
)
def
init_decorator
(
func
):
@
wraps
(
func
)
def
wrapper
(
self
,
*
args
,
**
kwargs
):
func
(
self
,
*
args
,
**
kwargs
)
self
.
__frozen
=
True
return
wrapper
cls
.
__setattr__
=
frozensetattr
cls
.
__init__
=
init_decorator
(
cls
.
__init__
)
return
cls
beat/editor/scripts/editor_cli.py
View file @
7749fc21
...
...
@@ -46,6 +46,8 @@ from beat.cmdline.decorators import raise_on_error
from
beat.cmdline.decorators
import
verbosity_option
from
..utils
import
setup_logger
from
..utils
import
check_prefix_folders
from
..utils
import
check_prefix_dataformats
from
..widgets.mainwindow
import
MainWindow
from
..widgets.assetwidget
import
AssetWidget
from
..backend.asset
import
AssetType
...
...
@@ -73,6 +75,14 @@ def setup_environment_cache(ctx, param, value):
dump_environments
(
environments
)
def
check_prefix
(
prefix_path
):
"""Check that the prefix is usable"""
folder_status
,
_
=
check_prefix_folders
(
prefix_path
)
dataformat_status
,
_
=
check_prefix_dataformats
(
prefix_path
)
return
folder_status
and
dataformat_status
refresh_environment_cache_flag
=
click
.
option
(
"--no-check-env"
,
is_flag
=
True
,
...
...
@@ -130,6 +140,11 @@ def start(ctx):
"""Start the beat editor"""
app
=
QApplication
(
sys
.
argv
)
config
=
ctx
.
meta
[
"config"
]
if
not
check_prefix
(
config
.
prefix
):
return
app
.
installEventFilter
(
MouseWheelFilter
(
app
))
mainwindow
=
MainWindow
()
mainwindow
.
set_context
(
ctx
)
...
...
beat/editor/test/test_decorators.py
0 → 100644
View file @
7749fc21
# vim: set fileencoding=utf-8 :
###############################################################################
# #
# Copyright (c) 2019 Idiap Research Institute, http://www.idiap.ch/ #
# Contact: beat.support@idiap.ch #
# #
# This file is part of the beat.editor 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/. #
# #
###############################################################################
import
pytest
from
..decorators
import
frozen
def
test_frozen
():
@
frozen
class
FrozenClass
:
pass
frozen_cls_instance
=
FrozenClass
()
with
pytest
.
raises
(
RuntimeError
):
frozen_cls_instance
.
value
=
None
beat/editor/test/test_utils.py
View file @
7749fc21
...
...
@@ -30,37 +30,87 @@ Test the utils.py file
"""
import
os
import
tempfile
import
pkg_resources
from
PyQt5
import
QtCore
from
PyQt5.QtWidgets
import
QMessageBox
from
..backend.asset
import
AssetType
from
..
import
utils
DATA_PATH
=
pkg_resources
.
resource_filename
(
"beat.editor.test"
,
"reference_data"
)
def
test_Qt_version_equal
():
assert
utils
.
is_Qt_equal_or_higher
(
QtCore
.
QT_VERSION_STR
)
def
test_Qt_version_higher
():
assert
utils
.
is_Qt_equal_or_higher
(
"4.8.7"
)
def
test_Qt_version_smaller
():
assert
not
utils
.
is_Qt_equal_or_higher
(
"12.0.0"
)
def
test_check_prefix_folders_yes
(
monkeypatch
):
monkeypatch
.
setattr
(
QMessageBox
,
"question"
,
lambda
*
args
:
QMessageBox
.
Yes
)
with
tempfile
.
TemporaryDirectory
()
as
prefix_folder
:
result
,
modified
=
utils
.
check_prefix_folders
(
prefix_folder
)
assert
result
assert
modified
for
asset_type
in
AssetType
:
if
asset_type
==
AssetType
.
UNKNOWN
:
continue
assert
os
.
path
.
exists
(
os
.
path
.
join
(
prefix_folder
,
asset_type
.
path
))
result
,
modified
=
utils
.
check_prefix_folders
(
prefix_folder
)
assert
result
assert
not
modified
def
test_check_prefix_folders_no
(
monkeypatch
):
monkeypatch
.
setattr
(
QMessageBox
,
"question"
,
lambda
*
args
:
QMessageBox
.
No
)
with
tempfile
.
TemporaryDirectory
()
as
prefix_folder
:
result
,
modified
=
utils
.
check_prefix_folders
(
prefix_folder
)
assert
not
result
assert
not
modified
assert
os
.
listdir
(
prefix_folder
)
==
[]
def
test_check_prefix_folders_with_prefix
(
monkeypatch
,
test_prefix
):
result
,
modified
=
utils
.
check_prefix_folders
(
test_prefix
)
assert
result
assert
not
modified
def
compare_with_reference
(
generated
,
reference_file_name
):
""" Compare the given generated code with the content of the reference file
"""
with
open
(
os
.
path
.
join
(
DATA_PATH
,
reference_file_name
))
as
reference_file
:
reference
=
reference_file
.
read
()
return
generated
==
reference
def
test_check_prefix_dataformats_yes
(
monkeypatch
):
monkeypatch
.
setattr
(
QMessageBox
,
"question"
,
lambda
*
args
:
QMessageBox
.
Yes
)
with
tempfile
.
TemporaryDirectory
()
as
prefix_folder
:
result
,
modified
=
utils
.
check_prefix_dataformats
(
prefix_folder
)
assert
result
assert
modified
asset_type
=
AssetType
.
DATAFORMAT
assert
os
.
path
.
exists
(
os
.
path
.
join
(
prefix_folder
,
asset_type
.
path
,
"user"
,
"integers"
)
)
def
test_generate_empty_database
():
database
=
utils
.
generate_database
()
assert
compare_with_reference
(
database
,
"empty_database.py"
)
result
,
modified
=
utils
.
check_prefix_dataformats
(
prefix_folder
)
assert
result
assert
not
modified
def
test_
generate_empty_algorithm
(
):
alg
=
{
"name"
:
"user/alg/1"
,
"contents"
:
{
"splittable"
:
True
,
"groups"
:
[],
"uses"
:
{}},
}
algorithm
=
utils
.
generate_algorithm
(
alg
[
"contents"
])
assert
compare_with_reference
(
algorithm
,
"empty_algorithm.py"
)
def
test_
check_prefix_dataformats_no
(
monkeypatch
):
monkeypatch
.
setattr
(
QMessageBox
,
"question"
,
lambda
*
args
:
QMessageBox
.
No
)
with
tempfile
.
TemporaryDirectory
()
as
prefix_folder
:
result
,
modified
=
utils
.
check_prefix_dataformats
(
prefix_folder
)
assert
not
result
assert
not
modified
assert
os
.
listdir
(
prefix_folder
)
==
[]
def
test_generate_empty_library
():
library
=
utils
.
generate_library
()
assert
compare_with_reference
(
library
,
"empty_library.py"
)
def
test_check_prefix_dataformats_with_prefix
(
monkeypatch
,
test_prefix
):
result
,
modified
=
utils
.
check_prefix_dataformats
(
test_prefix
)
assert
result
assert
not
modified
beat/editor/utils.py
View file @
7749fc21
...
...
@@ -34,147 +34,19 @@ import sys
import
logging
import
simplejson
as
json
import
pkg_resources
import
shutil
import
jinja2
from
functools
import
wraps
from
packaging
import
version
from
PyQt5
import
QtCore
logger
=
logging
.
getLogger
(
__name__
)
# Jinja2 environment for loading our templates
ENV
=
jinja2
.
Environment
(
loader
=
jinja2
.
PackageLoader
(
__name__
,
"templates"
),
autoescape
=
True
,
keep_trailing_newline
=
True
,
)
def
generate_database
(
views
=
None
):
"""Generates a valid BEAT database from our stored template
Parameters:
views (:py:class:`list`, Optional): A list of strings that represents the
views for the database
Returns:
str: The rendered template as a string
"""
views
=
views
or
[
"View"
]
template
=
ENV
.
get_template
(
"database.jinja2"
)
return
template
.
render
(
views
=
views
)
def
generate_library
(
uses
=
None
):
"""Generates a valid BEAT library from our stored template
Parameters:
uses (:py:class:`dict`, Optional): A dict of other libraries that the
library uses. Keys are the value to reference the library, values are
the library being referenced.
Returns:
str: The rendered template as a string
"""
uses
=
uses
or
{}
template
=
ENV
.
get_template
(
"library.jinja2"
)
return
template
.
render
(
uses
=
uses
)
def
generate_algorithm
(
contents
):
"""Generates a valid BEAT algorithm from our stored template
Parameters:
contents (:py:class:`dict`): The algorithm's JSON metadata
Returns:
str: The rendered template as a string
"""
template
=
ENV
.
get_template
(
"algorithm.jinja2"
)
return
template
.
render
(
contents
=
contents
)
def
generate_plotter
(
uses
):
"""Generates a valid BEAT plotter from our stored template
Parameters:
contents (:py:class:`dict`): The plotter's JSON metadata
Returns:
str: The rendered template as a string
"""
uses
=
uses
or
{}
template
=
ENV
.
get_template
(
"plotter.jinja2"
)
return
template
.
render
(
uses
=
uses
)
TEMPLATE_FUNCTION
=
dict
(
databases
=
generate_database
,
libraries
=
generate_library
,
algorithms
=
generate_algorithm
,
plotters
=
generate_plotter
,
)
class
PythonFileAlreadyExistsError
(
Exception
):
pass
# Functions for template instantiation within beat.editor
def
generate_python_template
(
entity
,
name
,
confirm
,
config
,
**
kwargs
):
"""Generates a template for a BEAT entity with the given named arguments
Parameters:
entity (str): A valid BEAT entity
name (str): The name of the object to have a python file generated for
confirm (:py:class:`boolean`): Whether to override the Python file if
one is found at the desired location
"""
resource_path
=
os
.
path
.
join
(
config
.
path
,
entity
)
file_path
=
os
.
path
.
join
(
resource_path
,
name
)
+
".py"
if
not
confirm
and
os
.
path
.
isfile
(
file_path
):
# python file already exists
raise
PythonFileAlreadyExistsError
from
PyQt5
import
QtCore
from
PyQt5.QtCore
import
QCoreApplication
from
PyQt5.QtWidgets
import
QMessageBox
s
=
TEMPLATE_FUNCTION
[
entity
](
**
kwargs
)
from
beat.core.algorithm
import
load_algorithm_prototype
with
open
(
file_path
,
"w"
)
as
f
:
f
.
write
(
s
)
from
.backend.asset
import
AssetType
return
s
logger
=
logging
.
getLogger
(
__name__
)
def
setup_logger
(
name
,
verbosity
):
...
...
@@ -224,40 +96,6 @@ def setup_logger(name, verbosity):
return
logger
def
frozen
(
cls
):
"""
Don't allow new attributes to be added outside of init
Based on https://stackoverflow.com/a/29368642/5843716
"""
cls
.
__frozen
=
False
def
frozensetattr
(
self
,
key
,
value
):
"""Don't allow attributes to be added outside of __init__"""
if
self
.
__frozen
and
not
hasattr
(
self
,
key
):
print
(
"Class {} is frozen. Cannot set {} = {}"
.
format
(
cls
.
__name__
,
key
,
value
)
)
else
:
object
.
__setattr__
(
self
,
key
,
value
)
def
init_decorator
(
func
):
@
wraps
(
func
)
def
wrapper
(
self
,
*
args
,
**
kwargs
):
func
(
self
,
*
args
,
**
kwargs
)
self
.
__frozen
=
True
return
wrapper
cls
.
__setattr__
=
frozensetattr
cls
.
__init__
=
init_decorator
(
cls
.
__init__
)
return
cls
def
dataformat_basetypes
():
"""Returns the list of base types that can be used for dataformat"""
...
...
@@ -284,3 +122,76 @@ def dataformat_basetypes():
def
is_Qt_equal_or_higher
(
version_string
):
return
version
.
parse
(
QtCore
.
QT_VERSION_STR
)
>=
version
.
parse
(
version_string
)
def
check_prefix_folders
(
prefix_path
):
"""Check that all supported asset types have their containing folder
available
"""
modified
=
False
result
=
True
missing_folders
=
[]
for
asset_type
in
AssetType
:
if
asset_type
is
not
AssetType
.
UNKNOWN
:
path
=
os
.
path
.
join
(
prefix_path
,
asset_type
.
path
)
if
not
os
.
path
.
exists
(
path
):
missing_folders
.
append
(
path
)
if
missing_folders
:
answer
=
QMessageBox
.
question
(
None
,
QCoreApplication
.
translate
(
"utils"
,
"Prefix incomplete"
),
QCoreApplication
.
translate
(
"utils"
,
"Your prefix is missing folders.
\n
"
"Would you like to create them ?"
,
),
)
if
answer
==
QMessageBox
.
Yes
:
for
folder
in
missing_folders
:
os
.
makedirs
(
folder
)
modified
=
True
else
:
result
=
False
return
result
,
modified
def
check_prefix_dataformats
(
prefix_path
):
"""Currently checks that the data format needed for the algorithm is
available
"""
modified
=
False
result
=
True
try
:
load_algorithm_prototype
(
prefix_path
)
except
RuntimeError
:
answer
=
QMessageBox
.
question
(
None
,
QCoreApplication
.
translate
(
"utils"
,
"Prefix incomplete"
),
QCoreApplication
.
translate
(
"utils"
,
"Your prefix is missing a mandatory data format.
\n
"
"Would you like to create it ?"
,
),
)
if
answer
==
QMessageBox
.
Yes
:
asset_type
=
AssetType
.
DATAFORMAT
integers_path
=
os
.
path
.
join
(
"prefix"
,
asset_type
.
path
,
"user"
,
"integers"
)
integers_folder
=
pkg_resources
.
resource_filename
(
"beat.core.test"
,
integers_path
)
shutil
.
copytree
(
integers_folder
,
os
.
path
.
join
(
prefix_path
,
asset_type
.
path
,
"user"
,
"integers"
),
)
modified
=
True
else
:
result
=
False
return
result
,
modified
beat/editor/widgets/algorithmeditor.py
View file @
7749fc21
...
...
@@ -24,7 +24,7 @@
###############################################################################
from
..backend.asset
import
AssetType
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
.editor
import
AbstractAssetEditor
...
...
beat/editor/widgets/assetwidget.py
View file @
7749fc21
...
...
@@ -44,7 +44,7 @@ from PyQt5.QtWidgets import QMessageBox
from
..backend.asset
import
AssetType
from
..backend.asset
import
Asset
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
.editor
import
PlaceholderEditor
from
.algorithmeditor
import
AlgorithmEditor
...
...
beat/editor/widgets/databaseeditor.py
View file @
7749fc21
...
...
@@ -54,7 +54,7 @@ from beat.core.protocoltemplate import ProtocolTemplate
from
..backend.assetmodel
import
AssetModel
from
..backend.asset
import
AssetType
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
..utils
import
is_Qt_equal_or_higher
from
.editor
import
AbstractAssetEditor
...
...
beat/editor/widgets/dataformateditor.py
View file @
7749fc21
...
...
@@ -40,7 +40,7 @@ from PyQt5.QtWidgets import QSpinBox
from
PyQt5.QtWidgets
import
QVBoxLayout
from
PyQt5.QtWidgets
import
QWidget
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
..utils
import
dataformat_basetypes
from
..backend.asset
import
AssetType
...
...
beat/editor/widgets/experimenteditor.py
View file @
7749fc21
...
...
@@ -24,7 +24,7 @@
###############################################################################
from
..backend.asset
import
AssetType
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
.editor
import
AbstractAssetEditor
...
...
beat/editor/widgets/libraryeditor.py
View file @
7749fc21
...
...
@@ -23,7 +23,7 @@
# #
###############################################################################
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
..backend.asset
import
AssetType
from
..backend.assetmodel
import
AssetModel
...
...
beat/editor/widgets/plottereditor.py
View file @
7749fc21
...
...
@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import QLineEdit
from
PyQt5.QtWidgets
import
QPushButton
from
PyQt5.QtWidgets
import
QWidget
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
..backend.asset
import
AssetType
from
..backend.assetmodel
import
AssetModel
...
...
beat/editor/widgets/plotterparameterseditor.py
View file @
7749fc21
...
...
@@ -24,7 +24,7 @@
###############################################################################
from
..backend.asset
import
AssetType
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
.editor
import
AbstractAssetEditor
...
...
beat/editor/widgets/protocoltemplateeditor.py
View file @
7749fc21
...
...
@@ -40,7 +40,7 @@ from PyQt5.QtWidgets import QTableWidgetItem
from
PyQt5.QtWidgets
import
QVBoxLayout
from
PyQt5.QtWidgets
import
QWidget
from
..
util
s
import
frozen
from
..
decorator
s
import
frozen
from
..backend.asset
import
AssetType
from
..backend.assetmodel
import
AssetModel
...
...
beat/editor/widgets/spinboxes.py
View file @
7749fc21
...
...
@@ -34,7 +34,7 @@ from PyQt5.QtGui import QValidator
from
PyQt5.QtWidgets
import
QAbstractSpinBox