Skip to content
Snippets Groups Projects
Commit 970f9604 authored by André Anjos's avatar André Anjos :speech_balloon:
Browse files

More python fixes after gigantic move

parent db8a44fd
No related branches found
No related tags found
1 merge request!3Support for new conda-based CI/CD pipelines
Pipeline #
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''Server resources (API endpoints)'''
import os
import glob
import shutil
import subprocess
import simplejson
from flask_restful import Resource
import logging
logger = logging.getLogger(__name__)
from . import utils
class Home(Resource):
"""The base resource path with subroutes"""
def get(self):
"""Returns the available sub-routes"""
return {
'routes': [
'databases',
'dataformats',
'libraries',
'algorithms',
'toolchains',
'experiments',
'plotters',
'plotterparameters',
'settings',
'layout',
'environments',
]
}
class Layout(Resource):
"""Exposes toolchain layout functionality"""
def post(self):
data = request.get_json()
if data != None and 'toolchain' in data:
return get_tc_layout(data['toolchain'])
else:
raise RuntimeError('Invalid post content for tc layout!')
class Environments(Resource):
"""Exposes local environment info"""
def get(self):
return get_environment_info()
class Settings(Resource):
"""Exposes user settings"""
def get(self):
"""Returns the settings"""
return utils.get_user_conf()
def put(self):
"""Overwrites the settings"""
new_conf = request.get_json()
with open('./user_conf.json', 'w') as f:
try:
json.dump(new_conf, f)
except json.JSONDecodeError:
logger.critical('Invalid new_conf %s', new_conf)
raise SystemExit
return utils.get_user_conf()
class Templates(Resource):
"""Endpoint for generating template files"""
def post(self):
data = request.get_json()
entity = data.pop('entity')
name = data.pop('name')
generate_python_template(entity, name, **data)
def read_json(path):
"""Reads a JSON file and returns the parse JSON obj"""
with open(path, 'rt') as f:
return json.loads(f.read())
def path_to_dict(path):
"""Generates a dict of the given file/folder in the BEAT prefix"""
d = {
'name': os.path.basename(path)
}
if os.path.isdir(path):
d['type'] = "directory"
d['children'] = [path_to_dict(os.path.join(path, x))
for x in os.listdir(path)]
elif os.path.isfile(path):
d['type'] = "file"
fname, fext = os.path.splitext(path)
if fext == '.json':
d['json'] = read_json(path)
return d
VALID_ENTITIES = [
'dataformats',
'databases',
'libraries',
'algorithms',
'toolchains',
'experiments',
'plotters',
'plotterparameters',
]
"""List of valid BEAT object entitities"""
def get_tc_layout(tc_name):
"""Returns the JSON returned by Graphviz's dot "-Tjson0" output for the given toolchain"""
prefix = utils.get_prefix()
beat_ex_loc = '%s/bin/beat' % prefix
tc_dot_loc = '%s/toolchains/%s.dot' % (prefix, tc_name)
if not os.path.isfile(beat_ex_loc):
raise Exception('BEAT executable not at %s' % beat_ex_loc)
beat_cmd = 'cd %s && ./bin/beat tc draw %s' % (prefix, tc_name)
output = subprocess.call(beat_cmd, shell=True)
if not os.path.isfile(tc_dot_loc):
logger.critical('Running command "%s" got:', beat_cmd)
raise IOError('"%s" graphviz dot file not found at "%s"' % (tc_name, tc_dot_loc))
s = subprocess.check_output('dot %s -Tjson0' % tc_dot_loc, shell=True, encoding='utf-8')
return s
def get_environment_info():
json = simplejson.loads('''
[
{
"name": "Scientific Python 2.7",
"packages": {
"alabaster": "0.7.6",
"babel": "1.3"
},
"queues": {
"Default": {
"memory_limit": 5983,
"nb_slots": 2,
"max_slots_per_user": 2,
"nb_cores_per_slot": 1,
"time_limit": 360
}
},
"accessibility": "public",
"languages": [
"python"
],
"version": "0.0.4",
"short_description": "Scientific Python 2.7"
},
{
"name": "Scientific Python 2.7",
"packages": {
"alabaster": "0.7.10",
"Babel": "2.4.0"
},
"queues": {
"Default": {
"memory_limit": 5983,
"nb_slots": 2,
"max_slots_per_user": 2,
"nb_cores_per_slot": 1,
"time_limit": 360
}
},
"accessibility": "public",
"languages": [
"python"
],
"version": "1.0.0",
"short_description": "Scientific Python 2.7"
}
]
''')
return json
def assert_valid_entity(v):
"""Asserts the passed value corresponds to a valid BEAT entity"""
assert v in VALID_ENTITIES, '%s is not a valid BEAT entity ' \
'(valid values are %s)' % (v, ', '.join(VALID_ENTITIES))
def generate_file_tree(entity):
"""Generates a file tree (of dicts) given a specific BEAT entity"""
assert_valid_entity(entity)
resource_path = os.path.join(utils.get_prefix(), entity)
if not os.path.isdir(resource_path):
raise NotADirectoryError('Invalid resource path %s' % resource_path)
return path_to_dict(resource_path)
def generate_json_entity(fto, parent_names):
"""Generates info for a file in the BEAT path"""
if fto['type'] != 'file':
raise Exception('bad file tree obj')
fname, fext = os.path.splitext(fto['name'])
name_str = ''
for name in parent_names:
name_str += name + '/'
name_str += fname
return {
'name': name_str,
'contents': fto['json']
}
def generate_entity_tree(entity):
"""Generates the entire tree for an entity type from the prefix"""
file_tree = generate_file_tree(entity)
entity_tree = {}
user_and_name = [
'dataformat',
'library',
'algorithm',
'toolchain',
'plotter',
'plotterparameter',
]
if entity in user_and_name:
for user in file_tree['children']:
entity_tree[user['name']] = {}
for obj in user['children']:
entity_tree[user['name']][obj['name']] = list()
for f in obj['children']:
fname, fext = os.path.splitext(f['name'])
if fext != '.json':
continue
parent_names = [user['name'], obj['name']]
json_obj = generate_json_entity(f, parent_names)
entity_tree[user['name']][obj['name']].append(json_obj)
elif entity == BeatEntity.DATABASE:
for obj in file_tree['children']:
entity_tree[obj['name']] = list()
for f in obj['children']:
fname, fext = os.path.splitext(f['name'])
if fext != '.json':
continue
parent_names = [obj['name']]
json_obj = generate_json_entity(f, parent_names)
entity_tree[obj['name']].append(json_obj)
elif entity == BeatEntity.EXPERIMENT:
for user in file_tree['children']:
uname = user['name']
entity_tree[uname] = {}
for tc_user in user['children']:
tcuname = tc_user['name']
entity_tree[uname][tcuname] = {}
for tc_name in tc_user['children']:
tcname = tc_name['name']
entity_tree[uname][tcuname][tcname] = {}
for tc_version in tc_name['children']:
tcv = tc_version['name']
entity_tree[uname][tcuname][tcname][tcv] = list()
for exp_name in tc_version['children']:
fname, fext = os.path.splitext(exp_name['name'])
if fext != '.json':
continue
parent_names = [uname, tcuname, tcname, tcv]
json_obj = generate_json_entity(
exp_name, parent_names)
entity_tree[uname][tcuname][tcname][tcv].append(
json_obj)
return entity_tree
def write_json(entity, obj, mode, copy_obj_name='', **kwargs):
"""Writes JSON from a webapp request to the prefix using the specified
mode"""
assert_valid_entity(entity)
resource_path = os.path.join(utils.get_prefix(), entityt)
name = obj['name']
name_segs = name.split('/')
contents = obj['contents']
stringified = simplejson.dumps(contents, indent=4, sort_keys=True)
folder_path = os.path.join(resource_path, '/'.join(name_segs[:-1]))
file_subpath = os.path.join(resource_path, name)
file_path = file_subpath + '.json'
if mode == WriteMode.UPDATE:
os.makedirs(folder_path, exist_ok=True)
with open(file_path, 'w') as f:
f.write(stringified)
elif mode == WriteMode.CREATE:
if not os.path.isfile(file_path):
os.makedirs(folder_path, exist_ok=True)
if copy_obj_name != '':
copy_obj_path = os.path.join(resource_path, copy_obj_name)
if os.path.isfile(copy_obj_path + '.json'):
files_to_copy = glob.glob('%s.*' % copy_obj_path)
copy_locations = ['%s%s' % (file_subpath, os.path.splitext(f)[1]) for f in files_to_copy]
for i in range(0, len(files_to_copy)):
if files_to_copy[i].endswith('.json'): continue
shutil.copy(files_to_copy[i], copy_locations[i])
with open(file_path, 'w') as f:
f.write(stringified)
elif mode == WriteMode.DELETE:
if os.path.isfile(file_path):
files_to_delete = glob.glob('%s.*' % file_subpath)
for f in files_to_delete:
os.remove(f)
# taken from https://stackoverflow.com/a/23488980
def remove_empty_dirs(path):
"""Remove directories now made empty by deleting an object from the prefix"""
for root, dirnames, filenames in os.walk(path, topdown=False):
for dirname in dirnames:
remove_empty_dirs(
os.path.realpath(
os.path.join(root, dirname)
)
)
if not os.listdir(path):
os.rmdir(path)
remove_empty_dirs(folder_path)
else:
raise TypeError('Invalid WriteMode %s' % mode)
def gen_endpoint(entity):
"""Generates an endpoint for the given BEAT entity
Exposes actions to perform on the prefix
"""
class Endpoint(Resource):
"""A class representing the template for an endpoint for a BEAT entity"""
def refresh(self):
"""Regenerates the entity tree"""
try:
return generate_entity_tree(entity)
except NotADirectoryError:
return {}
def get(self):
"""Returns the entity tree"""
return self.refresh()
def post(self):
"""Creates a new object"""
obj_list = request.get_json()
if not isinstance(obj_list, list):
obj_list = [obj_list]
for o in obj_list:
# two fields:
# - "obj" field (the object to create)
# - "copyObjName" field (the object that was copied, blank if
# not copied)
obj = o['obj']
copy_obj_name = o['copiedObjName']
write_json(entity, obj, 'create', copy_obj_name)
return self.refresh()
def put(self):
"""Updates an already-existing object"""
obj_list = request.get_json()
if not isinstance(obj_list, list):
obj_list = [obj_list]
for obj in obj_list:
write_json(entity, obj, 'update')
return self.refresh()
def delete(self):
"""Deletes an object"""
obj = request.get_json()
write_json(entity, obj, 'delete')
return self.refresh()
Endpoint.__name__ = entity
return Endpoint
""" #!/usr/bin/env python
A simple Flask server providing an API for the local BEAT prefix and user configuration. # -*- coding: utf-8 -*-
Dependencies:
simplejson
flask
flask_restful
flask_cors
"""
import os
import glob
from shutil import copy as copy_file
import subprocess
import json
from enum import Enum
import simplejson
from flask import Flask, request
from flask_restful import Resource, Api
from flask_cors import CORS
from .. import templates
def get_user_conf():
"""Reads & returns the user configuration in a dict"""
user_conf = {}
if os.path.isfile('./user_conf.json'):
with open('./user_conf.json') as f:
try:
user_conf = json.load(f)
except json.JSONDecodeError:
print('user_conf.json is invalid!')
raise SystemExit
else:
with open('./user_conf.json', 'w') as f:
user_conf = {
'prefix': '~'
}
json.dump(user_conf, f)
if 'prefix' not in user_conf:
raise Exception('Invalid user_conf: Needs "prefix" key!')
return user_conf
def get_prefix():
"""Returns an absolute path to the user-defined BEAT prefix location"""
return os.path.expanduser(get_user_conf()['prefix'])
def get_tc_layout(tc_name):
"""Returns the JSON returned by Graphviz's dot "-Tjson0" output for the given toolchain"""
prefix = get_prefix()
beat_ex_loc = '%s/bin/beat' % prefix
tc_dot_loc = '%s/toolchains/%s.dot' % (prefix, tc_name)
if not os.path.isfile(beat_ex_loc):
raise Exception('BEAT executable not at %s' % beat_ex_loc)
beat_cmd = 'cd %s && ./bin/beat tc draw %s' % (prefix, tc_name)
output = subprocess.call(beat_cmd, shell=True)
if not os.path.isfile(tc_dot_loc):
print('Running command "%s" got:' % beat_cmd)
print(output)
raise Exception('"%s" graphviz dot file not found at "%s"' % (tc_name, tc_dot_loc))
s = subprocess.check_output('dot %s -Tjson0' % tc_dot_loc, shell=True, encoding='utf-8')
return s
def get_environment_info():
json = simplejson.loads('''
[
{
"name": "Scientific Python 2.7",
"packages": {
"alabaster": "0.7.6",
"babel": "1.3"
},
"queues": {
"Default": {
"memory_limit": 5983,
"nb_slots": 2,
"max_slots_per_user": 2,
"nb_cores_per_slot": 1,
"time_limit": 360
}
},
"accessibility": "public",
"languages": [
"python"
],
"version": "0.0.4",
"short_description": "Scientific Python 2.7"
},
{
"name": "Scientific Python 2.7",
"packages": {
"alabaster": "0.7.10",
"Babel": "2.4.0"
},
"queues": {
"Default": {
"memory_limit": 5983,
"nb_slots": 2,
"max_slots_per_user": 2,
"nb_cores_per_slot": 1,
"time_limit": 360
}
},
"accessibility": "public",
"languages": [
"python"
],
"version": "1.0.0",
"short_description": "Scientific Python 2.7"
}
]
''')
return json
class BeatEntity(Enum):
"""List of BEAT entities to serve to the webapp"""
DATAFORMAT = 'dataformats'
DATABASE = 'databases'
LIBRARY = 'libraries'
ALGORITHM = 'algorithms'
TOOLCHAIN = 'toolchains'
EXPERIMENT = 'experiments'
PLOTTER = 'plotters'
PLOTTERPARAMETER = 'plotterparameters'
def read_json(path):
"""Reads a JSON file and returns the parse JSON obj"""
with open(path, 'r') as f:
data = json.load(f)
return data
def path_to_dict(path):
"""Generates a dict of the given file/folder in the BEAT prefix"""
d = {
'name': os.path.basename(path)
}
if os.path.isdir(path):
d['type'] = "directory"
d['children'] = [path_to_dict(os.path.join(path, x))
for x in os.listdir(path)]
elif os.path.isfile(path):
d['type'] = "file"
fname, fext = os.path.splitext(path)
if fext == '.json':
d['json'] = read_json(path)
return d
def generate_file_tree(be): """Starts the BEAT editor
"""Generates a file tree (of dicts) given a specific BEAT entity"""
resource_path = os.path.join(get_prefix(), be.value)
if not os.path.isdir(resource_path):
raise NotADirectoryError('Invalid resource path %s' % resource_path)
#print('generating entity tree for path %s' % resource_path) Usage: %(prog)s [-v...] [--debug]
return path_to_dict(resource_path) %(prog)s --help
%(prog)s --version
def generate_json_entity(fto, parent_names): Options:
"""Generates info for a file in the BEAT path"""
if fto['type'] != 'file':
raise Exception('bad file tree obj')
fname, fext = os.path.splitext(fto['name']) -h, --help Shows this help message and exits
-V, --version Prints the version and exits
-v, --verbose Increases the output verbosity level. Using "-vv" allows the
program to output informational messages as it goes along.
-d, --debug Use the debug version of the javascript source to launch the
editor
name_str = ''
for name in parent_names:
name_str += name + '/'
name_str += fname Examples:
return { Start the editor:
'name': name_str,
'contents': fto['json']
}
$ %(prog)s -vv
def generate_entity_tree(be): Start the editor in development mode:
"""Generates the entire tree for an entity type from the prefix"""
file_tree = generate_file_tree(be)
entity_tree = {}
user_and_name = [
BeatEntity.DATAFORMAT,
BeatEntity.LIBRARY,
BeatEntity.ALGORITHM,
BeatEntity.TOOLCHAIN,
BeatEntity.PLOTTER,
BeatEntity.PLOTTERPARAMETER,
]
if be in user_and_name: $ %(prog)s -vv --debug
for user in file_tree['children']:
entity_tree[user['name']] = {}
for obj in user['children']:
entity_tree[user['name']][obj['name']] = list()
for f in obj['children']:
fname, fext = os.path.splitext(f['name'])
if fext != '.json':
continue
parent_names = [user['name'], obj['name']]
json_obj = generate_json_entity(f, parent_names)
entity_tree[user['name']][obj['name']].append(json_obj)
elif be == BeatEntity.DATABASE:
for obj in file_tree['children']:
entity_tree[obj['name']] = list()
for f in obj['children']:
fname, fext = os.path.splitext(f['name'])
if fext != '.json':
continue
parent_names = [obj['name']]
json_obj = generate_json_entity(f, parent_names)
entity_tree[obj['name']].append(json_obj)
elif be == BeatEntity.EXPERIMENT:
for user in file_tree['children']:
uname = user['name']
entity_tree[uname] = {}
for tc_user in user['children']:
tcuname = tc_user['name']
entity_tree[uname][tcuname] = {}
for tc_name in tc_user['children']:
tcname = tc_name['name']
entity_tree[uname][tcuname][tcname] = {}
for tc_version in tc_name['children']:
tcv = tc_version['name']
entity_tree[uname][tcuname][tcname][tcv] = list()
for exp_name in tc_version['children']:
fname, fext = os.path.splitext(exp_name['name'])
if fext != '.json':
continue
parent_names = [uname, tcuname, tcname, tcv]
json_obj = generate_json_entity(
exp_name, parent_names)
entity_tree[uname][tcuname][tcname][tcv].append(
json_obj)
return entity_tree
def generate_python_template(be, name, **kwargs):
"""Generates a template for a certain beat entity type with the given named arguments"""
template_func = None
if be == BeatEntity.DATABASE:
template_func = templates.generate_database
elif be == BeatEntity.LIBRARY:
template_func = templates.generate_library
elif be == BeatEntity.ALGORITHM:
template_func = templates.generate_algorithm
else:
raise TypeError('Cannot create template for beat entity type "%s"' % be.value)
s = template_func(**kwargs)
resource_path = os.path.join(get_prefix(), be.value)
file_path = os.path.join(resource_path, name) + '.py'
with open(file_path, 'w') as f:
f.write(s)
return s
class WriteMode(Enum):
"""Write modes for requests from the web app"""
CREATE = 0
UPDATE = 1
DELETE = 2
def write_json(be, obj, mode, copy_obj_name='', **kwargs):
"""writes JSON from a webapp request to the prefix using the specified mode"""
resource_path = os.path.join(get_prefix(), be.value)
name = obj['name']
name_segs = name.split('/')
contents = obj['contents']
stringified = simplejson.dumps(contents, indent=4, sort_keys=True)
folder_path = os.path.join(resource_path, '/'.join(name_segs[:-1]))
file_subpath = os.path.join(resource_path, name)
file_path = file_subpath + '.json'
if mode == WriteMode.UPDATE:
os.makedirs(folder_path, exist_ok=True)
with open(file_path, 'w') as f:
f.write(stringified)
elif mode == WriteMode.CREATE:
if not os.path.isfile(file_path):
os.makedirs(folder_path, exist_ok=True)
if copy_obj_name != '':
copy_obj_path = os.path.join(resource_path, copy_obj_name)
if os.path.isfile(copy_obj_path + '.json'):
files_to_copy = glob.glob('%s.*' % copy_obj_path)
copy_locations = ['%s%s' % (file_subpath, os.path.splitext(f)[1]) for f in files_to_copy]
for i in range(0, len(files_to_copy)):
if files_to_copy[i].endswith('.json'): continue
copy_file(files_to_copy[i], copy_locations[i])
with open(file_path, 'w') as f:
f.write(stringified)
elif mode == WriteMode.DELETE:
if os.path.isfile(file_path):
files_to_delete = glob.glob('%s.*' % file_subpath)
for f in files_to_delete:
os.remove(f)
# taken from https://stackoverflow.com/a/23488980
def remove_empty_dirs(path):
"""Remove directories now made empty by deleting an object from the prefix"""
for root, dirnames, filenames in os.walk(path, topdown=False):
for dirname in dirnames:
remove_empty_dirs(
os.path.realpath(
os.path.join(root, dirname)
)
)
if not os.listdir(path):
os.rmdir(path)
remove_empty_dirs(folder_path)
else:
raise TypeError('Invalid WriteMode %s' % mode)
class Home(Resource):
"""The base resource path with subroutes"""
def get(self):
"""Returns the available sub-routes"""
return {
'routes': [
'databases',
'dataformats',
'libraries',
'algorithms',
'toolchains',
'experiments',
'plotters',
'plotterparameters',
'settings',
'layout',
'environments',
]
}
class Layout(Resource):
"""Exposes toolchain layout functionality"""
def post(self):
data = request.get_json()
if data != None and 'toolchain' in data:
return get_tc_layout(data['toolchain'])
else:
print(data)
raise Exception('Invalid post content for tc layout!')
class Environments(Resource):
"""Exposes local environment info"""
def get(self):
return get_environment_info()
class Settings(Resource):
"""Exposes user settings"""
def get(self):
"""Returns the settings"""
return get_user_conf()
def put(self):
"""Overwrites the settings"""
new_conf = request.get_json()
with open('./user_conf.json', 'w') as f:
try:
json.dump(new_conf, f)
except json.JSONDecodeError:
print('Invalid new_conf %s' % new_conf)
raise SystemExit
return get_user_conf()
"""
class Templates(Resource):
"""Endpoint for generating template files"""
def post(self):
data = request.get_json()
entity = BeatEntity(data.pop('entity'))
name = data.pop('name')
generate_python_template(entity, name, **data)
def gen_beat_endpoint(entity): import os
"""Generates an endpoint for the given BEAT entity, exposing actions to perform on the prefix""" import sys
class BeatEndpoint(Resource): import docopt
"""A class representing the template for an endpoint for a BEAT entity"""
be = entity
def refresh(self):
"""Regenerates the entity tree"""
try:
return generate_entity_tree(self.be)
except NotADirectoryError:
return {}
def get(self): def main(user_input=None):
"""Returns the entity tree"""
return self.refresh()
def post(self): if user_input is not None:
"""Creates a new object""" argv = user_input
obj_list = request.get_json() else:
if not isinstance(obj_list, list): argv = sys.argv[1:]
obj_list = [obj_list]
for o in obj_list:
# two fields:
# - "obj" field (the object to create)
# - "copyObjName" field (the object that was copied, blank if not copied)
obj = o['obj']
copy_obj_name = o['copiedObjName']
write_json(self.be, obj, WriteMode.CREATE, copy_obj_name)
return self.refresh()
def put(self): import pkg_resources
"""Updates an already-existing object"""
obj_list = request.get_json()
if not isinstance(obj_list, list):
obj_list = [obj_list]
for obj in obj_list:
write_json(self.be, obj, WriteMode.UPDATE)
return self.refresh()
def delete(self): completions = dict(
"""Deletes an object""" prog=os.path.basename(sys.argv[0]),
obj = request.get_json() version=pkg_resources.require('beat.editor')[0].version
write_json(self.be, obj, WriteMode.DELETE) )
return self.refresh()
BeatEndpoint.__name__ = entity.value args = docopt.docopt(
__doc__ % completions,
argv=argv,
version=completions['version'],
)
return BeatEndpoint from ..utils import setup_logger
logger = setup_logger('beat.editor', args['--verbose'])
def main(): from flask import Flask, request
from flask_restful import Api
from flask_cors import CORS
from ..resources import Home, Settings, Layout, Templates, Environments
from ..resources import VALID_ENTITIES, gen_endpoint
app = Flask(__name__) app = Flask(__name__)
api = Api(app) api = Api(app)
...@@ -459,8 +75,7 @@ def main(): ...@@ -459,8 +75,7 @@ def main():
api.add_resource(Layout, '/layout') api.add_resource(Layout, '/layout')
api.add_resource(Templates, '/templates') api.add_resource(Templates, '/templates')
api.add_resource(Environments, '/environments') api.add_resource(Environments, '/environments')
for entity in BeatEntity: for entity in VALID_ENTITIES:
val = entity.value api.add_resource(gen_endpoint(entity), '/' + entity)
api.add_resource(gen_beat_endpoint(entity), '/' + val)
app.run(debug=True) app.run(debug=args['--debug'])
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from jinja2 import Environment, PackageLoader
ENV = Environment(loader=PackageLoader(__name__, 'templates'))
"""Jinja2 environment for loading our templates"""
def generate_database(views=None):
"""Generates a BEAT database 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')
s = template.render(views=views)
return s
def generate_library(uses=None):
"""Parameters:
- uses : A dict of other libraries that the library uses.
Keys are the value to reference the library, values are the library being referenced.
"""
uses = uses or {}
template = env.get_template('library.jinja2')
s = template.render(uses=uses)
return s
def generate_algorithm(has_parameters=False, uses={}):
"""Parameters:
- has_parameters: Whether the algorithm has parameters or not (default: False)
- uses : A dict of libraries that the algorithm uses.
Keys are the value to reference the library, values are the library being referenced.
"""
template = env.get_template('algorithm.jinja2')
s = template.render(uses=uses, has_parameters=has_parameters)
return s
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import jinja2
import logging
logger = logging.getLogger(__name__)
ENV = jinja2.Environment(loader=jinja2.PackageLoader(__name__, 'templates'))
"""Jinja2 environment for loading our templates"""
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(has_parameters=False, uses=None):
"""Generates a valid BEAT algorithm from our stored template
Parameters:
has_parameters (:py:class:`bool`, Optional): Whether the algorithm has
parameters or not (default: False)
uses (:py:class:`dict`, Optional): A dict of libraries that the algorithm
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('algorithm.jinja2')
return template.render(uses=uses, has_parameters=has_parameters)
TEMPLATE_FUNCTION = dict(
database = generate_database,
library = generate_library,
algorithm = generate_algorithm,
)
"""Functions for template instantiation within beat.editor"""
def generate_python_template(entity, name, **kwargs):
"""Generates a template for a BEAT entity with the given named arguments
Parameters:
entity (str): A valid BEAT entity (valid values are
"""
s = TEMPLATE_FUNCTION[entity](**kwargs)
resource_path = os.path.join(get_prefix(), entity)
file_path = os.path.join(resource_path, name) + '.py'
with open(file_path, 'w') as f: f.write(s)
return s
def get_user_conf():
"""Reads & returns the user configuration in a dict"""
user_conf = {}
if os.path.isfile('./user_conf.json'):
with open('./user_conf.json') as f:
try:
user_conf = json.load(f)
except json.JSONDecodeError:
logger.critical('user_conf.json is invalid!')
raise SystemExit
else:
with open('./user_conf.json', 'w') as f:
user_conf = {
'prefix': '~'
}
json.dump(user_conf, f)
if 'prefix' not in user_conf:
raise Exception('Invalid user_conf: Needs "prefix" key!')
return user_conf
def get_prefix():
"""Returns an absolute path to the user-defined BEAT prefix location"""
return os.path.expanduser(get_user_conf()['prefix'])
def setup_logger(name, verbosity):
'''Sets up the logging of a script
Parameters:
name (str): The name of the logger to setup
verbosity (int): The verbosity level to operate with. A value of ``0``
(zero) means only errors, ``1``, errors and warnings; ``2``, errors,
warnings and informational messages and, finally, ``3``, all types of
messages including debugging ones.
'''
logger = logging.getLogger(name)
formatter = logging.Formatter("%(name)s@%(asctime)s -- %(levelname)s: " \
"%(message)s")
_warn_err = logging.StreamHandler(sys.stderr)
_warn_err.setFormatter(formatter)
_warn_err.setLevel(logging.WARNING)
class _InfoFilter:
def filter(self, record): return record.levelno <= logging.INFO
_debug_info = logging.StreamHandler(sys.stdout)
_debug_info.setFormatter(formatter)
_debug_info.setLevel(logging.DEBUG)
_debug_info.addFilter(_InfoFilter())
logger.addHandler(_debug_info)
logger.addHandler(_warn_err)
logger.setLevel(logging.ERROR)
if verbosity == 1: logger.setLevel(logging.WARNING)
elif verbosity == 2: logger.setLevel(logging.INFO)
elif verbosity >= 3: logger.setLevel(logging.DEBUG)
return logger
{% set name = 'beat.editor' %} {% set name = 'beat.editor' %}
{% set project_dir = environ.get('RECIPE_DIR') + '/..' %} {% set project_dir = environ.get('RECIPE_DIR') + '/..' %}
{% set nodejs = '8.9.3' %}
package: package:
name: {{ name }} name: {{ name }}
...@@ -8,7 +7,7 @@ package: ...@@ -8,7 +7,7 @@ package:
build: build:
entry_points: entry_points:
- beatedit = beat.editor.scripts.server:main - beateditor = beat.editor.scripts.server:main
number: {{ environ.get('BOB_BUILD_NUMBER', 0) }} number: {{ environ.get('BOB_BUILD_NUMBER', 0) }}
run_exports: run_exports:
- {{ pin_subpackage(name) }} - {{ pin_subpackage(name) }}
...@@ -32,7 +31,8 @@ requirements: ...@@ -32,7 +31,8 @@ requirements:
- flask - flask
- flask-cors - flask-cors
- flask-restful - flask-restful
- enum34 # [py<34] - docopt
- beat.cmdline
test: test:
source_files: source_files:
...@@ -52,6 +52,7 @@ test: ...@@ -52,6 +52,7 @@ test:
- {{ name }} - {{ name }}
commands: commands:
- beateditor --help
- nosetests --with-coverage --cover-package={{ name }} -sv {{ name }} - nosetests --with-coverage --cover-package={{ name }} -sv {{ name }}
- sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx - sphinx-build -aEW {{ project_dir }}/doc {{ project_dir }}/sphinx
- sphinx-build -aEb doctest {{ project_dir }}/doc sphinx - sphinx-build -aEb doctest {{ project_dir }}/doc sphinx
......
...@@ -4,3 +4,5 @@ jinja2 ...@@ -4,3 +4,5 @@ jinja2
flask flask
flask-cors flask-cors
flask-restful flask-restful
docopt
beat.cmdline
...@@ -51,7 +51,7 @@ setup( ...@@ -51,7 +51,7 @@ setup(
install_requires=load_requirements('requirements.txt'), install_requires=load_requirements('requirements.txt'),
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'beatedit = beat.editor.scripts.server:main', 'beateditor = beat.editor.scripts.server:main',
], ],
}, },
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment