Commit 35e17cbe authored by Jaden DIEFENBAUGH's avatar Jaden DIEFENBAUGH

added python & js code to handle templates trying to be written to an existing file, #108

parent eac1150f
......@@ -48,6 +48,22 @@ from . import utils
from beat.core.dock import Host
from beat.core.environments import enumerate_packages
def make_error(status_code, message):
"""Overrides flask-restful's response handling to return a custom error message
Adapted from https://stackoverflow.com/a/21639552
Parameters:
status_code (int): The HTTP status code to return
message (str): The error message text to return
"""
response = simplejson.dumps({
'status': status_code,
'message': message
})
response.status_code = status_code
return response
class Layout(Resource):
"""Exposes toolchain layout functionality"""
......@@ -94,7 +110,8 @@ class Templates(Resource):
data = request.get_json()
entity = data.pop('entity')
name = data.pop('name')
utils.generate_python_template(entity, name, self.config, **data)
confirm = data.pop('confirm')
utils.generate_python_template(entity, name, confirm, self.config, **data)
class Settings(Resource):
......
......@@ -72,6 +72,7 @@ import docopt
from beat.cmdline.config import Configuration
def main(user_input=None):
if user_input is not None:
......@@ -111,7 +112,13 @@ def main(user_input=None):
static_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../js')
app = Flask(__name__, static_folder=static_folder, static_url_path='')
api = Api(app)
errors = errors = {
'PythonFileAlreadyExistsError': {
'message': "The python template file trying to be created already exists.",
'status': 409,
}
}
api = Api(app, errors=errors)
CORS(app)
@app.route('/')
......
......@@ -108,20 +108,31 @@ TEMPLATE_FUNCTION = dict(
)
"""Functions for template instantiation within beat.editor"""
class PythonFileAlreadyExistsError(Exception):
pass
def generate_python_template(entity, name, config, **kwargs):
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 (valid values are
"""
entity (str): A valid BEAT entity
s = TEMPLATE_FUNCTION[entity](**kwargs)
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
s = TEMPLATE_FUNCTION[entity](**kwargs)
with open(file_path, 'w') as f: f.write(s)
return s
......
......@@ -5,6 +5,7 @@ import {
Modal,
ModalBody,
ModalHeader,
ModalFooter,
} from 'reactstrap';
import {
generateDatabaseTemplate,
......@@ -22,6 +23,7 @@ type Props = {
type State = {
infoOpen: boolean,
confirmOpen: boolean,
};
export default class EntityTemplateGenerationButton extends React.Component<Props, State> {
......@@ -31,13 +33,18 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop
state = {
infoOpen: false,
confirmOpen: false,
}
toggleInfo = (val: boolean = !this.state.infoOpen) => {
this.setState({ infoOpen: val });
}
click = () => {
toggleConfirm = (val: boolean = !this.state.confirmOpen) => {
this.setState({ confirmOpen: val });
}
click = async (confirm: boolean) => {
let uses;
let res;
switch(this.props.entity){
......@@ -50,25 +57,32 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop
.map(p => p.sets.map(s => s.view))
.reduce((a, vs) => [...a, ...vs], [])
));
res = generateDatabaseTemplate(this.props.data.name, views);
res = await generateDatabaseTemplate(this.props.data.name, confirm, views);
break;
case('algorithm'):
// find if the alg has parameters
// find the used libraries
if(!this.props.data.contents.parameters)
throw new Error(`Bad alg object, no params field: ${ this.props.data.contents }`);
res = generateAlgorithmTemplate(this.props.data.name, this.props.data.contents);
res = await generateAlgorithmTemplate(this.props.data.name, confirm, this.props.data.contents);
break;
case('library'):
// find the used libraries
uses = copyObj(this.props.data.contents.uses);
res = generateLibraryTemplate(this.props.data.name, uses);
res = await generateLibraryTemplate(this.props.data.name, confirm, uses);
break;
default:
throw new Error(`Cannot generate template for entity "${ this.props.entity }"`);
}
this.toggleInfo(true);
return res;
// inspect the result
if(res === false){
// the python file already existed, ask for confirmation to overwrite
this.toggleConfirm(true);
} else {
// its fine
this.toggleInfo(true);
}
}
render = () => {
......@@ -77,7 +91,7 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop
<Button outline
color='danger'
className='mx-auto'
onClick={this.click}
onClick={e => this.click(false)}
title={`Some types of objects (databases, algorithms, and libraries) need Python files to be used in experiments. You may generate a template Python file to provide a starting point for development.`}
>
Generate Python File
......@@ -97,6 +111,22 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop
</pre>
</ModalBody>
</Modal>
<Modal color='info' isOpen={this.state.confirmOpen} toggle={e => this.toggleConfirm(false)}>
<ModalHeader toggle={e => this.toggleConfirm(false)}>
Confirm Overwrite
</ModalHeader>
<ModalBody>
The generated Python file will overwrite the Python file that already exists at:
<pre className='preInline'>
/your/beat/prefix/{ pluralize(this.props.entity) }/{ this.props.data.name }.py
</pre>
Are you sure you want to overwrite this file?
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={e => { this.toggleConfirm(false); this.click(true); }}>Overwrite</Button>{' '}
<Button color="secondary" onClick={e => this.toggleConfirm(false)}>Cancel</Button>
</ModalFooter>
</Modal>
</React.Fragment>
);
}
......
......@@ -73,6 +73,8 @@ export const fetchLayout = async (tcName: string) => {
// generates a python file from a template given some args payload
const templatesUrl = toUrl('templates');
// given the BEAT entity and a dict of args, tries to generate a python file from a template
// returns either the response if successful or `false` if the python file already exists
const generateTemplate = async (be: string, args: {[string]: any}) => {
const templatesConfig = {
headers: {
......@@ -83,18 +85,26 @@ const generateTemplate = async (be: string, args: {[string]: any}) => {
body: JSON.stringify({ entity: be, ...args}),
};
const res = await fetch(templatesUrl, templatesConfig);
const json = await res.json();
return JSON.parse(json);
if(res.status === 200){
// okay
const json = await res.json();
return JSON.parse(json);
} else {
if(res.status === 409){
// python file already exists, return false to let callers know
return false;
}
}
};
export const generateDatabaseTemplate = async (name: string, views: string[]) => {
generateTemplate('databases', {name, views});
export const generateDatabaseTemplate = async (name: string, confirm: boolean, views: string[]) => {
return generateTemplate('databases', {name, views, confirm});
};
export const generateAlgorithmTemplate = async (name: string, contents: any) => {
generateTemplate('algorithms', {name, contents});
export const generateAlgorithmTemplate = async (name: string, confirm: boolean, contents: any) => {
return generateTemplate('algorithms', {name, contents, confirm});
};
export const generateLibraryTemplate = async (name: string, uses: StringObject) => {
generateTemplate('libraries', {name, uses});
export const generateLibraryTemplate = async (name: string, confirm: boolean, uses: StringObject) => {
return generateTemplate('libraries', {name, uses, confirm});
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment