diff --git a/api/api/_settings/apps.py b/api/api/_settings/apps.py index 1ddadae..a7900c6 100644 --- a/api/api/_settings/apps.py +++ b/api/api/_settings/apps.py @@ -11,6 +11,7 @@ 'libs', 'users', 'projects', + 'experiments', 'api', ) diff --git a/api/api/urls.py b/api/api/urls.py index d87c680..cb3cfcc 100644 --- a/api/api/urls.py +++ b/api/api/urls.py @@ -10,13 +10,14 @@ class IndexView(TemplateView): - template_name = "index.html" + template_name = "api/index.html" urlpatterns = [ - url(r'^admin', include(admin.site.urls)), + url(r'^users/', include('users.urls', namespace='users')), + url(r'^admin/', include(admin.site.urls)), url(r'^api/token/', obtain_auth_token), url(r'^v1/api/', include('projects.urls', namespace='projects_v1')), - url(r'^.*$', IndexView.as_view(), name='index'), + url(r'^$', IndexView.as_view(), name='index'), ] if settings.DEBUG: diff --git a/api/core/admin.py b/api/core/admin.py deleted file mode 100644 index 4bc587c..0000000 --- a/api/core/admin.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division, print_function - -from django.contrib import admin - -from core.models import ( - Agent, - AgentMemory, - Bridge, - Decoder, - Encoder, - Environment, - Estimator, - Experiment, - InputData, - Loss, - Metric, - Optimizer, - Pipeline, - PolyaxonModel, - RunConfig, - SubGraph, -) - -admin.site.register(Agent) -admin.site.register(AgentMemory) -admin.site.register(Environment) -admin.site.register(Loss) -admin.site.register(Metric) -admin.site.register(Optimizer) -admin.site.register(Bridge) -admin.site.register(Encoder) -admin.site.register(Decoder) -admin.site.register(SubGraph) -admin.site.register(PolyaxonModel) -admin.site.register(Estimator) -admin.site.register(Pipeline) -admin.site.register(InputData) -admin.site.register(RunConfig) -admin.site.register(Experiment) diff --git a/api/core/models.py b/api/core/models.py deleted file mode 100644 index 09a6118..0000000 --- a/api/core/models.py +++ /dev/null @@ -1,722 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division, print_function - -import json - -import polyaxon as plx -import tensorflow as tf - -from django.contrib.postgres.fields import ArrayField, JSONField -from django.db import models - -from polyaxon.estimators.hooks import HOOKS -from polyaxon.models.summarizer import SummaryOptions - -from core.utils import remove_empty_keys -from libs.models import DiffModel - - -class Loss(DiffModel): - """The `Loss` model serializes the module and parameters to define a loss funciton.""" - LOSSES = ((obj, obj) for obj in plx.losses.LOSSES.keys()) - - module = models.CharField(max_length=256, choices=LOSSES) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return {'module': self.module, 'params': self.params} - - @staticmethod - def from_config(config): - if not config: - return None - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return Loss.objects.create(**config_dict) - - def to_config(self): - return plx.configs.LossConfig(**self.to_dict()) - - -class Metric(DiffModel): - """The `Metric` model serializes the module and parameters to define a metric function.""" - METRICS = ((obj, obj) for obj in plx.metrics.EVAL_METRICS.keys()) - - module = models.CharField(max_length=256, choices=METRICS) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return {'module': self.module, 'params': self.params} - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return Metric.objects.create(**config_dict) - - def to_config(self): - return plx.configs.MetricConfig(**self.to_dict()) - - -class AgentMemory(DiffModel): - """The `AgentMemory` model serializes the module and parameters to define an agent memory.""" - MEMORIES = ((obj, obj) for obj in plx.rl.memories.MEMORIES.keys()) - - module = models.CharField(max_length=256, choices=MEMORIES) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return {'module': self.module, 'params': self.params} - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return AgentMemory.objects.create(**config_dict) - - def to_config(self): - return plx.configs.MetricConfig(**self.to_dict()) - - -class Optimizer(DiffModel): - """The `Optimizer` model serializes the module and parameters to define a model optimizer.""" - OPTIMIZERS = ((obj, obj) for obj in plx.optimizers.OPTIMIZERS.keys()) - - module = models.CharField(max_length=256, choices=OPTIMIZERS) - learning_rate = models.FloatField(default=1e-4) - decay_type = models.CharField(max_length=256, null=True, blank=True, default="") - decay_steps = models.IntegerField(default=100) - decay_rate = models.FloatField(default=0.99) - start_decay_at = models.IntegerField(default=0) - stop_decay_at = models.IntegerField(default=tf.int32.max) - min_learning_rate = models.FloatField(default=1e-12) - staircase = models.BooleanField(default=False) - sync_replicas = models.IntegerField(default=0) - sync_replicas_to_aggregate = models.IntegerField(default=0) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return { - 'module': self.module, - 'learning_rate': self.learning_rate, - 'decay_type': self.decay_type, - 'decay_steps': self.decay_steps, - 'decay_rate': self.decay_rate, - 'start_decay_at': self.start_decay_at, - 'stop_decay_at': self.stop_decay_at, - 'min_learning_rate': self.min_learning_rate, - 'staircase': self.staircase, - 'sync_replicas': self.sync_replicas, - 'sync_replicas_to_aggregate': self.sync_replicas_to_aggregate, - 'params': self.params - } - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return Optimizer.objects.create(**config_dict) - - def to_config(self): - return plx.configs.OptimizerConfig(**self.to_dict()) - - -class Bridge(DiffModel): - """The `Bridge` model serializes the module and parameters to define a generator's bridge.""" - BRIDGES = ((obj, obj) for obj in plx.bridges.BRIDGES.keys()) - - module = models.CharField(max_length=256, choices=BRIDGES) - state_size = models.CharField(max_length=64, null=True, blank=True) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return { - 'module': self.module, - 'state_size': self.state_size, - 'params': self.params - } - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return Bridge.objects.create(**config_dict) - - def to_config(self): - return plx.configs.SubGraphConfig(**self.to_dict()) - - -class SubGraph(DiffModel): - """The `SugGraph` model serializes the definition of computation graph.""" - definition = models.TextField() - - def to_dict(self): - return {'definition': self.definition} - - def __str__(self): - return '{}'.format(self.id) - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return SubGraph.objects.create(definition=json.dumps(config_dict)) - - def to_config(self): - return plx.configs.SubGraphConfig.read_configs(json.loads(self.definition)) - - -class Encoder(DiffModel): - """The `Encoder` model serializes the definition of a generator's encoder graph.""" - ENCODERS = ((obj, obj) for obj in plx.encoders.ENCODERS.keys()) - - module = models.CharField(max_length=256, choices=ENCODERS, null=True, blank=True) - definition = models.TextField() - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return {'module': self.module, 'definition': self.definition} - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - module = config_dict.pop('module', None) - return Encoder.objects.create(module=module, definition=json.dumps(config_dict)) - - def to_config(self): - return plx.configs.SubGraphConfig.read_configs([{'module': self.module}, - json.loads(self.definition)]) - - -class Decoder(DiffModel): - """The `Encoder` model serializes the definition of a generator's decoder graph.""" - DECODERS = ((obj, obj) for obj in plx.decoders.DECODERS.keys()) - - module = models.CharField(max_length=256, choices=DECODERS, null=True, blank=True) - definition = models.TextField() - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return {'module': self.module, 'definition': self.definition} - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - module = config_dict.pop('module', None) - return Decoder.objects.create(module=module, definition=json.dumps(config_dict)) - - def to_config(self): - return plx.configs.SubGraphConfig.read_configs([{'module': self.module}, - json.loads(self.definition)]) - - -class PolyaxonModel(DiffModel): - """The `PolyaxonModel` model serializes the params to define a deep learning model.""" - MODELS = ((obj, obj) for obj in plx.models.MODELS.keys()) - SUMMARIES = ((obj, obj) for obj in SummaryOptions.VALUES) - - module = models.CharField(max_length=256, choices=MODELS) - summaries = ArrayField(base_field=models.CharField(max_length=256, choices=SUMMARIES), - default=['all']) - loss = models.ForeignKey(Loss, null=True, blank=True) - eval_metrics = models.ManyToManyField(Metric) - optimizer = models.ForeignKey(Optimizer, null=True, blank=True) - graph = models.ForeignKey(SubGraph, null=True, blank=True) - encoder = models.ForeignKey(Encoder, null=True, blank=True) - decoder = models.ForeignKey(Decoder, null=True, blank=True) - bridge = models.ForeignKey(Bridge, null=True, blank=True) - clip_gradients = models.FloatField(default=5.0) - clip_embed_gradients = models.FloatField(default=0.1) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return { - 'module': self.module, - 'summaries': self.summaries, - 'clip_gradients': self.clip_gradients, - 'clip_embed_gradients': self.clip_embed_gradients, - 'loss': self.loss.to_dict() if self.loss else None, - 'eval_metrics': [m.to_dict() for m in self.eval_metrics.all()], - 'optimizer': self.optimizer.to_dict() if self.optimizer else None, - 'graph': self.graph.to_dict() if self.graph else None, - 'encoder': self.encoder.to_dict() if self.encoder else None, - 'decoder': self.decoder.to_dict() if self.decoder else None, - 'bridge': self.bridge.to_dict() if self.bridge else None, - 'params': self.params, - } - - @staticmethod - def from_config(config): - if not config: - return None - params = {} - fields = [f.name for f in PolyaxonModel._meta.get_fields()] - config_dict = config.to_dict() - if not isinstance(config_dict.get('summaries', []), list): - config_dict['summaries'] = [config_dict['summaries']] - - config_dict['params'] = params - config_dict['loss'] = Loss.from_config(config.loss_config) - config_dict.pop('loss_config', None) - config_dict['optimizer'] = Optimizer.from_config(config.optimizer_config) - config_dict.pop('optimizer_config', None) - config_dict['graph'] = SubGraph.from_config(config.graph_config) - config_dict.pop('graph_config', None) - config_dict['encoder'] = Encoder.from_config(config.encoder_config) - config_dict.pop('encoder_config', None) - config_dict['decoder'] = Decoder.from_config(config.decoder_config) - config_dict.pop('decoder_config', None) - config_dict['bridge'] = Bridge.from_config(config.bridge_config) - config_dict.pop('bridge_config', None) - - # also remove eval_metrics_config - config_dict.pop('eval_metrics_config', None) - - # Rest of the keys should go to params - keys = list(config_dict.keys()) - - for key in keys: - if key not in fields: - params[key] = config_dict.pop(key) - - config_dict['params'] = params - - config_dict = remove_empty_keys(config_dict) - model = PolyaxonModel.objects.create(**config_dict) - model.eval_metrics = [Metric.from_config(m) for m in config.eval_metrics_config] - return model - - def to_config(self): - config_dict = { - 'module': self.module, - 'summaries': self.summaries, - 'clip_gradients': self.clip_gradients, - 'clip_embed_gradients': self.clip_embed_gradients, - 'loss_config': self.loss.to_config() if self.loss else None, - 'eval_metrics_config': [m.to_config() for m in self.eval_metrics.all()], - 'optimizer_config': self.optimizer.to_config() if self.optimizer else None, - 'graph_config': self.graph.to_config() if self.graph else None, - 'encoder_config': self.encoder.to_config() if self.encoder else None, - 'decoder_config': self.decoder.to_config() if self.decoder else None, - 'bridge_config': self.bridge.to_config() if self.bridge else None, - } - config_dict.update(self.params) - return plx.configs.ModelConfig(**config_dict) - - -class Estimator(DiffModel): - """The `Estimator` model serializes the params to define a polyaxon estimator.""" - ESTIMATORS = ((obj, obj) for obj in plx.estimators.ESTIMATORS.keys()) - - module = models.CharField(max_length=256, choices=ESTIMATORS) - output_dir = models.CharField(max_length=256, blank=True, null=True) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return {'module': self.module, 'output_dir': self.output_dir, 'params': self.params} - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return Estimator.objects.create(**config_dict) - - def to_config(self): - return plx.configs.EstimatorConfig(**self.to_dict()) - - -class Agent(DiffModel): - """The `Agent` model serializes the params to define a reinforcement learning agent.""" - AGENTS = ((obj, obj) for obj in plx.estimators.AGENTS.keys()) - - module = models.CharField(max_length=256, choices=AGENTS) - memory = models.ForeignKey(AgentMemory) - output_dir = models.CharField(max_length=256, blank=True, null=True) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return {'module': self.module, - 'memory': self.memory.to_dict(), - 'output_dir': self.output_dir, - 'params': self.params} - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict.pop('memory_config', None) - config_dict['memory'] = AgentMemory.from_config(config.memory_config) - config_dict = remove_empty_keys(config_dict) - return Agent.objects.create(**config_dict) - - def to_config(self): - config_dict = { - 'module': self.module, - 'memory_config': self.memory.to_config(), - 'output_dir': self.output_dir, - 'params': self.params} - return plx.configs.EstimatorConfig(**config_dict) - - -class Environment(DiffModel): - """The `Environment` model serializes a the module and params to define a reinforcement - learning environment. - """ - ENVIRONMENTS = ((obj, obj) for obj in plx.rl.environments.ENVIRONMENTS.keys()) - - module = models.CharField(max_length=256, choices=ENVIRONMENTS) - env_id = models.CharField(max_length=256) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return '{}-{}'.format(self.module, self.id) - - def to_dict(self): - return {'module': self.module, 'env_id': self.env_id, 'params': self.params} - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return Environment.objects.create(**config_dict) - - def to_config(self): - return plx.configs.EnvironmentConfig(**self.to_dict()) - - -class Pipeline(DiffModel): - """The `Pipeline` model serializes the params to define an experiment data pipeline.""" - PIPELINES = ((obj, obj) for obj in plx.processing.pipelines.PIPELINES.keys()) - - module = models.CharField(max_length=256, choices=PIPELINES, null=True, blank=True) - name = models.CharField(max_length=256, blank=True, null=True) - definition = models.TextField() - dynamic_pad = models.BooleanField(default=True) - bucket_boundaries = models.BooleanField(default=False) - batch_size = models.IntegerField(default=64) - num_epochs = models.IntegerField(default=1) - min_after_dequeue = models.IntegerField(default=5000) - num_threads = models.IntegerField(default=3) - shuffle = models.BooleanField(default=False) - allow_smaller_final_batch = models.BooleanField(default=True) - params = JSONField(blank=True, null=True, default={}) - - def __str__(self): - return ('{}-{}-{}'.format(self.module, self.name, self.id) if self.name - else '{}-{}'.format(self.module, self.id)) - - def to_dict(self): - kwargs = { - 'module': self.module, - 'name': self.name, - 'definition': self.definition, - 'dynamic_pad': self.dynamic_pad, - 'bucket_boundaries': self.bucket_boundaries, - 'batch_size': self.batch_size, - 'num_epochs': self.num_epochs, - 'min_after_dequeue': self.min_after_dequeue, - 'num_threads': self.num_threads, - 'shuffle': self.shuffle, - 'allow_smaller_final_batch': self.allow_smaller_final_batch, - 'params': self.params - } - return kwargs - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict['definition'] = json.dumps(config_dict.get('definition', {})) - config_dict = remove_empty_keys(config_dict) - pipeline = Pipeline.objects.create(**config_dict) - - return pipeline - - def to_config(self): - config_dict = self.to_dict() - config_dict['definition'] = json.loads(config_dict.get('definition', {})) - return plx.configs.PipelineConfig.read_configs(config_dict) - - -class InputData(DiffModel): - """The `InputData` model serializes the params to define an experiment input data flow.""" - INPUT_DATA_TYPES = ( - ('NUMPY', 'NUMPY'), - ('PANDAS', 'PANDAS') - ) - - input_type = models.CharField(max_length=256, choices=INPUT_DATA_TYPES, blank=True, null=True) - pipeline = models.ForeignKey(Pipeline) - - def __str__(self): - return '{}-{}'.format(self.input_type, self.pipeline) - - def to_dict(self): - return { - 'input_type': self.input_type, - 'pipeline': self.pipeline.to_dict() - } - - @staticmethod - def from_config(config): - if not config: - return None - - return InputData.objects.create( - input_type=config.input_type, - pipeline=Pipeline.from_config(config.pipeline_config)) - - def to_config(self): - return plx.configs.InputDataConfig(input_type=self.input_type, - pipeline_config=self.pipeline.to_config()) - - -class RunConfig(DiffModel): - """The `RunConfig` model serializes the params to define a tensorflow run config.""" - model_dir = models.CharField(max_length=256, blank=True, null=True) - master = models.CharField(max_length=256, blank=True, null=True) - num_cores = models.IntegerField(default=0) - log_device_placement = models.BooleanField(default=False) - gpu_memory_fraction = models.FloatField(default=1.0) - tf_random_seed = models.IntegerField(blank=True, null=True) - save_summary_steps = models.IntegerField(default=100, null=True, blank=True) - save_checkpoints_secs = models.IntegerField(default=600, null=True, blank=True) - save_checkpoints_steps = models.IntegerField(blank=True, null=True) - keep_checkpoint_max = models.IntegerField(default=5, null=True, blank=True) - keep_checkpoint_every_n_hours = models.IntegerField(default=10000, null=True, blank=True) - evaluation_master = models.CharField(max_length=256, blank=True, null=True, default='') - gpu_allow_growth = models.BooleanField(default=False) - cluster_config = JSONField(blank=True, null=True, default={}) - - def to_dict(self): - return { - 'master': self.master, - 'num_cores': self.num_cores, - 'log_device_placement': self.log_device_placement, - 'gpu_memory_fraction': self.gpu_memory_fraction, - 'tf_random_seed': self.tf_random_seed, - 'save_summary_steps': self.save_summary_steps, - 'save_checkpoints_secs': self.save_checkpoints_secs, - 'save_checkpoints_steps': self.save_checkpoints_steps, - 'keep_checkpoint_max': self.keep_checkpoint_max, - 'keep_checkpoint_every_n_hours': self.keep_checkpoint_every_n_hours, - 'evaluation_master': self.evaluation_master, - 'gpu_allow_growth': self.gpu_allow_growth, - 'cluster_config': self.cluster_config - } - - def __str__(self): - return '{}'.format(self.id) - - @staticmethod - def from_config(config): - if not config: - return None - - config_dict = config.to_dict() - config_dict = remove_empty_keys(config_dict) - return RunConfig.objects.create(**config_dict) - - def to_config(self): - return plx.configs.RunConfig.read_configs(self.to_dict()) - - -class Experiment(DiffModel): - """The `Experiment` model serializes an the params to define a a polyaxon experiment.""" - HOOK_OPTIONS = ((obj, obj) for obj in HOOKS.keys()) - - # shared attributes - name = models.CharField(max_length=256) - output_dir = models.CharField(max_length=256) - run_config = models.ForeignKey(RunConfig) - model = models.ForeignKey(PolyaxonModel) - train_hooks = ArrayField(base_field=models.CharField(max_length=256, choices=HOOK_OPTIONS), - blank=True, null=True, default=[]) - eval_hooks = ArrayField(base_field=models.CharField(max_length=256, choices=HOOK_OPTIONS), - blank=True, null=True, default=[]) - eval_metrics = models.ManyToManyField(Metric, blank=True) - eval_every_n_steps = models.IntegerField(default=1000) - train_steps = models.IntegerField(default=10000) - eval_steps = models.IntegerField(default=10) - eval_delay_secs = models.IntegerField(default=0) - continuous_eval_throttle_secs = models.IntegerField(default=60) - delay_workers_by_global_step = models.BooleanField(default=False) - export_strategies = models.CharField(max_length=256, blank=True, null=True) - train_steps_per_iteration = models.IntegerField(default=1000) - - # experiment attributes - is_rl = models.BooleanField(default=False) - estimator = models.ForeignKey(Estimator, null=True, blank=True) - train_input_data = models.ForeignKey(InputData, related_name='train', null=True, blank=True) - eval_input_data = models.ForeignKey(InputData, related_name='eval', null=True, blank=True) - - # rl experiment attributes - agent = models.ForeignKey(Agent, null=True, blank=True) - environment = models.ForeignKey(Environment, null=True, blank=True) - - def __str__(self): - return '{}-{}'.format(self.name, self.id) - - def to_dict(self): - kwargs = { - 'name': self.name, - 'output_dir': self.output_dir, - 'run_config': self.run_config.to_dict(), - 'model': self.model.to_dict(), - 'train_hooks': self.train_hooks, - 'eval_hooks': self.eval_hooks, - 'eval_metrics': [m.to_dict() for m in self.eval_metrics.all()], - 'eval_every_n_steps': self.eval_every_n_steps, - 'train_steps': self.train_steps, - 'eval_steps': self.eval_steps, - 'eval_delay_secs': self.eval_delay_secs, - 'continuous_eval_throttle_secs': self.continuous_eval_throttle_secs, - 'delay_workers_by_global_step': self.delay_workers_by_global_step, - 'train_steps_per_iteration': self.train_steps_per_iteration - } - - if self.is_rl: - kwargs['agent'] = self.estimator.to_dict() - kwargs['env'] = self.env.to_dict() - else: - kwargs['estimator'] = self.estimator.to_dict() - kwargs['train_input_data'] = self.train_input_data.to_dict() - kwargs['eval_input_data'] = self.eval_input_data.to_dict() - - return kwargs - - @staticmethod - def _base_from_config(config): - config_dict = config.to_dict() - config_dict['run_config'] = RunConfig.from_config(config.run_config) - config_dict['model'] = PolyaxonModel.from_config(config.model_config) - config_dict.pop('model_config', None) - config_dict['eval_metrics'] = [Metric.from_config(m) - for m in config.eval_metrics_config] - config_dict.pop('eval_metrics_config', None) - config_dict['train_hooks'] = config_dict.pop('train_hooks_config', []) - config_dict['eval_hooks'] = config_dict.pop('eval_hooks_config', []) - return config_dict - - @classmethod - def _experiment_from_dict(cls, config): - config_dict = cls._base_from_config(config) - config_dict['estimator'] = Estimator.from_config(config.estimator_config) - config_dict.pop('estimator_config', None) - config_dict['train_input_data'] = InputData.from_config(config.train_input_data_config) - config_dict.pop('train_input_data_config', None) - config_dict['eval_input_data'] = InputData.from_config(config.eval_input_data_config) - config_dict.pop('eval_input_data_config', None) - eval_metrics = config_dict.pop('eval_metrics') - - config_dict = remove_empty_keys(config_dict) - exp = Experiment.objects.create(**config_dict) - exp.eval_metrics = eval_metrics - return exp - - @classmethod - def _rl_experiment_from_config(cls, config): - config_dict = cls._base_from_config(config) - config_dict['is_rl'] = True - config_dict['agent'] = Agent.from_config(config.agent_config) - config_dict.pop('agent_config', None) - config_dict['environment'] = Environment.from_config(config.environment_config) - config_dict.pop('environment_config', None) - eval_metrics = config_dict.pop('eval_metrics') - - config_dict = remove_empty_keys(config_dict) - exp = Experiment.objects.create(**config_dict) - exp.eval_metrics = eval_metrics - return exp - - @classmethod - def from_config(cls, config): - if isinstance(config, plx.configs.RLExperimentConfig): - return cls._rl_experiment_from_config(config) - return cls._experiment_from_dict(config) - - def to_config(self): - to_config = { - 'name': self.name, - 'output_dir': self.output_dir, - 'run_config': self.run_config.to_config(), - 'model_config': self.model.to_config(), - 'train_hooks_config': self.train_hooks, - 'eval_hooks_config': self.eval_hooks, - 'eval_metrics_config': [m.to_config() for m in self.eval_metrics.all()], - 'eval_every_n_steps': self.eval_every_n_steps, - 'train_steps': self.train_steps, - 'eval_steps': self.eval_steps, - 'eval_delay_secs': self.eval_delay_secs, - 'continuous_eval_throttle_secs': self.continuous_eval_throttle_secs, - 'delay_workers_by_global_step': self.delay_workers_by_global_step, - 'train_steps_per_iteration': self.train_steps_per_iteration - } - if self.is_rl: - to_config['agent_config'] = self.agent.to_config() - to_config['environment_config'] = self.environment.to_config() - return plx.configs.RLExperimentConfig(**to_config) - else: - to_config['estimator_config'] = self.estimator.to_config() - to_config['train_input_data_config'] = self.train_input_data.to_config() - to_config['eval_input_data_config'] = self.eval_input_data.to_config() - return plx.configs.ExperimentConfig(**to_config) diff --git a/api/core/serialiazers.py b/api/core/serialiazers.py deleted file mode 100644 index 2d3d4aa..0000000 --- a/api/core/serialiazers.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division, print_function - -from rest_framework import serializers - -from core.models import ( - Experiment, - RunConfig, - InputData, - Pipeline, - Environment, - Agent, - Estimator, - PolyaxonModel, - Decoder, - Encoder, - SubGraph, - Bridge, - Optimizer, - AgentMemory, - Metric, - Loss, -) - - -class LossSerializer(serializers.ModelSerializer): - class Meta: - model = Loss - fields = ('id', 'module') - - -class LossDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Loss - fields = '__all__' - - -class MetricSerializer(serializers.ModelSerializer): - class Meta: - model = Metric - fields = ('id', 'module') - - -class MetricDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Metric - fields = '__all__' - - -class AgentMemorySerializer(serializers.ModelSerializer): - class Meta: - model = AgentMemory - fields = ('id', 'module') - - -class AgentMemoryDetailSerializer(serializers.ModelSerializer): - class Meta: - model = AgentMemory - fields = '__all__' - - -class OptimizerSerializer(serializers.ModelSerializer): - class Meta: - model = Optimizer - fields = ('id', 'module') - - -class OptimizerDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Optimizer - fields = '__all__' - - -class BridgeSerializer(serializers.ModelSerializer): - class Meta: - model = Bridge - fields = ('id', 'module') - - -class BridgeDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Bridge - fields = '__all__' - - -class SubGraphSerializer(serializers.ModelSerializer): - class Meta: - model = SubGraph - fields = ('id', 'module') - - -class SubGraphDetailSerializer(serializers.ModelSerializer): - class Meta: - model = SubGraph - fields = '__all__' - - -class EncoderSerializer(serializers.ModelSerializer): - class Meta: - model = Encoder - fields = ('id', 'module') - - -class EncoderDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Encoder - fields = '__all__' - - -class DecoderSerializer(serializers.ModelSerializer): - class Meta: - model = Decoder - fields = ('id', 'module') - - -class DecoderDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Decoder - fields = '__all__' - - -class PolyaxonModelSerializer(serializers.ModelSerializer): - class Meta: - model = PolyaxonModel - fields = ('id', 'module') - - -class PolyaxonModelDetailSerializer(serializers.ModelSerializer): - loss = LossDetailSerializer() - eval_metrics = MetricDetailSerializer(many=True) - optimizer = OptimizerDetailSerializer() - graph = SubGraphDetailSerializer() - encoder = EncoderDetailSerializer() - decoder = DecoderDetailSerializer() - bridge = BridgeDetailSerializer() - - class Meta: - model = PolyaxonModel - fields = '__all__' - - -class EstimatorSerializer(serializers.ModelSerializer): - class Meta: - model = Estimator - fields = ('id', 'module') - - -class EstimatorDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Estimator - fields = '__all__' - - -class AgentSerializer(serializers.ModelSerializer): - class Meta: - model = Agent - fields = ('id', 'module') - - -class AgentDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Agent - fields = '__all__' - - -class EnvironmentSerializer(serializers.ModelSerializer): - class Meta: - model = Environment - fields = ('id', 'module') - - -class EnvironmentDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Environment - fields = '__all__' - - -class PipelineSerializer(serializers.ModelSerializer): - class Meta: - model = Pipeline - fields = ('id', 'module') - - -class PipelineDataDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Pipeline - fields = '__all__' - - -class InputDataSerializer(serializers.ModelSerializer): - class Meta: - model = InputData - fields = ('id',) - - -class InputDataDetailSerializer(serializers.ModelSerializer): - class Meta: - model = InputData - fields = '__all__' - - -class RunConfigSerializer(serializers.ModelSerializer): - class Meta: - model = RunConfig - fields = ('id',) - - -class RunConfigDetailSerializer(serializers.ModelSerializer): - class Meta: - model = RunConfig - fields = '__all__' - - -class ExperimentSerializer(serializers.ModelSerializer): - class Meta: - model = Experiment - fields = ('id', 'name', 'created_at', 'updated_at', ) - - -class ExperimentDetailSerializer(serializers.ModelSerializer): - class Meta: - model = Experiment - fields = '__all__' - - -class StatusSerializer(serializers.Serializer): - job_id = serializers.CharField() - status = serializers.CharField() diff --git a/api/core/urls.py b/api/core/urls.py deleted file mode 100644 index 3ed2dcb..0000000 --- a/api/core/urls.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division, print_function - -from django.conf.urls import url -from rest_framework.urlpatterns import format_suffix_patterns - -from core import views - -urlpatterns = [ - url(r'^experiments/?$', views.ExperimentListView.as_view()), - url(r'^experiments/(?P[0-9]+)/?$', views.ExperimentDetailView.as_view()), - url(r'^experiments/(?P[0-9]+)/estimator/?$', views.ExperimentEstimatorDetailView.as_view()), - url(r'^experiments/(?P[0-9]+)/model/?$', views.ExperimentModelDetailView.as_view()), - url(r'^experiments/(?P[0-9]+)/start/?$', views.ExperimentStartView.as_view()), - url(r'^experiments/(?P[0-9]+)/status/?$', views.ExperimentStartView.as_view()), - url(r'^estimators/?$', views.EstimatorListView.as_view()), - url(r'^estimators/(?P[0-9]+)/?$', views.EstimatorDetailView.as_view()), - url(r'^models/?$', views.PolyaxonModelListView.as_view()), - url(r'^models/(?P[0-9]+)/?$', views.PolyaxonModelDetailView.as_view()), -] - -urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/api/core/views.py b/api/core/views.py deleted file mode 100644 index 40070bf..0000000 --- a/api/core/views.py +++ /dev/null @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division, print_function - -from rest_framework import status -from rest_framework.generics import ListAPIView, RetrieveAPIView, CreateAPIView -from rest_framework.response import Response - -from core.models import Experiment, PolyaxonModel, Estimator -from core.serialiazers import ( - ExperimentSerializer, - ExperimentDetailSerializer, - PolyaxonModelSerializer, - PolyaxonModelDetailSerializer, - EstimatorSerializer, - EstimatorDetailSerializer, - StatusSerializer) -from core.tasks import start_experiment, get_experiment_run_status - - -class ExperimentListView(ListAPIView): - queryset = Experiment.objects.all() - serializer_class = ExperimentSerializer - - -class ExperimentDetailView(RetrieveAPIView): - queryset = Experiment.objects.all() - serializer_class = ExperimentDetailSerializer - - -class ExperimentEstimatorDetailView(RetrieveAPIView): - queryset = Experiment.objects.all() - serializer_class = EstimatorDetailSerializer - - def get_object(self): - obj = super(ExperimentEstimatorDetailView, self).get_object() - return obj.estimator - - -class ExperimentModelDetailView(RetrieveAPIView): - queryset = Experiment.objects.all() - serializer_class = PolyaxonModelDetailSerializer - - def get_object(self): - obj = super(ExperimentModelDetailView, self).get_object() - return obj.model - - -class ExperimentStartView(CreateAPIView, RetrieveAPIView): - queryset = Experiment.objects.all() - serializer_class = StatusSerializer - - def retrieve(self, request, *args, **kwargs): - obj = self.get_object() - serializer = self.get_serializer(get_experiment_run_status(obj)) - return Response(serializer.data, status=status.HTTP_200_OK) - - def post(self, request, *args, **kwargs): - obj = self.get_object() - job_info = start_experiment(obj) - if job_info['status'] == 'PENDING': - return Response(status=status.HTTP_201_CREATED, data=job_info) - return Response(job_info, status=status.HTTP_200_OK) - - -class EstimatorListView(ListAPIView): - queryset = Estimator.objects.all() - serializer_class = EstimatorSerializer - - -class EstimatorDetailView(RetrieveAPIView): - queryset = Estimator.objects.all() - serializer_class = EstimatorDetailSerializer - - -class PolyaxonModelListView(ListAPIView): - queryset = PolyaxonModel.objects.all() - serializer_class = PolyaxonModelSerializer - - -class PolyaxonModelDetailView(RetrieveAPIView): - queryset = PolyaxonModel.objects.all() - serializer_class = PolyaxonModelDetailSerializer diff --git a/api/core/__init__.py b/api/experiments/__init__.py similarity index 100% rename from api/core/__init__.py rename to api/experiments/__init__.py diff --git a/api/experiments/admin.py b/api/experiments/admin.py new file mode 100644 index 0000000..a1a21b4 --- /dev/null +++ b/api/experiments/admin.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + +from django.contrib import admin + +from experiments.models import Experiment + +admin.site.register(Experiment) diff --git a/api/experiments/apps.py b/api/experiments/apps.py new file mode 100644 index 0000000..ab8e2f6 --- /dev/null +++ b/api/experiments/apps.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + +from django.apps import AppConfig + + +class ExperimentsConfig(AppConfig): + name = 'Experiments' + verbose_name = "Experiments" diff --git a/api/experiments/models.py b/api/experiments/models.py new file mode 100644 index 0000000..4540e56 --- /dev/null +++ b/api/experiments/models.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + +import uuid + +from django.contrib.postgres.fields import JSONField +from django.db import models + +from libs.models import DiffModel +from projects.models import PolyaxonFile + + +class Experiment(DiffModel): + """A model that represents experiments created through a polyaxonfile.""" + uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, null=False) + name = models.CharField(max_length=256, blank=True, null=True, + help_text='Name of the experiment') + description = models.TextField(blank=True, null=True, + help_text='Description of the experiment.') + polyaxonfile = models.ForeignKey(PolyaxonFile, + help_text='The polyaxonfile that generate this experiment.') + compiled_polyaxonfile = JSONField(help_text='The compiled polyaxon with specific values ' + 'for this experiment.') diff --git a/api/experiments/serialiazers.py b/api/experiments/serialiazers.py new file mode 100644 index 0000000..0714e74 --- /dev/null +++ b/api/experiments/serialiazers.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + +from rest_framework import serializers + +from experiments.models import Experiment + + +class ExperimentSerializer(serializers.ModelSerializer): + class Meta: + model = Experiment + fields = ('id', 'name', 'created_at', 'updated_at', ) + + +class ExperimentDetailSerializer(serializers.ModelSerializer): + class Meta: + model = Experiment + fields = '__all__' + + +class StatusSerializer(serializers.Serializer): + job_id = serializers.CharField() + status = serializers.CharField() diff --git a/api/core/task_status.py b/api/experiments/task_status.py similarity index 100% rename from api/core/task_status.py rename to api/experiments/task_status.py diff --git a/api/core/tasks.py b/api/experiments/tasks.py similarity index 87% rename from api/core/tasks.py rename to api/experiments/tasks.py index c7c7d53..8da81cb 100644 --- a/api/core/tasks.py +++ b/api/experiments/tasks.py @@ -7,11 +7,11 @@ from api.settings import CeleryTasks from api.celery_api import app -from core.models import Experiment -from core.task_status import ExperimentStatus +from experiments.models import Experiment +from experiments.task_status import ExperimentStatus -logger = logging.getLogger('polyaxon.api.core') +logger = logging.getLogger('polyaxon.api.experiments') def get_experiment_run_status(experiment): diff --git a/api/experiments/urls.py b/api/experiments/urls.py new file mode 100644 index 0000000..7cd4cc9 --- /dev/null +++ b/api/experiments/urls.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + +from django.conf.urls import url +from rest_framework.urlpatterns import format_suffix_patterns + +from experiments import views + +urlpatterns = [ + url(r'^experiments/?$', views.ExperimentListView.as_view()), + url(r'^experiments/(?P\w+)/?$', views.ExperimentDetailView.as_view()), + url(r'^experiments/(?P\w+)/start/?$', views.ExperimentStartView.as_view()), + url(r'^experiments/(?P\w+)/status/?$', views.ExperimentStartView.as_view()), +] + +urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/api/core/utils.py b/api/experiments/utils.py similarity index 67% rename from api/core/utils.py rename to api/experiments/utils.py index 90f7e02..98dc01f 100644 --- a/api/core/utils.py +++ b/api/experiments/utils.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + + def remove_empty_keys(config_dict): keys = list(config_dict.keys()) for key in keys: diff --git a/api/experiments/views.py b/api/experiments/views.py new file mode 100644 index 0000000..897f5a5 --- /dev/null +++ b/api/experiments/views.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + +from rest_framework import status +from rest_framework.generics import ListAPIView, RetrieveAPIView, CreateAPIView +from rest_framework.response import Response + +from experiments.models import Experiment +from experiments.serialiazers import ( + ExperimentSerializer, + ExperimentDetailSerializer, + StatusSerializer) +from experiments.tasks import start_experiment, get_experiment_run_status + + +class ExperimentListView(ListAPIView): + queryset = Experiment.objects.all() + serializer_class = ExperimentSerializer + + +class ExperimentDetailView(RetrieveAPIView): + queryset = Experiment.objects.all() + serializer_class = ExperimentDetailSerializer + lookup_field = 'uuid' + + +class ExperimentStartView(CreateAPIView, RetrieveAPIView): + queryset = Experiment.objects.all() + serializer_class = StatusSerializer + lookup_field = 'uuid' + + def retrieve(self, request, *args, **kwargs): + obj = self.get_object() + serializer = self.get_serializer(get_experiment_run_status(obj)) + return Response(serializer.data, status=status.HTTP_200_OK) + + def post(self, request, *args, **kwargs): + obj = self.get_object() + job_info = start_experiment(obj) + if job_info['status'] == 'PENDING': + return Response(status=status.HTTP_201_CREATED, data=job_info) + return Response(job_info, status=status.HTTP_200_OK) diff --git a/api/libs/apps.py b/api/libs/apps.py new file mode 100644 index 0000000..d6e7af3 --- /dev/null +++ b/api/libs/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ProjectsConfig(AppConfig): + name = 'Libs' + verbose_name = 'Libs' diff --git a/api/projects/apps.py b/api/projects/apps.py new file mode 100644 index 0000000..aec7f4f --- /dev/null +++ b/api/projects/apps.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, print_function + +from django.apps import AppConfig + + +class ProjectsConfig(AppConfig): + name = 'Projects' + verbose_name = 'Projects' diff --git a/api/projects/migrations/0001_initial.py b/api/projects/migrations/0001_initial.py deleted file mode 100644 index cca7e21..0000000 --- a/api/projects/migrations/0001_initial.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.6 on 2017-10-08 20:07 -from __future__ import unicode_literals - -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Experiment', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ('name', models.CharField(blank=True, help_text='Name of the experiment', max_length=256, null=True)), - ('description', models.TextField(blank=True, help_text='Description of the experiment.', null=True)), - ('compiled_polyaxonfile', django.contrib.postgres.fields.jsonb.JSONField(help_text='The compiled polyaxon with specific values for this experiment.')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='PolyaxonFile', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ('content', models.TextField(help_text='The yaml content of the polyaxonfile.')), - ], - options={ - 'abstract': False, - }, - ), - migrations.CreateModel( - name='Project', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, db_index=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), - ('name', models.CharField(help_text='Name of the project.', max_length=256)), - ('description', models.TextField(blank=True, help_text='Description of the project.', null=True)), - ('is_public', models.BooleanField(default=True, help_text='If project is public or private.')), - ], - options={ - 'abstract': False, - }, - ), - migrations.AddField( - model_name='polyaxonfile', - name='project', - field=models.ForeignKey(help_text='The project this polyaxonfile belongs to.', on_delete=django.db.models.deletion.CASCADE, related_name='polyaxonfile', to='projects.Project'), - ), - migrations.AddField( - model_name='experiment', - name='polyaxonfile', - field=models.ForeignKey(help_text='The polyaxonfile that generate this experiment.', on_delete=django.db.models.deletion.CASCADE, to='projects.PolyaxonFile'), - ), - ] diff --git a/api/projects/models.py b/api/projects/models.py index 6144131..55a922b 100644 --- a/api/projects/models.py +++ b/api/projects/models.py @@ -3,7 +3,6 @@ import uuid -from django.contrib.postgres.fields import JSONField from django.db import models from libs.models import DiffModel @@ -38,15 +37,3 @@ class PolyaxonFile(DiffModel): @property def experiments(self): return self.experiment.all() - - -class Experiment(DiffModel): - """A model that represents experiments created through a polyaxonfile.""" - uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, null=False) - name = models.CharField(max_length=256, blank=True, null=True, - help_text='Name of the experiment') - description = models.TextField(blank=True, null=True, help_text='Description of the experiment.') - polyaxonfile = models.ForeignKey(PolyaxonFile, - help_text='The polyaxonfile that generate this experiment.') - compiled_polyaxonfile = JSONField(help_text='The compiled polyaxon with specific values ' - 'for this experiment.') diff --git a/api/projects/serialiazers.py b/api/projects/serialiazers.py index 47f66a5..2d62e9e 100644 --- a/api/projects/serialiazers.py +++ b/api/projects/serialiazers.py @@ -3,13 +3,8 @@ from rest_framework import serializers -from projects.models import Project, Experiment - - -class ExperimentSerializer(serializers.ModelSerializer): - class Meta: - model = Experiment - fields = '__all__' +from experiments.serialiazers import ExperimentSerializer +from projects.models import Project class ProjectSerializer(serializers.ModelSerializer): diff --git a/api/projects/urls.py b/api/projects/urls.py index 1860185..ffe3229 100644 --- a/api/projects/urls.py +++ b/api/projects/urls.py @@ -8,7 +8,7 @@ urlpatterns = [ url(r'^projects/?$', views.ProjectListView.as_view()), - url(r'^projects/(?P[0-9]+)/?$', views.ProjectDetailView.as_view()), + url(r'^projects/(?P[0-9]+)/?$', views.ProjectDetailView.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/api/projects/views.py b/api/projects/views.py index caa8488..eb39148 100644 --- a/api/projects/views.py +++ b/api/projects/views.py @@ -2,8 +2,12 @@ from __future__ import absolute_import, division, print_function from rest_framework.generics import RetrieveAPIView, ListCreateAPIView + from projects.models import Project -from projects.serialiazers import ProjectSerializer, ProjectDetailSerializer +from projects.serialiazers import ( + ProjectSerializer, + ProjectDetailSerializer, +) class ProjectListView(ListCreateAPIView): @@ -14,3 +18,4 @@ class ProjectListView(ListCreateAPIView): class ProjectDetailView(RetrieveAPIView): queryset = Project.objects.all() serializer_class = ProjectDetailSerializer + lookup_field = 'uuid' diff --git a/tests/test_core/test_read_from_to_configs.py b/tests/test_core/test_read_from_to_configs.py index 8edba2a..e091854 100644 --- a/tests/test_core/test_read_from_to_configs.py +++ b/tests/test_core/test_read_from_to_configs.py @@ -5,7 +5,7 @@ import polyaxon as plx -from core.models import Experiment, Loss, Optimizer, SubGraph, PolyaxonModel +from experiments.models import Experiment, Loss, Optimizer, SubGraph, PolyaxonModel class TestCoreModels(TestCase):