from flask import render_template,redirect
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder import ModelView, ModelRestApi
from flask_appbuilder import ModelView,AppBuilder,expose,BaseView,has_access
from importlib import reload
from flask_babel import gettext as __
from flask_babel import lazy_gettext as _
import uuid
from myapp.models.model_nni import NNI
from myapp.models.model_job import Repository
from flask_appbuilder.actions import action
from flask_appbuilder.models.sqla.filters import FilterEqualFunction, FilterStartsWith,FilterEqual,FilterNotEqual
from wtforms.validators import EqualTo,Length
from flask_babel import lazy_gettext,gettext
from flask_appbuilder.security.decorators import has_access
from flask_appbuilder.forms import GeneralModelConverter
from myapp.utils import core
from myapp import app, appbuilder,db,event_logger
from wtforms.ext.sqlalchemy.fields import QuerySelectField
import os,sys
from wtforms.validators import DataRequired, Length, NumberRange, Optional,Regexp
from sqlalchemy import and_, or_, select
from myapp.exceptions import MyappException
from wtforms import BooleanField, IntegerField, SelectField, StringField,FloatField,DateField,DateTimeField,SelectMultipleField,FormField,FieldList
from flask_appbuilder.fieldwidgets import BS3TextFieldWidget,BS3PasswordFieldWidget,DatePickerWidget,DateTimePickerWidget,Select2ManyWidget,Select2Widget
from myapp.forms import MyBS3TextAreaFieldWidget,MySelect2Widget,MyCodeArea,MyLineSeparatedListField,MyJSONField,MyBS3TextFieldWidget,MyCommaSeparatedListField,MySelectMultipleField
from myapp.views.view_team import Project_Filter,Project_Join_Filter
from myapp.utils.py import py_k8s
from flask_wtf.file import FileField
import shlex
import re,copy
from flask import (
current_app,
abort,
flash,
g,
Markup,
make_response,
redirect,
render_template,
request,
send_from_directory,
Response,
url_for,
)
from .baseApi import (
MyappModelRestApi
)
from myapp import security_manager
from werkzeug.datastructures import FileStorage
from .base import (
api,
BaseMyappView,
check_ownership,
data_payload_response,
DeleteMixin,
generate_download_headers,
get_error_msg,
get_user_roles,
handle_api_exception,
json_error_response,
json_success,
MyappFilter,
MyappModelView,
)
from flask_appbuilder import CompactCRUDMixin, expose
import pysnooper,datetime,time,json
conf = app.config
class NNI_Filter(MyappFilter):
# @pysnooper.snoop()
def apply(self, query, func):
user_roles = [role.name.lower() for role in list(self.get_user_roles())]
if "admin" in user_roles:
return query.order_by(self.model.id.desc())
join_projects_id = security_manager.get_join_projects_id(db.session)
# public_project_id =
# logging.info(join_projects_id)
return query.filter(
or_(
self.model.project_id.in_(join_projects_id),
# self.model.project.name.in_(['public'])
)
).order_by(self.model.id.desc())
class NNI_ModelView_Base():
datamodel = SQLAInterface(NNI)
conv = GeneralModelConverter(datamodel)
label_title='nni超参搜索'
check_redirect_list_url = conf.get('MODEL_URLS',{}).get('nni','')
base_permissions = ['can_add', 'can_edit', 'can_delete', 'can_list', 'can_show']
base_order = ('id', 'desc')
base_filters = [["id", NNI_Filter, lambda: []]]
order_columns = ['id']
list_columns = ['project','describe_url','job_type','creator','modified','run','log']
show_columns = ['created_by','changed_by','created_on','changed_on','job_type','name','namespace','describe',
'parallel_trial_count','max_trial_count','objective_type',
'objective_goal','objective_metric_name','objective_additional_metric_names','algorithm_name',
'algorithm_setting','parameters_html','trial_spec_html',
'working_dir','volume_mount','node_selector','image_pull_policy','resource_memory','resource_cpu','resource_gpu',
'experiment_html','alert_status']
add_form_query_rel_fields = {
"project": [["name", Project_Join_Filter, 'org']]
}
edit_form_query_rel_fields = add_form_query_rel_fields
edit_form_extra_fields={}
edit_form_extra_fields["alert_status"] = MySelectMultipleField(
label=_(datamodel.obj.lab('alert_status')),
widget=Select2ManyWidget(),
# default=datamodel.obj.alert_status.default.arg,
choices=[[x, x] for x in
['Pending', 'Running', 'Succeeded', 'Failed', 'Unknown', 'Waiting', 'Terminated']],
description="选择通知状态",
)
edit_form_extra_fields['name'] = StringField(
_(datamodel.obj.lab('name')),
description='英文名(小写字母、数字、- 组成),最长50个字符',
widget=BS3TextFieldWidget(),
validators=[DataRequired(), Regexp("^[a-z][a-z0-9\-]*[a-z0-9]$"), Length(1, 54)]
)
edit_form_extra_fields['describe'] = StringField(
_(datamodel.obj.lab('describe')),
description='中文描述',
widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
edit_form_extra_fields['namespace'] = StringField(
_(datamodel.obj.lab('namespace')),
description='运行命名空间',
widget=BS3TextFieldWidget(),
default=datamodel.obj.namespace.default.arg,
validators=[DataRequired()]
)
edit_form_extra_fields['parallel_trial_count'] = IntegerField(
_(datamodel.obj.lab('parallel_trial_count')),
default=datamodel.obj.parallel_trial_count.default.arg,
description='可并行的计算实例数目',
widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
edit_form_extra_fields['max_trial_count'] = IntegerField(
_(datamodel.obj.lab('max_trial_count')),
default=datamodel.obj.max_trial_count.default.arg,
description='最大并行的计算实例数目',
widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
edit_form_extra_fields['max_failed_trial_count'] = IntegerField(
_(datamodel.obj.lab('max_failed_trial_count')),
default=datamodel.obj.max_failed_trial_count.default.arg,
description='最大失败的计算实例数目',
widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
edit_form_extra_fields['objective_type'] = SelectField(
_(datamodel.obj.lab('objective_type')),
default=datamodel.obj.objective_type.default.arg,
description='目标函数类型(和自己代码中对应)',
widget=Select2Widget(),
choices=[['maximize', 'maximize'], ['minimize', 'minimize']],
validators=[DataRequired()]
)
edit_form_extra_fields['objective_goal'] = FloatField(
_(datamodel.obj.lab('objective_goal')),
default=datamodel.obj.objective_goal.default.arg,
description='目标门限',
widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
edit_form_extra_fields['objective_metric_name'] = StringField(
_(datamodel.obj.lab('objective_metric_name')),
default=NNI.objective_metric_name.default.arg,
description='目标函数(和自己代码中对应)',
widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
edit_form_extra_fields['objective_additional_metric_names'] = StringField(
_(datamodel.obj.lab('objective_additional_metric_names')),
default=datamodel.obj.objective_additional_metric_names.default.arg,
description='其他目标函数(和自己代码中对应)',
widget=BS3TextFieldWidget()
)
algorithm_name_choices = {
'TPE':"TPE",
'Random':"随机搜索",
"Anneal":"退火算法",
"Evolution":"进化算法",
"SMAC":"SMAC",
"BatchTuner": "批量调参器",
"GridSearch":"网格搜索",
"Hyperband":"Hyperband",
"NetworkMorphism":"Network Morphism",
"MetisTuner":"Metis Tuner",
"BOHB":"BOHB Advisor",
"GPTuner": "GP Tuner",
"PPOTuner": "PPO Tuner",
"PBTTuner":"PBT Tuner"
}
algorithm_name_choices = list(algorithm_name_choices.items())
edit_form_extra_fields['algorithm_name'] = SelectField(
_(datamodel.obj.lab('algorithm_name')),
default=datamodel.obj.algorithm_name.default.arg,
description='搜索算法',
widget=Select2Widget(),
choices=algorithm_name_choices,
validators=[DataRequired()]
)
edit_form_extra_fields['algorithm_setting'] = StringField(
_(datamodel.obj.lab('algorithm_setting')),
default=datamodel.obj.algorithm_setting.default.arg,
widget=BS3TextFieldWidget(),
description='搜索算法配置'
)
edit_form_extra_fields['parameters_demo'] = StringField(
_(datamodel.obj.lab('parameters_demo')),
description='搜索参数示例,标准json格式,注意:所有整型、浮点型都写成字符串型',
widget=MyCodeArea(code=core.nni_parameters_demo()),
)
edit_form_extra_fields['parameters'] = StringField(
_(datamodel.obj.lab('parameters')),
default=datamodel.obj.parameters.default.arg,
description=Markup(('搜索参数,注意:所有整型、浮点型都写成字符串型,示例:\n'+"
%s
"%core.nni_parameters_demo()).replace('\n','
')),
widget=MyBS3TextAreaFieldWidget(rows=10),
validators=[DataRequired()]
)
edit_form_extra_fields['node_selector'] = StringField(
_(datamodel.obj.lab('node_selector')),
description="部署task所在的机器(目前无需填写)",
default=datamodel.obj.node_selector.default.arg,
widget=BS3TextFieldWidget()
)
edit_form_extra_fields['working_dir'] = StringField(
_(datamodel.obj.lab('working_dir')),
description="代码所在目录,nni代码、配置和log都将在/mnt/${your_name}/nni/目录下进行",
default=datamodel.obj.working_dir.default.arg,
widget=BS3TextFieldWidget()
)
edit_form_extra_fields['image_pull_policy'] = SelectField(
_(datamodel.obj.lab('image_pull_policy')),
description="镜像拉取策略(always为总是拉取远程镜像,IfNotPresent为若本地存在则使用本地镜像)",
widget=Select2Widget(),
choices=[['Always', 'Always'], ['IfNotPresent', 'IfNotPresent']]
)
edit_form_extra_fields['volume_mount'] = StringField(
_(datamodel.obj.lab('volume_mount')),
description='外部挂载,格式:$pvc_name1(pvc):/$container_path1,$pvc_name2(pvc):/$container_path2',
default=datamodel.obj.volume_mount.default.arg,
widget=BS3TextFieldWidget()
)
edit_form_extra_fields['resource_memory'] = StringField(
_(datamodel.obj.lab('resource_memory')),
default=datamodel.obj.resource_memory.default.arg,
description='内存的资源使用限制(每个测试实例),示例:1G,20G',
widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
edit_form_extra_fields['resource_cpu'] = StringField(
_(datamodel.obj.lab('resource_cpu')),
default=datamodel.obj.resource_cpu.default.arg,
description='cpu的资源使用限制(每个测试实例)(单位:核),示例:2', widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
# @pysnooper.snoop()
def set_column(self, nni=None):
# 对编辑进行处理
# request_data = request.args.to_dict()
# job_type = request_data.get('job_type', 'Job')
# if nni:
# job_type = nni.job_type
#
# job_type_choices = ['','Job']
# job_type_choices = [[job_type_choice,job_type_choice] for job_type_choice in job_type_choices]
#
# if nni:
# self.edit_form_extra_fields['job_type'] = SelectField(
# _(self.datamodel.obj.lab('job_type')),
# description="超参搜索的任务类型",
# choices=job_type_choices,
# widget=MySelect2Widget(extra_classes="readonly",value=job_type),
# validators=[DataRequired()]
# )
# else:
# self.edit_form_extra_fields['job_type'] = SelectField(
# _(self.datamodel.obj.lab('job_type')),
# description="超参搜索的任务类型",
# widget=MySelect2Widget(new_web=True,value=job_type),
# choices=job_type_choices,
# validators=[DataRequired()]
# )
self.edit_form_extra_fields['job_worker_image'] = StringField(
_(self.datamodel.obj.lab('job_worker_image')),
default=json.loads(nni.job_json).get('job_worker_image',conf.get('NNI_JOB_DEFAULT_IMAGE','')) if nni and nni.job_json else conf.get('NNI_JOB_DEFAULT_IMAGE',''),
description='工作节点镜像',
widget=BS3TextFieldWidget(),
validators=[DataRequired()]
)
self.edit_form_extra_fields['job_worker_command'] = StringField(
_(self.datamodel.obj.lab('job_worker_command')),
default=json.loads(nni.job_json).get('job_worker_command','python xx.py') if nni and nni.job_json else 'python xx.py',
description='工作节点启动命令',
widget=MyBS3TextAreaFieldWidget(),
validators=[DataRequired()]
)
self.edit_columns = ['project','name','describe','parallel_trial_count','max_trial_count',
'objective_type','objective_goal','objective_metric_name','objective_additional_metric_names',
'algorithm_name','algorithm_setting','parameters']
self.edit_fieldsets=[(
lazy_gettext('common'),
{"fields": copy.deepcopy(self.edit_columns), "expanded": True},
)]
task_column=['job_worker_image','working_dir','job_worker_command','resource_memory','resource_cpu']
self.edit_fieldsets.append((
lazy_gettext('task args'),
{"fields": task_column, "expanded": True},
))
for column in task_column:
self.edit_columns.append(column)
self.edit_fieldsets.append((
lazy_gettext('run experiment'),
{"fields": ['alert_status'], "expanded": True},
))
self.edit_columns.append('alert_status')
self.add_form_extra_fields = self.edit_form_extra_fields
self.add_fieldsets = self.edit_fieldsets
self.add_columns=self.edit_columns
pre_add_get=set_column
pre_update_get=set_column
# 处理form请求
def process_form(self, form, is_created):
# from flask_appbuilder.forms import DynamicForm
if 'parameters_demo' in form._fields:
del form._fields['parameters_demo'] # 不处理这个字段
# @pysnooper.snoop()
def deploy_nni_service(self,nni,command):
image_secrets = conf.get('HUBSECRET', [])
user_hubsecrets = db.session.query(Repository.hubsecret).filter(Repository.created_by_fk == g.user.id).all()
if user_hubsecrets:
for hubsecret in user_hubsecrets:
if hubsecret[0] not in image_secrets:
image_secrets.append(hubsecret[0])
from myapp.utils.py.py_k8s import K8s
k8s_client = K8s(nni.project.cluster.get('KUBECONFIG',''))
namespace = conf.get('AUTOML_NAMESPACE','katib')
run_id='nni-'+nni.name
try:
nni_deploy = k8s_client.AppsV1Api.read_namespaced_deployment(name=nni.name,namespace=namespace)
if nni_deploy:
print('exist nni deploy')
k8s_client.AppsV1Api.delete_namespaced_deployment(name=nni.name,namespace=namespace)
# return
except Exception as e:
print(e)
volume_mount = nni.volume_mount+",/usr/share/zoneinfo/Asia/Shanghai(hostpath):/etc/localtime"
labels={"app": nni.name, "user": nni.created_by.username,'run-id':run_id,'pod-type':"nni"}
k8s_client.create_debug_pod(
namespace=namespace,
name=nni.name,
labels=labels,
command=command,
args=None,
volume_mount=volume_mount,
working_dir='/mnt/%s'%nni.created_by.username,
node_selector=nni.get_node_selector(),
resource_memory='2G',
resource_cpu='2',
resource_gpu='0',
image_pull_policy=conf.get('IMAGE_PULL_POLICY','Always'),
image_pull_secrets=image_secrets,
image=conf.get('NNI_IMAGES',json.loads(nni.job_json).get('job_worker_image')) ,
hostAliases=conf.get('HOSTALIASES',''),
env=None,
privileged=False,
accounts='nni',
username=nni.created_by.username
)
k8s_client.create_service(namespace=namespace,
name=nni.name,
username=nni.created_by.username,
ports=[8888],
selector=labels
)
host = nni.project.cluster.get('NNI_DOMAIN',request.host)
if not host:
host=request.host
if ':' in host:
host = host[:host.rindex(':')] # 如果捕获到端口号,要去掉
vs_json = {
"apiVersion": "networking.istio.io/v1alpha3",
"kind": "VirtualService",
"metadata": {
"name": nni.name,
"namespace": namespace
},
"spec": {
"gateways": [
"kubeflow/kubeflow-gateway"
],
"hosts": [
"*" if core.checkip(host) else host
],
"http": [
{
"match": [
{
"uri": {
"prefix": "/nni/%s//"%nni.name
}
},
{
"uri": {
"prefix": "/nni/%s/" % nni.name
}
}
],
"rewrite": {
"uri": "/nni/%s/"%nni.name
},
"route": [
{
"destination": {
"host": "%s.%s.svc.cluster.local"%(nni.name,namespace),
"port": {
"number": 8888
}
}
}
],
"timeout": "300s"
}
]
}
}
crd_info=conf.get('CRD_INFO')['virtualservice']
k8s_client.delete_istio_ingress(namespace=namespace,name=nni.name)
k8s_client.create_crd(group=crd_info['group'], version=crd_info['version'], plural=crd_info['plural'],namespace=namespace, body=vs_json)
# 生成实验
# @pysnooper.snoop()
@expose('/run/',methods=['GET','POST'])
# @pysnooper.snoop()
def run(self,nni_id):
nni = db.session.query(NNI).filter(NNI.id == nni_id).first()
image_secrets = conf.get('HUBSECRET', [])
user_hubsecrets = db.session.query(Repository.hubsecret).filter(Repository.created_by_fk == g.user.id).all()
if user_hubsecrets:
for hubsecret in user_hubsecrets:
if hubsecret[0] not in image_secrets:
image_secrets.append(hubsecret[0])
image_secrets = str(image_secrets)
trial_template=f'''
apiVersion: frameworkcontroller.microsoft.com/v1
kind: Framework
metadata:
name: {nni.name}
namespace: {nni.namespace}
spec:
executionType: Start
retryPolicy:
fancyRetryPolicy: true
maxRetryCount: 2
taskRoles:
- name: worker
taskNumber: 1
frameworkAttemptCompletionPolicy:
minFailedTaskCount: 1
minSucceededTaskCount: 3
task:
retryPolicy:
fancyRetryPolicy: false
maxRetryCount: 0
podGracefulDeletionTimeoutSec: 1800
pod:
spec:
restartPolicy: Never
hostNetwork: false
imagePullSecrets: {image_secrets}
containers:
- name: {nni.name}
image: {json.loads(nni.job_json).get("job_worker_image",'')}
command: {json.loads(nni.job_json).get("job_worker_command",'').split(' ')}
ports:
- containerPort: 5001
volumeMounts:
- name: frameworkbarrier-volume
mountPath: /mnt/frameworkbarrier
- name: data-volume
mountPath: /tmp/mount
serviceAccountName: frameworkbarrier
initContainers:
- name: frameworkbarrier
image: frameworkcontroller/frameworkbarrier
imagePullPolicy: IfNotPresent
volumeMounts:
- name: frameworkbarrier-volume
mountPath: /mnt/frameworkbarrier
volumes:
- name: frameworkbarrier-volume
emptyDir: {{}}
- name: data-volume
hostPath:
path: {conf.get('WORKSPACE_HOST_PATH','')}/{nni.created_by.username}/nni/{nni.name}
'''
controll_yaml=f'''
authorName: default
experimentName: {nni.name}
trialConcurrency: {nni.parallel_trial_count}
maxExecDuration: {nni.maxExecDuration}s
maxTrialNum: {nni.max_trial_count}
logLevel: info
logCollection: none
#choice: local, remote, pai, kubeflow
trainingServicePlatform: frameworkcontroller
searchSpacePath: /mnt/{nni.created_by.username}/nni/{nni.name}/search_space.json
#choice: true, false
useAnnotation: false
tuner:
#choice: TPE, Random, Anneal, Evolution, BatchTuner, MetisTuner, GPTuner
builtinTunerName: {nni.algorithm_name}
trial:
codeDir: {nni.working_dir}
frameworkcontrollerConfig:
namespace: {conf.get('AUTOML_NAMESPACE','katib')}
storage: pvc
configPath: /mnt/{nni.created_by.username}/nni/{nni.name}/trial_template.yaml
pvc:
path: "/mnt/{nni.created_by.username}/nni/{nni.name}/"
'''
code_dir = "%s/%s/nni/%s"%(conf.get('WORKSPACE_HOST_PATH',''),nni.created_by.username,nni.name)
if not os.path.exists(code_dir):
os.makedirs(code_dir)
trial_template_path = os.path.join(code_dir,'trial_template.yaml')
file = open(trial_template_path,mode='w')
file.write(trial_template)
file.close()
controll_template_path = os.path.join(code_dir, 'controll_template.yaml')
file = open(controll_template_path, mode='w')
file.write(controll_yaml)
file.close()
searchSpacePath = os.path.join(code_dir, 'search_space.json')
file = open(searchSpacePath, mode='w')
file.write(nni.parameters)
file.close()
flash('nni服务部署完成',category='success')
# 执行启动命令
# command = ['bash','-c','mkdir -p /nni/nni_node/static/nni/%s && cp -r /nni/nni_node/static/* /nni/nni_node/static/nni/%s/ ; nnictl create --config /mnt/%s/nni/%s/controll_template.yaml -p 8888 --foreground --url_prefix nni/%s'%(nni.name,nni.name,nni.created_by.username,nni.name,nni.name)]
# command = ['bash', '-c','nnictl create --config /mnt/%s/nni/%s/controll_template.yaml -p 8888 --foreground' % (nni.created_by.username, nni.name)]
command = ['bash','-c','nnictl create --config /mnt/%s/nni/%s/controll_template.yaml -p 8888 --foreground --url_prefix nni/%s'%(nni.created_by.username,nni.name,nni.name)]
print(command)
self.deploy_nni_service(nni,command)
return redirect(conf.get('MODEL_URLS',{}).get('nni',''))
# @pysnooper.snoop(watch_explode=())
def merge_trial_spec(self,item):
image_secrets = conf.get('HUBSECRET',[])
user_hubsecrets = db.session.query(Repository.hubsecret).filter(Repository.created_by_fk == g.user.id).all()
if user_hubsecrets:
for hubsecret in user_hubsecrets:
if hubsecret[0] not in image_secrets:
image_secrets.append(hubsecret[0])
image_secrets = [
{
"name": hubsecret
} for hubsecret in image_secrets
]
item.job_json={}
item.trial_spec=core.merge_job_experiment_template(
node_selector=item.get_node_selector(),
volume_mount=item.volume_mount,
image=item.job_worker_image,
image_secrets=image_secrets,
hostAliases=conf.get('HOSTALIASES', ''),
workingDir=item.working_dir,
image_pull_policy=conf.get('IMAGE_PULL_POLICY','Always'),
resource_memory=item.resource_memory,
resource_cpu=item.resource_cpu,
command=item.job_worker_command
)
item.job_json = {
"job_worker_image": item.job_worker_image,
"job_worker_command": item.job_worker_command,
}
item.job_json = json.dumps(item.job_json,indent=4,ensure_ascii=False)
# 检验参数是否有效
# @pysnooper.snoop()
def validate_parameters(self,parameters,algorithm):
return parameters
@expose("/log/", methods=["GET", "POST"])
def log_task(self,nni_id):
nni = db.session.query(NNI).filter_by(id=nni_id).first()
from myapp.utils.py.py_k8s import K8s
k8s = K8s(nni.project.cluster.get('KUBECONFIG',''))
namespace = conf.get('AUTOML_NAMESPACE')
pod = k8s.get_pods(namespace=namespace, pod_name=nni.name)
if pod:
pod = pod[0]
return redirect("/myapp/web/log/%s/%s/%s" % (nni.project.cluster['NAME'],namespace, nni.name))
flash("未检测到当前搜索正在运行的容器",category='success')
return redirect(conf.get('MODEL_URLS',{}).get('nni',''))
# @pysnooper.snoop()
def pre_add(self, item):
if item.job_type is None:
item.job_type='Job'
# raise MyappException("Job type is mandatory")
if not item.volume_mount:
item.volume_mount = item.project.volume_mount
core.validate_json(item.parameters)
item.parameters = self.validate_parameters(item.parameters,item.algorithm_name)
item.resource_memory=core.check_resource_memory(item.resource_memory,self.src_item_json.get('resource_memory',None) if self.src_item_json else None)
item.resource_cpu = core.check_resource_cpu(item.resource_cpu,self.src_item_json.get('resource_cpu',None) if self.src_item_json else None)
self.merge_trial_spec(item)
# self.make_experiment(item)
def pre_update(self, item):
self.pre_add(item)
@action(
"copy", __("Copy NNI Experiment"), confirmation=__('Copy NNI Experiment'), icon="fa-copy",multiple=True, single=False
)
def copy(self, nnis):
if not isinstance(nnis, list):
nnis = [nnis]
for nni in nnis:
new_nni = nni.clone()
new_nni.name = new_nni.name+"-copy"
new_nni.describe = new_nni.describe + "-copy"
new_nni.created_on = datetime.datetime.now()
new_nni.changed_on = datetime.datetime.now()
db.session.add(new_nni)
db.session.commit()
return redirect(request.referrer)
class NNI_ModelView(NNI_ModelView_Base,MyappModelView):
datamodel = SQLAInterface(NNI)
conv = GeneralModelConverter(datamodel)
appbuilder.add_separator("训练") # 在指定菜单栏下面的每个子菜单中间添加一个分割线的显示。
# 添加视图和菜单
# appbuilder.add_view(NNI_ModelView,"nni超参搜索",icon = 'fa-shopping-basket',category = '超参搜索',category_icon = 'fa-share-alt')
appbuilder.add_view(NNI_ModelView,"nni超参搜索",icon = 'fa-shopping-basket',category = '训练')
# appbuilder.add_view_no_menu(NNI_ModelView)
# 添加api
class NNI_ModelView_Api(NNI_ModelView_Base,MyappModelRestApi):
datamodel = SQLAInterface(NNI)
conv = GeneralModelConverter(datamodel)
route_base = '/nni_modelview/api'
list_columns = ['project','describe_url','creator','modified','run','log']
add_columns = ['project','name','describe',
'parallel_trial_count','max_trial_count','objective_type',
'objective_goal','objective_metric_name','objective_additional_metric_names','algorithm_name',
'algorithm_setting','parameters','job_json','working_dir','node_selector',
'resource_memory','resource_cpu','alert_status','job_worker_image','job_worker_command']
edit_columns = add_columns
appbuilder.add_api(NNI_ModelView_Api)