Skip to content
Snippets Groups Projects
Commit 35e17cbe authored by Jaden DIEFENBAUGH's avatar Jaden DIEFENBAUGH
Browse files

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

parent eac1150f
No related branches found
No related tags found
1 merge request!8Miscellaneous editor-specific improvements
...@@ -48,6 +48,22 @@ from . import utils ...@@ -48,6 +48,22 @@ from . import utils
from beat.core.dock import Host from beat.core.dock import Host
from beat.core.environments import enumerate_packages 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): class Layout(Resource):
"""Exposes toolchain layout functionality""" """Exposes toolchain layout functionality"""
...@@ -94,7 +110,8 @@ class Templates(Resource): ...@@ -94,7 +110,8 @@ class Templates(Resource):
data = request.get_json() data = request.get_json()
entity = data.pop('entity') entity = data.pop('entity')
name = data.pop('name') 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): class Settings(Resource):
......
...@@ -72,6 +72,7 @@ import docopt ...@@ -72,6 +72,7 @@ import docopt
from beat.cmdline.config import Configuration from beat.cmdline.config import Configuration
def main(user_input=None): def main(user_input=None):
if user_input is not None: if user_input is not None:
...@@ -111,7 +112,13 @@ def main(user_input=None): ...@@ -111,7 +112,13 @@ def main(user_input=None):
static_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../js') static_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../js')
app = Flask(__name__, static_folder=static_folder, static_url_path='') 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) CORS(app)
@app.route('/') @app.route('/')
......
...@@ -108,20 +108,31 @@ TEMPLATE_FUNCTION = dict( ...@@ -108,20 +108,31 @@ TEMPLATE_FUNCTION = dict(
) )
"""Functions for template instantiation within beat.editor""" """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 """Generates a template for a BEAT entity with the given named arguments
Parameters: 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) resource_path = os.path.join(config.path, entity)
file_path = os.path.join(resource_path, name) + '.py' 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) with open(file_path, 'w') as f: f.write(s)
return s return s
......
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
Modal, Modal,
ModalBody, ModalBody,
ModalHeader, ModalHeader,
ModalFooter,
} from 'reactstrap'; } from 'reactstrap';
import { import {
generateDatabaseTemplate, generateDatabaseTemplate,
...@@ -22,6 +23,7 @@ type Props = { ...@@ -22,6 +23,7 @@ type Props = {
type State = { type State = {
infoOpen: boolean, infoOpen: boolean,
confirmOpen: boolean,
}; };
export default class EntityTemplateGenerationButton extends React.Component<Props, State> { export default class EntityTemplateGenerationButton extends React.Component<Props, State> {
...@@ -31,13 +33,18 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop ...@@ -31,13 +33,18 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop
state = { state = {
infoOpen: false, infoOpen: false,
confirmOpen: false,
} }
toggleInfo = (val: boolean = !this.state.infoOpen) => { toggleInfo = (val: boolean = !this.state.infoOpen) => {
this.setState({ infoOpen: val }); this.setState({ infoOpen: val });
} }
click = () => { toggleConfirm = (val: boolean = !this.state.confirmOpen) => {
this.setState({ confirmOpen: val });
}
click = async (confirm: boolean) => {
let uses; let uses;
let res; let res;
switch(this.props.entity){ switch(this.props.entity){
...@@ -50,25 +57,32 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop ...@@ -50,25 +57,32 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop
.map(p => p.sets.map(s => s.view)) .map(p => p.sets.map(s => s.view))
.reduce((a, vs) => [...a, ...vs], []) .reduce((a, vs) => [...a, ...vs], [])
)); ));
res = generateDatabaseTemplate(this.props.data.name, views); res = await generateDatabaseTemplate(this.props.data.name, confirm, views);
break; break;
case('algorithm'): case('algorithm'):
// find if the alg has parameters // find if the alg has parameters
// find the used libraries // find the used libraries
if(!this.props.data.contents.parameters) if(!this.props.data.contents.parameters)
throw new Error(`Bad alg object, no params field: ${ this.props.data.contents }`); 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; break;
case('library'): case('library'):
// find the used libraries // find the used libraries
uses = copyObj(this.props.data.contents.uses); uses = copyObj(this.props.data.contents.uses);
res = generateLibraryTemplate(this.props.data.name, uses); res = await generateLibraryTemplate(this.props.data.name, confirm, uses);
break; break;
default: default:
throw new Error(`Cannot generate template for entity "${ this.props.entity }"`); 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 = () => { render = () => {
...@@ -77,7 +91,7 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop ...@@ -77,7 +91,7 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop
<Button outline <Button outline
color='danger' color='danger'
className='mx-auto' 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.`} 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 Generate Python File
...@@ -97,6 +111,22 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop ...@@ -97,6 +111,22 @@ export default class EntityTemplateGenerationButton extends React.Component<Prop
</pre> </pre>
</ModalBody> </ModalBody>
</Modal> </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> </React.Fragment>
); );
} }
......
...@@ -73,6 +73,8 @@ export const fetchLayout = async (tcName: string) => { ...@@ -73,6 +73,8 @@ export const fetchLayout = async (tcName: string) => {
// generates a python file from a template given some args payload // generates a python file from a template given some args payload
const templatesUrl = toUrl('templates'); 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 generateTemplate = async (be: string, args: {[string]: any}) => {
const templatesConfig = { const templatesConfig = {
headers: { headers: {
...@@ -83,18 +85,26 @@ const generateTemplate = async (be: string, args: {[string]: any}) => { ...@@ -83,18 +85,26 @@ const generateTemplate = async (be: string, args: {[string]: any}) => {
body: JSON.stringify({ entity: be, ...args}), body: JSON.stringify({ entity: be, ...args}),
}; };
const res = await fetch(templatesUrl, templatesConfig); const res = await fetch(templatesUrl, templatesConfig);
const json = await res.json(); if(res.status === 200){
return JSON.parse(json); // 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[]) => { export const generateDatabaseTemplate = async (name: string, confirm: boolean, views: string[]) => {
generateTemplate('databases', {name, views}); return generateTemplate('databases', {name, views, confirm});
}; };
export const generateAlgorithmTemplate = async (name: string, contents: any) => { export const generateAlgorithmTemplate = async (name: string, confirm: boolean, contents: any) => {
generateTemplate('algorithms', {name, contents}); return generateTemplate('algorithms', {name, contents, confirm});
}; };
export const generateLibraryTemplate = async (name: string, uses: StringObject) => { export const generateLibraryTemplate = async (name: string, confirm: boolean, uses: StringObject) => {
generateTemplate('libraries', {name, uses}); return generateTemplate('libraries', {name, uses, confirm});
}; };
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