<commit_before>import json
import typing
import uuid
from tahiti.models import *

class WorkflowService(object):
    def get_tasks_for_modeling(self, workflow: Workflow,
            task_type: str, method: str) -> typing.List[Task]:

        common = self._get_common_tasks(workflow)

        if task_type == 'clustering':
            return common + self._get_tasks_for_clustering(workflow, method)
        elif task_type == 'regression':
            return common + self._get_tasks_for_regression(workflow, method)
        elif task_type == 'classification':
            return common + self._get_tasks_for_classification(workflow, method)

        return common

    def _get_common_tasks(self, workflow: Workflow) -> typing.List[Task]:
        result = []
        split = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='split', Platform.id==1000).first()

        forms = json.dumps(
            {"strategy": {"value": "split"}, "ratio": {"value": "0.8"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=split, name=split.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        sample = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='sample', Platform.id==1000).first()

        forms = json.dumps(
            {"type": {"value": "value"}, "value": {"value": "10000"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=sample, name=sample.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))
        return result

    def _get_tasks_for_clustering(self, workflow: Workflow,
            method: str) -> typing.List[Task]:

        result = []
        evaluator = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='evaluator', Platform.id==1000).first()
        forms = json.dumps(
            {"task_type": {"value": "clustering"}, "clust_metric": {"value": "silhouette"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=evaluator, name=evaluator.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        grid = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='grid', Platform.id==1000).first()

        forms = json.dumps({"strategy": {"value": "grid"}})
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=grid, name=grid.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        k_means = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='k-means', Platform.id==1000).first()
        if k_means and method == 'fast':
            k = {"list": ["2", "4", "8", "16"], "type": "list"}
            alg_type = ["kmeans"]
            dist = ["euclidean"]
            init_mode = ["k-means||"]
            forms = json.dumps({
                "number_of_clusters": {
                    "value": k, "internalValue": k, "enabled": True},
                "type": {"value": alg_type, "internalValue": alg_type},
                "init_mode": {"value": init_mode, "internalValue": init_mode},
                "distance": {"value": dist, "internalValue": dist}
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=k_means, name=k_means.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        return result

    def _get_tasks_for_regression(self, workflow: Workflow,
            method: str) -> typing.List[Task]:

        result = []

        evaluator = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='evaluator', Platform.id==1000).first()
        forms = json.dumps(
            {"task_type": {"value": "regression"}, "reg_metric": {"value": "rmse"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=evaluator, name=evaluator.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        features = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='features', Platform.id==1000).first()

        workflow_form = json.loads(workflow.forms)

        forms = json.dumps(
                {"features": {"value": [
                    {"name": workflow_form.get('$meta').get('value').get('label'),
                        "usage": "label", "enable": True,
                        "feature_type": "numerical", "transform": "keep"}]}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=features, name=features.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        if method == 'fast':
            random_forest = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='random-forest-regressor', Platform.id==1000).first()

            trees = {"list": ["100"], "type": "list"}
            depth = {"list": ["2", "6"], "type": "list"}
            forms = json.dumps({
                "num_trees": {
                    "value": trees, "internalValue": trees},
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=random_forest, name=random_forest.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        elif method == 'interpretable':
            dt = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='decision-tree-regressor', Platform.id==1000).first()

            depth = {"list": ["6"], "type": "list"}
            forms = json.dumps({
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=dt, name=dt.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=3, z_index=3))
        elif method == 'performance':
            grid = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='grid', Platform.id==1000).first()

            forms = json.dumps({"strategy": {"value": "random"},
                "max_iterations": {"value": 30}})
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=grid, name=grid.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=1, z_index=0))

            random_forest = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='random-forest-regressor', Platform.id==1000).first()

            trees = {"type": "range", "min": 80, "max": 200, "distribution": "uniform"}
            depth = {"type": "range", "min": 5, "max": 10, "distribution": "uniform"}
            feat_subset = ["auto"]
            forms = json.dumps({
                "num_trees": {
                    "value": trees, "internalValue": trees},
                "max_depth": {"value": depth, "internalValue": depth},
                "feature_subset_strategy": {"value": feat_subset,
                    "internalValue": feat_subset},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=random_forest, name=random_forest.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

            gbt = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='gbt-regressor', Platform.id==1000).first()
            depth = {"type": "range", "min": 5, "max": 10, "distribution": "uniform"}
            forms = json.dumps({
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=gbt, name=gbt.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        if method in ('fast', 'interpretable'):
            linear_reg = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='linear-regression', Platform.id==1000).first()

            elastic_net = {"list": ["0.1", "1.0", "3.0"], "type": "list"}
            forms = json.dumps({
                "elastic_net": {"value": elastic_net, "internalValue": elastic_net},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=linear_reg, name=linear_reg.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=3, z_index=3))

            grid = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='grid', Platform.id==1000).first()

            forms = json.dumps({"strategy": {"value": "grid"}})
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=grid, name=grid.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=1, z_index=0))

        return result

    def _get_tasks_for_classification(self, workflow: Workflow,
            method: str) -> typing.List[Task]:

        result = []

        evaluator = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='evaluator', Platform.id==1000).first()
        forms = json.dumps(
            {"task_type": {"value": "multiclass-classification"},
                "multi_metric": {"value": "accuracy"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=evaluator, name=evaluator.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        features = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='features', Platform.id==1000).first()

        workflow_form = json.loads(workflow.forms)

        forms = json.dumps(
                {"features": {"value": [
                    {"name": workflow_form.get('$meta').get('value').get('label'),
                        "usage": "label", "enable": True,
                        "feature_type": "numerical", "transform": "keep"}]}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=features, name=features.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        if method == 'fast':
            random_forest = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='random-forest-classifier', Platform.id==1000).first()

            trees = {"list": ["100"], "type": "list"}
            depth = {"list": ["12", "6"], "type": "list"}
            forms = json.dumps({
                "num_trees": {
                    "value": trees, "internalValue": trees},
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=random_forest, name=random_forest.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        elif method == 'interpretable':
            dt = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='decision-tree-classifier', Platform.id==1000).first()

            depth = {"list": ["5"], "type": "list"}
            forms = json.dumps({
                "max_depth": {"value": depth, "internalValue": depth},
                "impurity": {"value": ["gini"], "internalValue": ["gini"]}
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=dt, name=dt.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=3, z_index=3))

        elif method == 'performance':
            grid = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='grid', Platform.id==1000).first()

            forms = json.dumps({"strategy": {"value": "random"},
                "max_iterations": {"value": 30}})
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=grid, name=grid.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=1, z_index=0))

            random_forest = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='random-forest-classifier', Platform.id==1000).first()

            trees = {"type": "range", "min": 80, "max": 200, "distribution": "uniform"}
            depth = {"type": "range", "min": 5, "max": 10, "distribution": "uniform"}
            feat_subset = ["auto"]
            forms = json.dumps({
                "num_trees": {
                    "value": trees, "internalValue": trees},
                "max_depth": {"value": depth, "internalValue": depth},
                "feature_subset_strategy": {"value": feat_subset,
                    "internalValue": feat_subset},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=random_forest, name=random_forest.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

            gbt = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='gbt-classifier', Platform.id==1000).first()
            depth = {"type": "range", "min": 5, "max": 10, "distribution": "uniform"}
            forms = json.dumps({
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=gbt, name=gbt.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

            log_reg = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='logistic-regression', Platform.id==1000).first()

            reg_param = {"elastic_net_param":
                    {"value": {"type": "range", "min": 0.01,
                        "max": 100, "distribution": "log_uniform"},
                        "internalValue": {"type": "range", "min": 0.01,
                            "max": 100, "distribution": "log_uniform"}}}
            forms = json.dumps({
                "reg_param": {"value": reg_param, "internalValue": reg_param},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=log_reg, name=log_reg.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        if method in ('fast', 'interpretable'):
            log_reg = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='logistic-regression', Platform.id==1000).first()

            reg_param = {"type": "list", "list": ["0.01", "0.1", "1", "10", "100"]}
            forms = json.dumps({
                "reg_param": {"value": reg_param, "internalValue": reg_param},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=log_reg, name=log_reg.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))
        return result
<commit_msg>Adjust workflow creation for model builder<commit_after>import json
import typing
import uuid
from tahiti.models import *

class WorkflowService(object):
    def get_tasks_for_modeling(self, workflow: Workflow,
            task_type: str, method: str) -> typing.List[Task]:

        common = self._get_common_tasks(workflow)

        if task_type == 'clustering':
            return common + self._get_tasks_for_clustering(workflow, method)
        elif task_type == 'regression':
            return common + self._get_tasks_for_regression(workflow, method)
        elif task_type == 'classification':
            return common + self._get_tasks_for_classification(workflow, method)

        return common

    def _get_common_tasks(self, workflow: Workflow) -> typing.List[Task]:
        result = []
        split = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='split', Platform.id==1000).first()

        forms = json.dumps(
            {"strategy": {"value": "split"}, "ratio": {"value": "0.8"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=split, name=split.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        sample = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='sample', Platform.id==1000).first()

        forms = json.dumps(
            {"type": {"value": "value"}, "value": {"value": "10000"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=sample, name=sample.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))
        return result

    def _get_tasks_for_clustering(self, workflow: Workflow,
            method: str) -> typing.List[Task]:

        result = []
        evaluator = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='evaluator', Platform.id==1000).first()
        forms = json.dumps(
            {"task_type": {"value": "clustering"}, "clust_metric": {"value": "silhouette"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=evaluator, name=evaluator.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        grid = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='grid', Platform.id==1000).first()

        forms = json.dumps({"strategy": {"value": "grid"}})
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=grid, name=grid.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        k_means = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='k-means', Platform.id==1000).first()
        if k_means and method == 'fast':
            k = {"list": ["2", "4", "8", "16"], "type": "list"}
            alg_type = ["kmeans"]
            dist = ["euclidean"]
            init_mode = ["k-means||"]
            forms = json.dumps({
                "number_of_clusters": {
                    "value": k, "internalValue": k, "enabled": True},
                "type": {"value": alg_type, "internalValue": alg_type},
                "init_mode": {"value": init_mode, "internalValue": init_mode},
                "distance": {"value": dist, "internalValue": dist}
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=k_means, name=k_means.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        return result

    def _get_tasks_for_regression(self, workflow: Workflow,
            method: str) -> typing.List[Task]:

        result = []

        evaluator = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='evaluator', Platform.id==1000).first()
        forms = json.dumps(
            {"task_type": {"value": "regression"}, "reg_metric": {"value": "rmse"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=evaluator, name=evaluator.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        features = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='features', Platform.id==1000).first()

        workflow_form = json.loads(workflow.forms)

        forms = json.dumps(
                {"features": {"value": [
                    {"name": workflow_form.get('$meta').get('value').get('label'),
                        "usage": "label", "enable": True,
                        "feature_type": "numerical", "transform": "keep"}]}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=features, name=features.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        if method == 'fast':
            random_forest = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='random-forest-regressor', Platform.id==1000).first()

            trees = {"list": [100], "type": "list"}
            depth = {"list": [2, 6], "type": "list"}
            forms = json.dumps({
                "num_trees": {
                    "value": trees, "internalValue": trees},
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=random_forest, name=random_forest.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        elif method == 'interpretable':
            dt = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='decision-tree-regressor', Platform.id==1000).first()

            depth = {"list": [6], "type": "list"}
            forms = json.dumps({
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=dt, name=dt.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=3, z_index=3))
        elif method == 'performance':
            grid = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='grid', Platform.id==1000).first()

            forms = json.dumps({"strategy": {"value": "random"},
                "max_iterations": {"value": 30}})
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=grid, name=grid.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=1, z_index=0))

            random_forest = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='random-forest-regressor', Platform.id==1000).first()

            trees = {"type": "range", "min": 80, "max": 200, "distribution": "uniform"}
            depth = {"type": "range", "min": 5, "max": 10, "distribution": "uniform"}
            feat_subset = ["auto"]
            forms = json.dumps({
                "num_trees": {
                    "value": trees, "internalValue": trees},
                "max_depth": {"value": depth, "internalValue": depth},
                "feature_subset_strategy": {"value": feat_subset,
                    "internalValue": feat_subset},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=random_forest, name=random_forest.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

            gbt = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='gbt-regressor', Platform.id==1000).first()
            depth = {"type": "range", "min": 5, "max": 10, "distribution": "uniform"}
            forms = json.dumps({
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=gbt, name=gbt.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        if method in ('fast', 'interpretable'):
            linear_reg = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='linear-regression', Platform.id==1000).first()

            elastic_net = {"list": [0.1, 1.0], "type": "list"}
            forms = json.dumps({
                "elastic_net": {"value": elastic_net, "internalValue": elastic_net},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=linear_reg, name=linear_reg.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=3, z_index=3))

            grid = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='grid', Platform.id==1000).first()

            forms = json.dumps({"strategy": {"value": "grid"}})
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=grid, name=grid.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=1, z_index=0))

        return result

    def _get_tasks_for_classification(self, workflow: Workflow,
            method: str) -> typing.List[Task]:

        result = []

        evaluator = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='evaluator', Platform.id==1000).first()
        forms = json.dumps(
            {"task_type": {"value": "multiclass-classification"},
                "multi_metric": {"value": "accuracy"}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=evaluator, name=evaluator.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        features = Operation.query.join(Operation.platforms).filter(
            Operation.slug=='features', Platform.id==1000).first()

        workflow_form = json.loads(workflow.forms)

        forms = json.dumps(
                {"features": {"value": [
                    {"name": workflow_form.get('$meta').get('value').get('label'),
                        "usage": "label", "enable": True,
                        "feature_type": "numerical", "transform": "keep"}]}}
        )
        result.append(Task(
            id=str(uuid.uuid4()),
            operation=features, name=features.name,
            workflow=workflow, forms=forms, left=0, top=0,
            display_order=1, z_index=0))

        if method == 'fast':
            random_forest = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='random-forest-classifier', Platform.id==1000).first()

            trees = {"list": [100], "type": "list"}
            depth = {"list": [12, 6], "type": "list"}
            forms = json.dumps({
                "num_trees": {
                    "value": trees, "internalValue": trees},
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=random_forest, name=random_forest.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        elif method == 'interpretable':
            dt = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='decision-tree-classifier', Platform.id==1000).first()

            depth = {"list": [5], "type": "list"}
            forms = json.dumps({
                "max_depth": {"value": depth, "internalValue": depth},
                "impurity": {"value": ["gini"], "internalValue": ["gini"]}
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=dt, name=dt.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=3, z_index=3))

        elif method == 'performance':
            grid = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='grid', Platform.id==1000).first()

            forms = json.dumps({"strategy": {"value": "random"},
                "max_iterations": {"value": 30}})
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=grid, name=grid.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=1, z_index=0))

            random_forest = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='random-forest-classifier', Platform.id==1000).first()

            trees = {"type": "range", "min": 80, "max": 200, "distribution": "uniform"}
            depth = {"type": "range", "min": 5, "max": 10, "distribution": "uniform"}
            feat_subset = ["auto"]
            forms = json.dumps({
                "num_trees": {
                    "value": trees, "internalValue": trees},
                "max_depth": {"value": depth, "internalValue": depth},
                "feature_subset_strategy": {"value": feat_subset,
                    "internalValue": feat_subset},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=random_forest, name=random_forest.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

            gbt = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='gbt-classifier', Platform.id==1000).first()
            depth = {"type": "range", "min": 5, "max": 10, "distribution": "uniform"}
            forms = json.dumps({
                "max_depth": {"value": depth, "internalValue": depth},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=gbt, name=gbt.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

            log_reg = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='logistic-regression', Platform.id==1000).first()

            reg_param = {"elastic_net_param":
                    {"value": {"type": "range", "min": 0.01,
                        "max": 100, "distribution": "log_uniform"},
                        "internalValue": {"type": "range", "min": 0.01,
                            "max": 100, "distribution": "log_uniform"}}}
            forms = json.dumps({
                "reg_param": {"value": reg_param, "internalValue": reg_param},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=log_reg, name=log_reg.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))

        if method in ('fast', 'interpretable'):
            log_reg = Operation.query.join(Operation.platforms).filter(
                Operation.slug=='logistic-regression', Platform.id==1000).first()

            reg_param = {"type": "list", "list": [0.01, 0.1, 1, 10, 100]}
            forms = json.dumps({
                "reg_param": {"value": reg_param, "internalValue": reg_param},
            })
            result.append(Task(
                id=str(uuid.uuid4()),
                operation=log_reg, name=log_reg.name,
                workflow=workflow, forms=forms, left=0, top=0,
                display_order=2, z_index=2))
        return result
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 - cologler <skyoflw@gmail.com>
# ----------
#
# ----------

import inspect
from inspect import signature
from inspect import Parameter
import typing
from .exceptions import InvalidOperationException

def _get_func_name(func):
    return getattr(func, '__name__', 'func')

def _check_callable(func):
    if not callable(func):
        name = _get_func_name(func)
        raise TypeError('%s is not a callable object.' % name)

class _ExpectedChecker:
    def check(self, value) -> bool:
        '''check if value match this checker.'''
        raise NotImplementedError
    def __str__(self) -> str:
        raise NotImplementedError

class _TypeExpectedChecker(_ExpectedChecker):
    def __init__(self, expected_type: type):
        self._expected_type = expected_type
    def check(self, value) -> bool:
        return isinstance(value, self._expected_type)
    def __str__(self) -> str:
        return self._expected_type.__name__

class _TypeTupleExpectedChecker(_ExpectedChecker):
    def __init__(self, expected_types: tuple):
        self._expected_types = expected_types
    def check(self, value) -> bool:
        return isinstance(value, self._expected_types)
    def __str__(self) -> str:
        return '/'.join([x.__name__ for x in self._expected_types])

class _CallableExpectedChecker(_ExpectedChecker):
    def __init__(self, expected_callable: tuple):
        self._expected_callable = expected_callable
        self._name = _get_func_name(expected_callable)
        self._sign = signature(expected_callable)
        if len(self._sign.parameters) != 1:
            raise InvalidOperationException(
                'function %s contains too many args (only support 1).' % self._name)
    def check(self, value) -> bool:
        return self._expected_callable(value)
    def __str__(self) -> str:
        return self._name + '()'

class _ComplexExpectedChecker(_ExpectedChecker):
    def __init__(self, expected_tuple: tuple):
        self._expected_tuple = expected_tuple
        expected_types = []
        expected_other = []
        for item in self._expected_tuple:
            if isinstance(item, type) or item is None:
                expected_types.append(item or type(None))
            else:
                expected_other.append(item)
        self._checkers = []
        if len(expected_types) > 0:
            self._checkers.append(_expected_checker(tuple(expected_types), False))
        if len(expected_other) > 0:
            self._checkers.append(_expected_checker(tuple(expected_other), False))
    def check(self, value) -> bool:
        for checker in self._checkers:
            if checker.check(value):
                return True
        return False
    def __str__(self):
        return '/'.join([str(x) for x in self._checkers])

def _expected_checker(annotation, allow_complex) -> _ExpectedChecker:
    if annotation == Parameter.empty:
        return None
    elif annotation is None:
        return _TypeExpectedChecker(type(None))
    elif isinstance(annotation, type):
        return _TypeExpectedChecker(annotation)
    elif isinstance(annotation, tuple):
        if len(annotation) == 1: # unpack
            return _expected_checker(annotation[0], allow_complex)
        is_all_type = True
        for item in annotation:
            if not isinstance(item, type):
                is_all_type = False
                break
        if is_all_type:
            return _TypeTupleExpectedChecker(annotation)
        elif allow_complex:
            return _ComplexExpectedChecker(annotation)
    elif callable(annotation):
        return _CallableExpectedChecker(annotation)
    raise NotImplementedError('unknown annotation contract.')

class _AnnotationChecker:
    def __init__(self, name: str, annotation):
        self._name = name
        self._annotation = annotation
        if self._annotation is None:
            self._annotation = type(None)
        self._checker = _expected_checker(annotation, True)

    @property
    def name(self):
        '''get parameter name.'''
        return self._name

    def can_check(self):
        '''check if '''
        return self._checker != None

    def check(self, value):
        '''check for argument.'''
        if self.can_check():
            if not self._checker.check(value):
                self._raise_error(value)
        return value

    def _raise_error(self, value):
        raise TypeError("parameter [%s] type error (expected %s, got %s)" % (
            self._name, self._checker, type(value).__name__))

def _wrap(wrapper, func):
    '''make wrapper sign same as func.'''
    wrapper.__doc__ = func.__doc__
    wrapper.__annotations__ = func.__annotations__
    # black technology
    wrapper.__signature__ = inspect.Signature.from_callable(func)
    return wrapper

def check_arguments(func):
    '''
    check function arguments by annotation.

    e.g.1: allow str
    @check_arguments
    def function(arg: str): pass

    e.g.2: allow str or int
    @check_arguments
    def function(arg: (str, int)): pass

    e.g.3: is_xxx canbe a callable[object] ptr.
    @check_arguments
    def function(arg: (str, is_xxx)): pass

    <cannot after @classmethod> please call before @classmethod like:
    @classmethod
    @check_arguments
    def method(cls):
        pass
    '''
    _check_callable(func)
    sign = signature(func)
    checkers = [_AnnotationChecker(x.name, x.annotation) for x in sign.parameters.values()]
    def _check():
        for checker in checkers:
            if checker.can_check():
                return
        name = _get_func_name(func)
        raise InvalidOperationException('%s does not contains any parameter annotation.' % name)
    _check()
    checkers_map = dict(zip([x.name for x in checkers], checkers))
    def _function(*args, **kwargs):
        for index, checker in enumerate(checkers[:len(args)]):
            checker.check(args[index])
        for name in kwargs:
            checker = checkers_map.get(name)
            if checker != None:
                checker.check(kwargs[name])
        return func(*args, **kwargs)
    return _wrap(_function, func)

def check_return(func):
    '''
    check function return value by annotation.

    e.g.1: allow str
    @check_arguments
    def function() -> str: pass

    e.g.2: allow str or int
    @check_arguments
    def function() -> (str, int): pass

    e.g.3: is_xxx canbe a callable[object] ptr
    @check_arguments
    def function() -> (str, is_xxx): pass

    <cannot after @classmethod> please call before @classmethod like:
    @classmethod
    @check_return
    def method(cls):
        pass
    '''
    _check_callable(func)
    expected_type = func.__annotations__.get('return')
    if expected_type is None:
        name = _get_func_name(func)
        raise InvalidOperationException('%s does not contains return value annotation.' % name)
    checker = _AnnotationChecker('return value', expected_type)
    def _function(*args, **kwargs):
        return checker.check(func(*args, **kwargs))
    return _wrap(_function, func)

def __check_type(actual_value, expected_type: type):
    if isinstance(expected_type, typing.TypingMeta):
        return __check_generic_type(actual_value, expected_type)
    if not isinstance(actual_value, expected_type):
        raise TypeError("type error (expected %s, got %s)" % (
            expected_type.__name__, type(actual_value).__name__))

def __check_generic_type(actual_value, expected_type: typing.TypingMeta):
    if expected_type._has_type_var():
        raise NotImplementedError('expected_type cannot contains type ver.')
    if isinstance(expected_type, typing.GenericMeta):
        parameters = expected_type.__parameters__
        if isinstance(actual_value, (list, set)):
            for item in actual_value:
                __check_type(item, parameters[0])
        elif isinstance(actual_value, dict):
            for key in actual_value:
                __check_type(key, parameters[0])
                __check_type(actual_value[key], parameters[1])
        else:
            raise NotImplementedError
    elif isinstance(expected_type, typing.TupleMeta):
        parameters = expected_type.__tuple_params__
        if len(actual_value) != len(parameters):
            expected_str = '(' + ', '.join([x.__name__ for x in parameters]) + ')'
            actual_str = '(' + ', '.join([type(x).__name__ for x in actual_value]) + ')'
            raise TypeError("type error (expected %s, got %s)" % (expected_str, actual_str))
        for left, right in zip(actual_value, parameters):
            __check_type(left, right)
    else:
        raise NotImplementedError

@check_arguments
def check_generic(actual_value, expected_type: typing.TypingMeta):
    '''
    check if argument match parameter.
    raise TypeError if not match.

    NOTE: check generic for collection will enumerate entire collection.
    '''
    __check_generic_type(actual_value, expected_type)

__all__ = [
    'check_arguments',
    'check_return',
    'check_generic'
]<commit_msg>overwrite func name.<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 - cologler <skyoflw@gmail.com>
# ----------
#
# ----------

import inspect
from inspect import signature
from inspect import Parameter
import typing
from .exceptions import InvalidOperationException

def _get_func_name(func):
    return getattr(func, '__name__', 'func')

def _check_callable(func):
    if not callable(func):
        name = _get_func_name(func)
        raise TypeError('%s is not a callable object.' % name)

class _ExpectedChecker:
    def check(self, value) -> bool:
        '''check if value match this checker.'''
        raise NotImplementedError
    def __str__(self) -> str:
        raise NotImplementedError

class _TypeExpectedChecker(_ExpectedChecker):
    def __init__(self, expected_type: type):
        self._expected_type = expected_type
    def check(self, value) -> bool:
        return isinstance(value, self._expected_type)
    def __str__(self) -> str:
        return self._expected_type.__name__

class _TypeTupleExpectedChecker(_ExpectedChecker):
    def __init__(self, expected_types: tuple):
        self._expected_types = expected_types
    def check(self, value) -> bool:
        return isinstance(value, self._expected_types)
    def __str__(self) -> str:
        return '/'.join([x.__name__ for x in self._expected_types])

class _CallableExpectedChecker(_ExpectedChecker):
    def __init__(self, expected_callable: tuple):
        self._expected_callable = expected_callable
        self._name = _get_func_name(expected_callable)
        self._sign = signature(expected_callable)
        if len(self._sign.parameters) != 1:
            raise InvalidOperationException(
                'function %s contains too many args (only support 1).' % self._name)
    def check(self, value) -> bool:
        return self._expected_callable(value)
    def __str__(self) -> str:
        return self._name + '()'

class _ComplexExpectedChecker(_ExpectedChecker):
    def __init__(self, expected_tuple: tuple):
        self._expected_tuple = expected_tuple
        expected_types = []
        expected_other = []
        for item in self._expected_tuple:
            if isinstance(item, type) or item is None:
                expected_types.append(item or type(None))
            else:
                expected_other.append(item)
        self._checkers = []
        if len(expected_types) > 0:
            self._checkers.append(_expected_checker(tuple(expected_types), False))
        if len(expected_other) > 0:
            self._checkers.append(_expected_checker(tuple(expected_other), False))
    def check(self, value) -> bool:
        for checker in self._checkers:
            if checker.check(value):
                return True
        return False
    def __str__(self):
        return '/'.join([str(x) for x in self._checkers])

def _expected_checker(annotation, allow_complex) -> _ExpectedChecker:
    if annotation == Parameter.empty:
        return None
    elif annotation is None:
        return _TypeExpectedChecker(type(None))
    elif isinstance(annotation, type):
        return _TypeExpectedChecker(annotation)
    elif isinstance(annotation, tuple):
        if len(annotation) == 1: # unpack
            return _expected_checker(annotation[0], allow_complex)
        is_all_type = True
        for item in annotation:
            if not isinstance(item, type):
                is_all_type = False
                break
        if is_all_type:
            return _TypeTupleExpectedChecker(annotation)
        elif allow_complex:
            return _ComplexExpectedChecker(annotation)
    elif callable(annotation):
        return _CallableExpectedChecker(annotation)
    raise NotImplementedError('unknown annotation contract.')

class _AnnotationChecker:
    def __init__(self, name: str, annotation):
        self._name = name
        self._annotation = annotation
        if self._annotation is None:
            self._annotation = type(None)
        self._checker = _expected_checker(annotation, True)

    @property
    def name(self):
        '''get parameter name.'''
        return self._name

    def can_check(self):
        '''check if '''
        return self._checker != None

    def check(self, value):
        '''check for argument.'''
        if self.can_check():
            if not self._checker.check(value):
                self._raise_error(value)
        return value

    def _raise_error(self, value):
        raise TypeError("parameter [%s] type error (expected %s, got %s)" % (
            self._name, self._checker, type(value).__name__))

def _wrap(wrapper, func):
    '''make wrapper sign same as func.'''
    wrapper.__doc__ = func.__doc__
    wrapper.__annotations__ = func.__annotations__
    wrapper.__name__ = func.__name__
    wrapper.__signature__ = inspect.Signature.from_callable(func)
    return wrapper

def check_arguments(func):
    '''
    check function arguments by annotation.

    e.g.1: allow str
    @check_arguments
    def function(arg: str): pass

    e.g.2: allow str or int
    @check_arguments
    def function(arg: (str, int)): pass

    e.g.3: is_xxx canbe a callable[object] ptr.
    @check_arguments
    def function(arg: (str, is_xxx)): pass

    <cannot after @classmethod> please call before @classmethod like:
    @classmethod
    @check_arguments
    def method(cls):
        pass
    '''
    _check_callable(func)
    sign = signature(func)
    checkers = [_AnnotationChecker(x.name, x.annotation) for x in sign.parameters.values()]
    def _check():
        for checker in checkers:
            if checker.can_check():
                return
        name = _get_func_name(func)
        raise InvalidOperationException('%s does not contains any parameter annotation.' % name)
    _check()
    checkers_map = dict(zip([x.name for x in checkers], checkers))
    def _function(*args, **kwargs):
        for index, checker in enumerate(checkers[:len(args)]):
            checker.check(args[index])
        for name in kwargs:
            checker = checkers_map.get(name)
            if checker != None:
                checker.check(kwargs[name])
        return func(*args, **kwargs)
    return _wrap(_function, func)

def check_return(func):
    '''
    check function return value by annotation.

    e.g.1: allow str
    @check_arguments
    def function() -> str: pass

    e.g.2: allow str or int
    @check_arguments
    def function() -> (str, int): pass

    e.g.3: is_xxx canbe a callable[object] ptr
    @check_arguments
    def function() -> (str, is_xxx): pass

    <cannot after @classmethod> please call before @classmethod like:
    @classmethod
    @check_return
    def method(cls):
        pass
    '''
    _check_callable(func)
    expected_type = func.__annotations__.get('return')
    if expected_type is None:
        name = _get_func_name(func)
        raise InvalidOperationException('%s does not contains return value annotation.' % name)
    checker = _AnnotationChecker('return value', expected_type)
    def _function(*args, **kwargs):
        return checker.check(func(*args, **kwargs))
    return _wrap(_function, func)

def __check_type(actual_value, expected_type: type):
    if isinstance(expected_type, typing.TypingMeta):
        return __check_generic_type(actual_value, expected_type)
    if not isinstance(actual_value, expected_type):
        raise TypeError("type error (expected %s, got %s)" % (
            expected_type.__name__, type(actual_value).__name__))

def __check_generic_type(actual_value, expected_type: typing.TypingMeta):
    if expected_type._has_type_var():
        raise NotImplementedError('expected_type cannot contains type ver.')
    if isinstance(expected_type, typing.GenericMeta):
        parameters = expected_type.__parameters__
        if isinstance(actual_value, (list, set)):
            for item in actual_value:
                __check_type(item, parameters[0])
        elif isinstance(actual_value, dict):
            for key in actual_value:
                __check_type(key, parameters[0])
                __check_type(actual_value[key], parameters[1])
        else:
            raise NotImplementedError
    elif isinstance(expected_type, typing.TupleMeta):
        parameters = expected_type.__tuple_params__
        if len(actual_value) != len(parameters):
            expected_str = '(' + ', '.join([x.__name__ for x in parameters]) + ')'
            actual_str = '(' + ', '.join([type(x).__name__ for x in actual_value]) + ')'
            raise TypeError("type error (expected %s, got %s)" % (expected_str, actual_str))
        for left, right in zip(actual_value, parameters):
            __check_type(left, right)
    else:
        raise NotImplementedError

@check_arguments
def check_generic(actual_value, expected_type: typing.TypingMeta):
    '''
    check if argument match parameter.
    raise TypeError if not match.

    NOTE: check generic for collection will enumerate entire collection.
    '''
    __check_generic_type(actual_value, expected_type)

__all__ = [
    'check_arguments',
    'check_return',
    'check_generic'
]<|endoftext|>


<commit_before># Copyright (C) 2005 by Async Open Source
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

from datetime import date
from gettext import gettext as _

import gtk
from gazpacho.custompropertyeditor import CustomPropertyEditor
from gazpacho.util import get_bool_from_string_with_default
from gazpacho.widget import Widget

# Register adapters
import kiwi.ui.gazpacholoader
root_library = 'kiwi.ui.widgets'
widget_prefix = 'Kiwi'

# When we can use a never version of gazpacho, remove this

class DataTypeAdaptor(object):
    def create_editor(self, context):
        model = gtk.ListStore(str, object)
        model.append((_('String'), str))
        model.append((_('Integer'), int))
        model.append((_('Float'), float))
        model.append((_('Boolean'), bool))
        model.append((_('Date'), date))
        model.append((_('Object'), object))
        combo = gtk.ComboBox(model)
        renderer = gtk.CellRendererText()
        combo.pack_start(renderer)
        combo.add_attribute(renderer, 'text', 0)
        combo.set_active(0)
        combo.set_data('connection-id', -1)
        return combo

    def update_editor(self, context, combo, kiwiwidget, proxy):
        connection_id = combo.get_data('connection-id')
        if (connection_id != -1):
            combo.disconnect(connection_id)
        connection_id = combo.connect('changed', self._editor_edit, kiwiwidget,
                                      proxy, context)
        combo.set_data('connection-id', connection_id)
        model = combo.get_model()
        value = kiwiwidget.get_property('data-type')
        for row in model:
            if row[1] == value:
                combo.set_active_iter(row.iter)
                break

    def _editor_edit(self, combo, kiwilist, proxy, context):
        model = combo.get_model()
        active_iter = combo.get_active_iter()
        value = model.get_value(active_iter, 1)
        proxy.set_value(value)

    def save(self, context, widget):
        gwidget = Widget.from_widget(widget)
        data_type = gwidget.get_glade_property('data-type')
        # data type is one of (str, float, int, bool)
        return data_type._value.__name__

class SpinBtnDataTypeAdaptor(DataTypeAdaptor):
    def create_editor(self, context):
        model = gtk.ListStore(str, object)
        model.append((_('Integer'), int))
        model.append((_('Float'), float))
        combo = gtk.ComboBox(model)
        renderer = gtk.CellRendererText()
        combo.pack_start(renderer)
        combo.add_attribute(renderer, 'text', 0)
        combo.set_active(0)
        combo.set_data('connection-id', -1)
        return combo

class ColumnDefinitionsAdaptor(object):
    def __init__(self):
        self._editor = ListColumnDefinitionsEditor()

    def set(self, context, kiwilist, value):
        kiwilist.set_property('column-definitions', value)

    def create_editor(self, context):
        button = gtk.Button(_('Edit...'))
        button.set_data('connection-id', -1)
        return button

    def update_editor(self, context, button, kiwilist, proxy):
        connection_id = button.get_data('connection-id')
        if connection_id != -1:
            button.disconnect(connection_id)
        connection_id = button.connect('clicked', self._editor_edit, kiwilist,
                                       proxy, context)
        button.set_data('connection-id', connection_id)

    def _editor_edit(self, button, kiwilist, proxy, context):
        application_window = context.get_application_window()
        self._editor.set_transient_for(application_window)
        self._editor.set_widget(kiwilist, proxy)
        self._editor.present()

(ATTRIBUTE,
 TITLE,
 DATA_TYPE,
 VISIBLE,
 JUSTIFY,
 TOOLTIP,
 FORMAT,
 WIDTH,
 SORTED,
 ORDER) = range(10)

class ListColumnDefinitionsEditor(CustomPropertyEditor):
    """This dialog is used to edit the column definitions of a Kiwi List"""

    def __init__(self):
        CustomPropertyEditor.__init__(self)

    def set_widget(self, widget, proxy):
        super(ListColumnDefinitionsEditor, self).set_widget(widget, proxy)
        self.set_title((_('Editing columns of list %s') % self.gwidget.name))
        self._load_columns()

    def _create_widgets(self):
        h_button_box = gtk.HButtonBox()
        h_button_box.set_layout(gtk.BUTTONBOX_START)
        self.add = gtk.Button(stock=gtk.STOCK_ADD)
        self.add.connect('clicked', self._on_add_clicked)
        h_button_box.pack_start(self.add)
        self.remove = gtk.Button(stock=gtk.STOCK_REMOVE)
        self.remove.connect('clicked', self._on_remove_clicked)
        h_button_box.pack_start(self.remove)
        self.up = gtk.Button(stock=gtk.STOCK_GO_UP)
        self.up.connect('clicked', self._on_up_clicked)
        h_button_box.pack_start(self.up)
        self.down = gtk.Button(stock=gtk.STOCK_GO_DOWN)
        self.down.connect('clicked', self._on_down_clicked)
        h_button_box.pack_start(self.down)
        self.vbox.pack_start(h_button_box, False, False)
        self.model = gtk.ListStore(str, str, str, bool, str, str, str, int,
                                   bool, str)
        self.treeview = gtk.TreeView(self.model)
        self.treeview.set_size_request(580, 300)
        selection = self.treeview.get_selection()
        selection.connect('changed', self._on_selection__changed)
        for i, title in enumerate(('Attribute',
                                      'Title',
                                      'Data type')):
            self.treeview.append_column(self._create_text_column(title, i))

        self.treeview.append_column(self._create_bool_column('Visible', 3))
        for i, title in enumerate(('Justify',
                                      'Tooltip',
                                      'Format')):
            self.treeview.append_column(self._create_text_column(title,
                                                                 (i + 4)))

        self.treeview.append_column(self._create_int_column('Width', 7))
        self.treeview.append_column(self._create_bool_column('Sorted', 8))
        self.treeview.append_column(self._create_text_column('Order', 9))
        sw = gtk.ScrolledWindow()
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        sw.add(self.treeview)
        self.vbox.pack_start(sw, True, True)
        label = gtk.Label()
        instructions = """Valid values:
        Data type: str, int, float or date
        Justify: left, center or right
        Order: ascending or descending"""
        label.set_alignment(0.0, 0.0)
        label.set_markup(instructions)
        self.vbox.pack_start(label, False, False)

    def _create_text_column(self, title, index):
        renderer = gtk.CellRendererText()
        renderer.set_property('editable', True)
        renderer.connect('edited', self._on_text_renderer__edited, index)
        col = gtk.TreeViewColumn(title, renderer, text=index)
        return col

    def _create_bool_column(self, title, index):
        renderer = gtk.CellRendererToggle()
        renderer.set_property('activatable', True)
        renderer.connect('toggled', self._on_toggle_renderer__toggled, index)
        col = gtk.TreeViewColumn(title, renderer, active=index)
        return col

    def _create_int_column(self, title, index):
        col = self._create_text_column(title, index)
        return col

    def _get_default_values(self):
        return ('unnamed', '', 'str', True, 'left', '', '', 1, False, '')

    def _update_proxy(self):
        cols = []
        for column in self.model:
            cols.append('|'.join(map(str, tuple(column))))

        data = '^'.join(cols)
        self.proxy.set_value(data)

    def _load_columns(self):
        self.model.clear()
        cd = self.gwidget.get_glade_property('column-definitions').value
        if not cd:
            return

        for col in cd.split('^'):
            (attr, title, data_type,
             visible, justify, tooltip,
             format, width, sorted, order) = col.split('|')

            visible = get_bool_from_string_with_default(visible, True)
            width = int(width)
            sorted = get_bool_from_string_with_default(sorted, False)
            self.model.append((attr, title, data_type,
                               visible, justify, tooltip,
                               format, width, sorted, order))

    def _on_add_clicked(self, button):
        row_iter = self.model.append(self._get_default_values())
        path = self.model.get_path(row_iter)
        col = self.treeview.get_column(0)
        self.treeview.set_cursor(path, col, True)
        self._update_proxy()

    def _on_remove_clicked(self, button):
        model, selected_iter = self.treeview.get_selection().get_selected()
        if selected_iter:
            model.remove(selected_iter)
            self._update_proxy()

    def _on_up_clicked(self, button):
        model, selected_iter = self.treeview.get_selection().get_selected()
        if not selected_iter:
            return

        path = self.model.get_path(selected_iter)
        prev_iter = self.model.get_iter(((path[0] - 1)))
        if not prev_iter:
            return

        model.swap(prev_iter, selected_iter)
        self._update_proxy()
        self._update_buttons()

    def _on_down_clicked(self, button):
        model, selected_iter = self.treeview.get_selection().get_selected()
        if not selected_iter:
            return

        next_iter = model.iter_next(selected_iter)
        if not next_iter:
            return

        model.swap(selected_iter, next_iter)
        self._update_proxy()
        self._update_buttons()

    def _on_text_renderer__edited(self, renderer, path, new_text, col_index):
        value = new_text
        if col_index == WIDTH:
            try:
                value = int(new_text)
            except ValueError:
                return

        row = self.model[path[0]]
        row[col_index] = value
        if col_index == ATTRIBUTE:
            title = row[TITLE]
            if not title:
                row[TITLE] = value.capitalize()
        self._update_proxy()

    def _on_toggle_renderer__toggled(self, renderer, path, col_index):
        row = self.model[path[0]]
        row[col_index] = not row[col_index]
        self._update_proxy()

    def _on_selection__changed(self, selection):
        self._update_buttons()

    def _update_buttons(self):
        selection = self.treeview.get_selection()
        model, selected_iter = selection.get_selected()
        if not selected_iter:
            self.remove.set_sensitive(False)
            self.down.set_sensitive(False)
            self.up.set_sensitive(False)
            return

        self.remove.set_sensitive(True)
        path = model.get_path(selected_iter)[0]
        size = len(model)
        if path == 0:
            self.up.set_sensitive(False)
            if size > 1:
                self.down.set_sensitive(True)

        if path == size - 1:
            self.down.set_sensitive(False)
            if size > 1:
                self.up.set_sensitive(True)

        if path > 0 and path < (size - 1):
            self.up.set_sensitive(True)
            self.down.set_sensitive(True)
<commit_msg>Implement SpinButton/Label::data-type with new gazpacho properties. We can now load/save pos.glade again<commit_after># Copyright (C) 2005 by Async Open Source
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

from datetime import date
from gettext import gettext as _

import gtk
from gazpacho.custompropertyeditor import CustomPropertyEditor
from gazpacho.properties import prop_registry, CustomProperty, StringType
from gazpacho.util import get_bool_from_string_with_default
from gazpacho.widget import Widget

# Register adapters
import kiwi.ui.gazpacholoader
root_library = 'kiwi.ui.widgets'
widget_prefix = 'Kiwi'

# When we can use a never version of gazpacho, remove this

class DataTypeAdaptor(object):
    def create_editor(self, context):
        model = gtk.ListStore(str, object)
        model.append((_('String'), str))
        model.append((_('Integer'), int))
        model.append((_('Float'), float))
        model.append((_('Boolean'), bool))
        model.append((_('Date'), date))
        model.append((_('Object'), object))
        combo = gtk.ComboBox(model)
        renderer = gtk.CellRendererText()
        combo.pack_start(renderer)
        combo.add_attribute(renderer, 'text', 0)
        combo.set_active(0)
        combo.set_data('connection-id', -1)
        return combo

    def update_editor(self, context, combo, kiwiwidget, proxy):
        connection_id = combo.get_data('connection-id')
        if (connection_id != -1):
            combo.disconnect(connection_id)
        connection_id = combo.connect('changed', self._editor_edit, kiwiwidget,
                                      proxy, context)
        combo.set_data('connection-id', connection_id)
        model = combo.get_model()
        value = kiwiwidget.get_property('data-type')
        for row in model:
            if row[1] == value:
                combo.set_active_iter(row.iter)
                break

    def _editor_edit(self, combo, kiwilist, proxy, context):
        model = combo.get_model()
        active_iter = combo.get_active_iter()
        value = model.get_value(active_iter, 1)
        proxy.set_value(value)

class DataTypeProperty(CustomProperty, StringType):
    def save(self):
        value = self.get()
        return value.__name__

class LabelDataType(DataTypeProperty):
    pass
prop_registry.override_property('kiwi+ui+widgets+label+Label::data-type', LabelDataType)

class SpinButtonDataType(DataTypeProperty):
    pass
prop_registry.override_property('kiwi+ui+widgets+spinbutton+SpinButton::data-type', SpinButtonDataType)

class SpinBtnDataTypeAdaptor(DataTypeAdaptor):
    def create_editor(self, context):
        model = gtk.ListStore(str, object)
        model.append((_('Integer'), int))
        model.append((_('Float'), float))
        combo = gtk.ComboBox(model)
        renderer = gtk.CellRendererText()
        combo.pack_start(renderer)
        combo.add_attribute(renderer, 'text', 0)
        combo.set_active(0)
        combo.set_data('connection-id', -1)
        return combo

class ColumnDefinitionsAdaptor(object):
    def __init__(self):
        self._editor = ListColumnDefinitionsEditor()

    def set(self, context, kiwilist, value):
        kiwilist.set_property('column-definitions', value)

    def create_editor(self, context):
        button = gtk.Button(_('Edit...'))
        button.set_data('connection-id', -1)
        return button

    def update_editor(self, context, button, kiwilist, proxy):
        connection_id = button.get_data('connection-id')
        if connection_id != -1:
            button.disconnect(connection_id)
        connection_id = button.connect('clicked', self._editor_edit, kiwilist,
                                       proxy, context)
        button.set_data('connection-id', connection_id)

    def _editor_edit(self, button, kiwilist, proxy, context):
        application_window = context.get_application_window()
        self._editor.set_transient_for(application_window)
        self._editor.set_widget(kiwilist, proxy)
        self._editor.present()

(ATTRIBUTE,
 TITLE,
 DATA_TYPE,
 VISIBLE,
 JUSTIFY,
 TOOLTIP,
 FORMAT,
 WIDTH,
 SORTED,
 ORDER) = range(10)

class ListColumnDefinitionsEditor(CustomPropertyEditor):
    """This dialog is used to edit the column definitions of a Kiwi List"""

    def __init__(self):
        CustomPropertyEditor.__init__(self)

    def set_widget(self, widget, proxy):
        super(ListColumnDefinitionsEditor, self).set_widget(widget, proxy)
        self.set_title((_('Editing columns of list %s') % self.gwidget.name))
        self._load_columns()

    def _create_widgets(self):
        h_button_box = gtk.HButtonBox()
        h_button_box.set_layout(gtk.BUTTONBOX_START)
        self.add = gtk.Button(stock=gtk.STOCK_ADD)
        self.add.connect('clicked', self._on_add_clicked)
        h_button_box.pack_start(self.add)
        self.remove = gtk.Button(stock=gtk.STOCK_REMOVE)
        self.remove.connect('clicked', self._on_remove_clicked)
        h_button_box.pack_start(self.remove)
        self.up = gtk.Button(stock=gtk.STOCK_GO_UP)
        self.up.connect('clicked', self._on_up_clicked)
        h_button_box.pack_start(self.up)
        self.down = gtk.Button(stock=gtk.STOCK_GO_DOWN)
        self.down.connect('clicked', self._on_down_clicked)
        h_button_box.pack_start(self.down)
        self.vbox.pack_start(h_button_box, False, False)
        self.model = gtk.ListStore(str, str, str, bool, str, str, str, int,
                                   bool, str)
        self.treeview = gtk.TreeView(self.model)
        self.treeview.set_size_request(580, 300)
        selection = self.treeview.get_selection()
        selection.connect('changed', self._on_selection__changed)
        for i, title in enumerate(('Attribute',
                                      'Title',
                                      'Data type')):
            self.treeview.append_column(self._create_text_column(title, i))

        self.treeview.append_column(self._create_bool_column('Visible', 3))
        for i, title in enumerate(('Justify',
                                      'Tooltip',
                                      'Format')):
            self.treeview.append_column(self._create_text_column(title,
                                                                 (i + 4)))

        self.treeview.append_column(self._create_int_column('Width', 7))
        self.treeview.append_column(self._create_bool_column('Sorted', 8))
        self.treeview.append_column(self._create_text_column('Order', 9))
        sw = gtk.ScrolledWindow()
        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        sw.add(self.treeview)
        self.vbox.pack_start(sw, True, True)
        label = gtk.Label()
        instructions = """Valid values:
        Data type: str, int, float or date
        Justify: left, center or right
        Order: ascending or descending"""
        label.set_alignment(0.0, 0.0)
        label.set_markup(instructions)
        self.vbox.pack_start(label, False, False)

    def _create_text_column(self, title, index):
        renderer = gtk.CellRendererText()
        renderer.set_property('editable', True)
        renderer.connect('edited', self._on_text_renderer__edited, index)
        col = gtk.TreeViewColumn(title, renderer, text=index)
        return col

    def _create_bool_column(self, title, index):
        renderer = gtk.CellRendererToggle()
        renderer.set_property('activatable', True)
        renderer.connect('toggled', self._on_toggle_renderer__toggled, index)
        col = gtk.TreeViewColumn(title, renderer, active=index)
        return col

    def _create_int_column(self, title, index):
        col = self._create_text_column(title, index)
        return col

    def _get_default_values(self):
        return ('unnamed', '', 'str', True, 'left', '', '', 1, False, '')

    def _update_proxy(self):
        cols = []
        for column in self.model:
            cols.append('|'.join(map(str, tuple(column))))

        data = '^'.join(cols)
        self.proxy.set_value(data)

    def _load_columns(self):
        self.model.clear()
        cd = self.gwidget.get_glade_property('column-definitions').value
        if not cd:
            return

        for col in cd.split('^'):
            (attr, title, data_type,
             visible, justify, tooltip,
             format, width, sorted, order) = col.split('|')

            visible = get_bool_from_string_with_default(visible, True)
            width = int(width)
            sorted = get_bool_from_string_with_default(sorted, False)
            self.model.append((attr, title, data_type,
                               visible, justify, tooltip,
                               format, width, sorted, order))

    def _on_add_clicked(self, button):
        row_iter = self.model.append(self._get_default_values())
        path = self.model.get_path(row_iter)
        col = self.treeview.get_column(0)
        self.treeview.set_cursor(path, col, True)
        self._update_proxy()

    def _on_remove_clicked(self, button):
        model, selected_iter = self.treeview.get_selection().get_selected()
        if selected_iter:
            model.remove(selected_iter)
            self._update_proxy()

    def _on_up_clicked(self, button):
        model, selected_iter = self.treeview.get_selection().get_selected()
        if not selected_iter:
            return

        path = self.model.get_path(selected_iter)
        prev_iter = self.model.get_iter(((path[0] - 1)))
        if not prev_iter:
            return

        model.swap(prev_iter, selected_iter)
        self._update_proxy()
        self._update_buttons()

    def _on_down_clicked(self, button):
        model, selected_iter = self.treeview.get_selection().get_selected()
        if not selected_iter:
            return

        next_iter = model.iter_next(selected_iter)
        if not next_iter:
            return

        model.swap(selected_iter, next_iter)
        self._update_proxy()
        self._update_buttons()

    def _on_text_renderer__edited(self, renderer, path, new_text, col_index):
        value = new_text
        if col_index == WIDTH:
            try:
                value = int(new_text)
            except ValueError:
                return

        row = self.model[path[0]]
        row[col_index] = value
        if col_index == ATTRIBUTE:
            title = row[TITLE]
            if not title:
                row[TITLE] = value.capitalize()
        self._update_proxy()

    def _on_toggle_renderer__toggled(self, renderer, path, col_index):
        row = self.model[path[0]]
        row[col_index] = not row[col_index]
        self._update_proxy()

    def _on_selection__changed(self, selection):
        self._update_buttons()

    def _update_buttons(self):
        selection = self.treeview.get_selection()
        model, selected_iter = selection.get_selected()
        if not selected_iter:
            self.remove.set_sensitive(False)
            self.down.set_sensitive(False)
            self.up.set_sensitive(False)
            return

        self.remove.set_sensitive(True)
        path = model.get_path(selected_iter)[0]
        size = len(model)
        if path == 0:
            self.up.set_sensitive(False)
            if size > 1:
                self.down.set_sensitive(True)

        if path == size - 1:
            self.down.set_sensitive(False)
            if size > 1:
                self.up.set_sensitive(True)

        if path > 0 and path < (size - 1):
            self.up.set_sensitive(True)
            self.down.set_sensitive(True)
<|endoftext|>


<commit_before>__author__ = 'Will Hart'

import logging
import os
import threading
import time

from redis import ConnectionError
import serial
from serial.tools.list_ports import comports

from blitz.constants import SerialUpdatePeriod, SerialCommands
from blitz.data.database import DatabaseServer
from blitz.io.signals import logging_started, logging_stopped

class SerialManager(object):
    """
    Manages serial (eventually RS232, SPI or I2C) communications with
    expansion boards.  It has both a monitoring loop and an "outbox"
    which it uses for sending information.
    """

    __instance = None
    database = None
    serial_mapping = None
    __serial_thread = None

    logger = logging.getLogger(__name__)

    @classmethod
    def Instance(cls):
        """
        Returns a reference to a single SerialManager instance

        :returns: The SerialManager singleton instance
        """
        cls.logger.debug("SerialManager Instance called")
        if cls.__instance is None:
            return SerialManager()
        else:
            return cls.__instance

    def __init__(self):
        """
        Follows a singleton pattern and prevents instantiation of more than one Serial Manager.

        :returns: Nothing
        """

        if SerialManager.__instance is not None:

            self.logger.error("Attempted to recreate an instance of a SerialManager - should use Instance()")
            raise Exception(
                "Attempted to instantiate a new SerialManager, but only one instance is"
                " allowed.  Use the Instance() method instead")
        else:
            self.logger.debug("SerialManager __init__")
            SerialManager.__instance = self

        # create a database object
        try:
            self.database = DatabaseServer()
        except ConnectionError as e:
            self.logger.critical("ConnectionError when attempting to start the DatabaseServer!")
            self.logger.critical(e)

        # work out which serial ports are connected
        self.get_available_ports()

        # register signals
        logging_started.connect(self.start)
        logging_stopped.connect(self.stop)

    def get_available_ports(self):
        """
        Generates a list of available serial ports, mapping their ID to
        the COM* or /dev/tty* reference.  Adapted from http://stackoverflow.com/a/14224477/233608

        :returns: Nothing
        """
        self.logger.info("Scanning for available serial ports")
        self.serial_mapping = {}
        ports = []

        # Windows
        if os.name == 'nt':
            self.logger.debug("Performing Windows scan")
            # Scan for available ports.
            available = []
            for i in range(256):
                try:
                    portname = "COM%s" % (i + 1)
                    s = serial.Serial(portname)
                    s.close()
                    ports.append(portname)

                except serial.SerialException:
                    pass
        else:
            # Mac / Linux
            self.logger.debug("Performing Mac/Linux scan")
            for port in comports():
                ports.append(port[0])

        for port in ports:
            board_id = self.send_id_request(port)
            if board_id is not None:
                self.logger.info("Found board ID %s at %s" % (board_id, port))
                self.serial_mapping[hex(board_id)[2:].zfill(2)] = port

    def create_serial_connection(self, port_name, baud_rate=57600, read_timeout=3):
        """
        Creates a serial port connection, opens it and returns it

        :param port_name: the name of the port to open (for instance COM3)
        :param baud_rate: the baud rate of the serial connection (default 57600)
        :param read_timeout: the timeout to use for reading from ports (default 3 seconds)

        :return: An open serial port object
        """
        return serial.Serial(port_name, baudrate=baud_rate, timeout=read_timeout)

    def receive_serial_data(self, board_id, port_name):
        """
        Requests a transmission from the specified board and
        saves the returned data to the database

        :param board_id: the ID of the board in hex form, (e.g. "08" for board with ID 8)
        :param port_name: the name of the port to open (for instance COM3)

        :returns: Nothing
        """
        port = self.create_serial_connection(port_name)

        # send the transmit request
        port.write(board_id + SerialCommands['TRANSMIT'] + "\n")

        # readlines until no more lines left (will read for the timeout period)
        lines = port.readlines()
        for line in lines:
            line_size = len(line)
            if line_size < 4:
                pass  # too short, ignore
            elif line_size == 4:
                # a short message
                command = line[2:]
                if command == SerialCommands['ACK']:
                    break  # all done, ignore the rest
            else:
                # a data message, save it for later
                self.database.queue(line)

        port.close()

    def send_id_request(self, port_name):
        """
        Requests an ID from the serial port name and returns it.
        If no ID is found, return None

        :param port_name: the name of the port to open (for instance COM3)

        :returns: A two digit hex board ID, or None if no ID was found
        """
        port = self.create_serial_connection(port_name)
        board_id = None
        serial_buffer = ""

        # clear out any junk in the board's serial buffer and ignore the response
        port.write("\n")
        port.readline()

        # send the ID request
        port.write("00" + SerialCommands['ID'] + "\n")
        serial_buffer = port.readline()
        port.close()

        # check if a valid id was returned
        if len(serial_buffer) > 2:
            board_id = int(serial_buffer[0:2], 16)

        return board_id

    def send_command_with_ack(self, command, board_id, port_name):
        """
        Sends the given command over the serial port and checks for
        an ACK response.  Returns None if the ACK was received, and the
        received message otherwise

        :param command: the string command to send over the serial port, from the SerialCommands constant
        :param board_id: the ID of the board in hex form, (e.g. "08" for board with ID 8)
        :param port_name: the name of the port to open (for instance COM3)

        :returns: the board response if an error was received, or None if ACK was received
        """
        port = self.create_serial_connection(port_name)
        port.write(board_id + command)

        serial_buffer = port.readline()

        # TODO: properly handle errors

        if serial_buffer.length != 4 or serial_buffer[2:] != SerialCommands['ACK']:
            return serial_buffer

        return None

    def start(self, signal_args):
        """
        Starts listening on the serial ports and polling for updates every SerialUpdatePeriod seconds

        :param signal_args: the arguments provided by the blinker signal (unused)

        :returns: Nothing
        """

        # enter a new session
        session_id = self.database.start_session()

        # send a start signal to all boards
        for k in self.serial_mapping.keys():
            success = self.send_command_with_ack(SerialCommands['START'], k, self.serial_mapping[k])

            # log errors for now
            if not success is None:
                self.logger.warn("Did not receive a valid ACK response from board ID %s on START request" % k)

        # Start a thread for polling serial for updates
        self.__stop_event = threading.Event()
        self.__serial_thread = threading.Thread(target=self.__poll_serial, args=[self.__stop_event])
        self.__serial_thread.daemon = True
        self.__serial_thread.start()
        self.logger.debug("Started serial polling thread: %s" % self.__serial_thread.name)

        # log about serial listening starting
        self.logger.info("Commenced logging session %s" % session_id)

    def stop(self, signal_args):
        """
        Stops logging data and sends a STOP request to all boards

        :param signal_args: the arguments provided by the blinker signal (unused)

        :returns: Nothing
        """

        self.logger.debug("Received signal to stop logging")

        # send a stop signal to all boards
        for k in self.serial_mapping.keys():
            success = self.send_command_with_ack(SerialCommands['STOP'], k, self.serial_mapping[k])

            # log errors for now
            if not success is None:
                self.logger.warn("Did not receive a valid ACK response from board ID %s on STOP request" % k)

        # end the new session
        self.database.stop_session()
        self.__serial_thread.join()
        self.logger.info("Serial polling thread stopped")

    def __poll_serial(self, stop_event):
        """
        A thread which periodically polls a serial connection until a stop_event is received

        :param stop_event: the threading Event which triggers stopping serial listening

        :returns: Nothing
        """
        while not stop_event.is_set():
            # enumerate each port
            for k in self.serial_mapping.keys():
                self.receive_serial_data(k, self.serial_mapping[k])

            time.sleep(SerialUpdatePeriod)

        self.logger.debug("Exited poll serial thread")
<commit_msg>Additional debugging messages<commit_after>__author__ = 'Will Hart'

import logging
import os
import threading
import time

from redis import ConnectionError
import serial
from serial.tools.list_ports import comports

from blitz.constants import SerialUpdatePeriod, SerialCommands
from blitz.data.database import DatabaseServer
from blitz.io.signals import logging_started, logging_stopped

class SerialManager(object):
    """
    Manages serial (eventually RS232, SPI or I2C) communications with
    expansion boards.  It has both a monitoring loop and an "outbox"
    which it uses for sending information.
    """

    __instance = None
    database = None
    serial_mapping = None
    __serial_thread = None

    logger = logging.getLogger(__name__)

    @classmethod
    def Instance(cls):
        """
        Returns a reference to a single SerialManager instance

        :returns: The SerialManager singleton instance
        """
        cls.logger.debug("SerialManager Instance called")
        if cls.__instance is None:
            return SerialManager()
        else:
            return cls.__instance

    def __init__(self):
        """
        Follows a singleton pattern and prevents instantiation of more than one Serial Manager.

        :returns: Nothing
        """

        if SerialManager.__instance is not None:

            self.logger.error("Attempted to recreate an instance of a SerialManager - should use Instance()")
            raise Exception(
                "Attempted to instantiate a new SerialManager, but only one instance is"
                " allowed.  Use the Instance() method instead")
        else:
            self.logger.debug("SerialManager __init__")
            SerialManager.__instance = self

        # create a database object
        try:
            self.database = DatabaseServer()
        except ConnectionError as e:
            self.logger.critical("ConnectionError when attempting to start the DatabaseServer!")
            self.logger.critical(e)

        # work out which serial ports are connected
        self.get_available_ports()

        # register signals
        logging_started.connect(self.start)
        logging_stopped.connect(self.stop)

    def get_available_ports(self):
        """
        Generates a list of available serial ports, mapping their ID to
        the COM* or /dev/tty* reference.  Adapted from http://stackoverflow.com/a/14224477/233608

        :returns: Nothing
        """
        self.logger.info("Scanning for available serial ports")
        self.serial_mapping = {}
        ports = []

        # Windows
        if os.name == 'nt':
            self.logger.debug("Performing Windows scan")
            # Scan for available ports.
            available = []
            for i in range(256):
                try:
                    portname = "COM%s" % (i + 1)
                    s = serial.Serial(portname)
                    s.close()
                    ports.append(portname)

                except serial.SerialException:
                    pass
        else:
            # Mac / Linux
            self.logger.debug("Performing Mac/Linux scan")
            for port in comports():
                ports.append(port[0])

        for port in ports:
            board_id = self.send_id_request(port)
            if board_id is not None:
                self.logger.info("Found board ID %s at %s" % (board_id, port))
                self.serial_mapping[hex(board_id)[2:].zfill(2)] = port

    def create_serial_connection(self, port_name, baud_rate=57600, read_timeout=3):
        """
        Creates a serial port connection, opens it and returns it

        :param port_name: the name of the port to open (for instance COM3)
        :param baud_rate: the baud rate of the serial connection (default 57600)
        :param read_timeout: the timeout to use for reading from ports (default 3 seconds)

        :return: An open serial port object
        """
        self.logger.debug("Creating and opening serial port, %s @ %s" % (port_name, baud_rate))
        return serial.Serial(port_name, baudrate=baud_rate, timeout=read_timeout)

    def receive_serial_data(self, board_id, port_name):
        """
        Requests a transmission from the specified board and
        saves the returned data to the database

        :param board_id: the ID of the board in hex form, (e.g. "08" for board with ID 8)
        :param port_name: the name of the port to open (for instance COM3)

        :returns: Nothing
        """
        port = self.create_serial_connection(port_name)

        # send the transmit request
        port.write(board_id + SerialCommands['TRANSMIT'] + "\n")

        # readlines until no more lines left (will read for the timeout period)
        lines = port.readlines().replace('\n', '').replace('\r','')
        for line in lines:
            line_size = len(line)
            if line_size < 4:
                self.logger.debug("Received short message (%s) from board %s, ignoring" % (line, board_id))
                pass
            elif line_size == 4:
                # a short message
                command = line[2:]
                if command == SerialCommands['ACK']:
                    self.logger.debug("Received serial ACK from board %s" % board_id)
                    break  # all done, ignore the rest
            else:
                # a data message, save it for later
                self.logger.debug("Received serial message from board %s: %s" % (board_id, line))
                self.database.queue(line)

        self.logger.debug("Finished receiving data from board %s" % board_id)
        port.close()

    def send_id_request(self, port_name):
        """
        Requests an ID from the serial port name and returns it.
        If no ID is found, return None

        :param port_name: the name of the port to open (for instance COM3)

        :returns: A two digit hex board ID, or None if no ID was found
        """
        port = self.create_serial_connection(port_name)
        board_id = None
        serial_buffer = ""

        # clear out any junk in the board's serial buffer and ignore the response
        port.write('\n')
        port.readline()

        # send the ID request
        port.write('00' + SerialCommands['ID'] + '\n')
        serial_buffer = port.readline()
        port.close()

        # check if a valid id was returned
        if len(serial_buffer) > 2:
            board_id = int(serial_buffer[0:2], 16)
            self.logger.debug("Received serial ID %s from port %s" % (board_id, port_name))

        return board_id

    def send_command_with_ack(self, command, board_id, port_name):
        """
        Sends the given command over the serial port and checks for
        an ACK response.  Returns None if the ACK was received, and the
        received message otherwise

        :param command: the string command to send over the serial port, from the SerialCommands constant
        :param board_id: the ID of the board in hex form, (e.g. "08" for board with ID 8)
        :param port_name: the name of the port to open (for instance COM3)

        :returns: the board response if an error was received, or None if ACK was received
        """
        port = self.create_serial_connection(port_name)

        # clear existing
        port.write('\n')
        port.readline()

        # write the command
        port.write(board_id + command)

        # read the response
        serial_buffer = port.readline().replace('\n', '').replace('\r', '')
        port.close()

        # TODO: properly handle errors
        self.logger.debug("Sent %s on serial to %s @ %s, received %s" % (command, board_id, port_name, serial_buffer))

        if len(serial_buffer) != 4 or serial_buffer[2:] != SerialCommands['ACK']:
            return serial_buffer

        return None

    def start(self, signal_args):
        """
        Starts listening on the serial ports and polling for updates every SerialUpdatePeriod seconds

        :param signal_args: the arguments provided by the blinker signal (unused)

        :returns: Nothing
        """

        # enter a new session
        session_id = self.database.start_session()

        # send a start signal to all boards
        for k in self.serial_mapping.keys():
            success = self.send_command_with_ack(SerialCommands['START'], k, self.serial_mapping[k])

            # log errors for now
            if not success is None:
                self.logger.warn("Received '%s' instead of ACK from board ID %s on START" % (success, k))

        # Start a thread for polling serial for updates
        self.__stop_event = threading.Event()
        self.__serial_thread = threading.Thread(target=self.__poll_serial, args=[self.__stop_event])
        self.__serial_thread.daemon = True
        self.__serial_thread.start()
        self.logger.debug("Started serial polling thread: %s" % self.__serial_thread.name)

        # log about serial listening starting
        self.logger.info("Commenced logging session %s" % session_id)

    def stop(self, signal_args):
        """
        Stops logging data and sends a STOP request to all boards

        :param signal_args: the arguments provided by the blinker signal (unused)

        :returns: Nothing
        """

        self.logger.debug("Received signal to stop logging")

        # send a stop signal to all boards
        for k in self.serial_mapping.keys():
            success = self.send_command_with_ack(SerialCommands['STOP'], k, self.serial_mapping[k])

            # log errors for now
            if not success is None:
                self.logger.warn("Received '%s' instead of ACK from board ID %s on STOP" % (success, k))

        # end the new session
        self.database.stop_session()
        self.__serial_thread.join()
        self.logger.info("Serial polling thread stopped")

    def __poll_serial(self, stop_event):
        """
        A thread which periodically polls a serial connection until a stop_event is received

        :param stop_event: the threading Event which triggers stopping serial listening

        :returns: Nothing
        """
        while not stop_event.is_set():
            # enumerate each port
            for k in self.serial_mapping.keys():
                self.receive_serial_data(k, self.serial_mapping[k])

            time.sleep(SerialUpdatePeriod)

        self.logger.debug("Exited poll serial thread")
<|endoftext|>