<commit_before>"""
South's fake ORM; lets you not have to write SQL inside migrations.
Roughly emulates the real Django ORM, to a point.
"""

import inspect
import datetime

from django.db import models
from django.db.models.loading import cache
from django.core.exceptions import ImproperlyConfigured

from south.db import db
from south.utils import ask_for_it_by_name
from south.hacks import hacks
from south.exceptions import UnfreezeMeLater, ORMBaseNotIncluded, ImpossibleORMUnfreeze

class ModelsLocals(object):

    """
    Custom dictionary-like class to be locals();
    falls back to lowercase search for items that don't exist
    (because we store model names as lowercase).
    """

    def __init__(self, data):
        self.data = data

    def __getitem__(self, key):
        try:
            return self.data[key]
        except KeyError:
            return self.data[key.lower()]

# Stores already-created ORMs.
_orm_cache = {}

def FakeORM(*args):
    """
    Creates a Fake Django ORM.
    This is actually a memoised constructor; the real class is _FakeORM.
    """
    if not args in _orm_cache:
        _orm_cache[args] = _FakeORM(*args)
    return _orm_cache[args]

class LazyFakeORM(object):
    """
    In addition to memoising the ORM call, this function lazily generates them
    for a Migration class. Assign the result of this to (for example)
    .orm, and as soon as .orm is accessed the ORM will be created.
    """

    def __init__(self, *args):
        self._args = args
        self.orm = None

    def __get__(self, obj, type=None):
        if not self.orm:
            self.orm = FakeORM(*self._args)
        return self.orm

class _FakeORM(object):

    """
    Simulates the Django ORM at some point in time,
    using a frozen definition on the Migration class.
    """

    def __init__(self, cls, app):
        self.default_app = app
        self.cls = cls
        # Try loading the models off the migration class; default to no models.
        self.models = {}
        try:
            self.models_source = cls.models
        except AttributeError:
            return

        # Start a 'new' AppCache
        hacks.clear_app_cache()

        # Now, make each model's data into a FakeModel
        # We first make entries for each model that are just its name
        # This allows us to have circular model dependency loops
        model_names = []
        for name, data in self.models_source.items():
            # Make sure there's some kind of Meta
            if "Meta" not in data:
                data['Meta'] = {}
            try:
                app_label, model_name = name.split(".", 1)
            except ValueError:
                app_label = self.default_app
                model_name = name

            # If there's an object_name in the Meta, use it and remove it
            if "object_name" in data['Meta']:
                model_name = data['Meta']['object_name']
                del data['Meta']['object_name']

            name = "%s.%s" % (app_label, model_name)
            self.models[name.lower()] = name
            model_names.append((name.lower(), app_label, model_name, data))

        # Loop until model_names is entry, or hasn't shrunk in size since
        # last iteration.
        # The make_model method can ask to postpone a model; it's then pushed
        # to the back of the queue. Because this is currently only used for
        # inheritance, it should thus theoretically always decrease by one.
        last_size = None
        while model_names:
            # First, make sure we've shrunk.
            if len(model_names) == last_size:
                raise ImpossibleORMUnfreeze()
            last_size = len(model_names)
            # Make one run through
            postponed_model_names = []
            for name, app_label, model_name, data in model_names:
                try:
                    self.models[name] = self.make_model(app_label, model_name, data)
                except UnfreezeMeLater:
                    postponed_model_names.append((name, app_label, model_name, data))
            # Reset
            model_names = postponed_model_names

        # And perform the second run to iron out any circular/backwards depends.
        self.retry_failed_fields()

        # Force evaluation of relations on the models now
        for model in self.models.values():
            model._meta.get_all_field_names()

        # Reset AppCache
        hacks.unclear_app_cache()

    def __iter__(self):
        return iter(self.models.values())

    def __getattr__(self, key):
        fullname = (self.default_app+"."+key).lower()
        try:
            return self.models[fullname]
        except KeyError:
            raise AttributeError("The model '%s' from the app '%s' is not available in this migration." % (key, self.default_app))

    def __getitem__(self, key):
        # Detect if they asked for a field on a model or not.
        if ":" in key:
            key, fname = key.split(":")
        else:
            fname = None
        # Now, try getting the model
        key = key.lower()
        try:
            model = self.models[key]
        except KeyError:
            try:
                app, model = key.split(".", 1)
            except ValueError:
                raise KeyError("The model '%s' is not in appname.modelname format." % key)
            else:
                raise KeyError("The model '%s' from the app '%s' is not available in this migration." % (model, app))
        # If they asked for a field, get it.
        if fname:
            return model._meta.get_field_by_name(fname)[0]
        else:
            return model

    def eval_in_context(self, code, app, extra_imports={}):
        "Evaluates the given code in the context of the migration file."

        # Drag in the migration module's locals (hopefully including models.py)
        fake_locals = dict(inspect.getmodule(self.cls).__dict__)

        # Remove all models from that (i.e. from modern models.py), to stop pollution
        for key, value in fake_locals.items():
            if isinstance(value, type) and issubclass(value, models.Model) and hasattr(value, "_meta"):
                del fake_locals[key]

        # We add our models into the locals for the eval
        fake_locals.update(dict([
            (name.split(".")[-1], model)
            for name, model in self.models.items()
        ]))

        # Make sure the ones for this app override.
        fake_locals.update(dict([
            (name.split(".")[-1], model)
            for name, model in self.models.items()
            if name.split(".")[0] == app
        ]))

        # Ourselves as orm, to allow non-fail cross-app referencing
        fake_locals['orm'] = self

        # And a fake _ function
        fake_locals['_'] = lambda x: x

        # Datetime; there should be no datetime direct accesses
        fake_locals['datetime'] = datetime

        # Now, go through the requested imports and import them.
        for name, value in extra_imports.items():
            # First, try getting it out of locals.
            parts = value.split(".")
            try:
                obj = fake_locals[parts[0]]
                for part in parts[1:]:
                    obj = getattr(obj, part)
            except (KeyError, AttributeError):
                pass
            else:
                fake_locals[name] = obj
                continue
            # OK, try to import it directly
            try:
                fake_locals[name] = ask_for_it_by_name(value)
            except ImportError:
                if name == "SouthFieldClass":
                    raise ValueError("Cannot import the required field '%s'" % value)
                else:
                    print "WARNING: Cannot import '%s'" % value

        # Use ModelsLocals to make lookups work right for CapitalisedModels
        fake_locals = ModelsLocals(fake_locals)

        return eval(code, globals(), fake_locals)

    def make_meta(self, app, model, data, stub=False):
        "Makes a Meta class out of a dict of eval-able arguments."
        results = {'app_label': app}
        for key, code in data.items():
            # Some things we never want to use.
            if key in ["_bases", "_ormbases"]:
                continue
            # Some things we don't want with stubs.
            if stub and key in ["order_with_respect_to"]:
                continue
            # OK, add it.
            try:
                results[key] = self.eval_in_context(code, app)
            except (NameError, AttributeError), e:
                raise ValueError("Cannot successfully create meta field '%s' for model '%s.%s': %s." % (
                    key, app, model, e
                ))
        return type("Meta", tuple(), results)

    def make_model(self, app, name, data):
        "Makes a Model class out of the given app name, model name and pickled data."

        # Extract any bases out of Meta
        if "_ormbases" in data['Meta']:
            # Make sure everything we depend on is done already; otherwise, wait.
            for key in data['Meta']['_ormbases']:
                key = key.lower()
                if key not in self.models:
                    raise ORMBaseNotIncluded("Cannot find ORM base %s" % key)
                elif isinstance(self.models[key], basestring):
                    # Then the other model hasn't been unfrozen yet.
                    # We postpone ourselves; the situation will eventually resolve.
                    raise UnfreezeMeLater()
            bases = [self.models[key.lower()] for key in data['Meta']['_ormbases']]
        # Perhaps the old style?
        if "_bases" in data['Meta']:
            bases = map(ask_for_it_by_name, data['Meta']['_bases'])
        # Ah, bog standard, then.
        else:
            bases = [models.Model]

        # Turn the Meta dict into a basic class
        meta = self.make_meta(app, name, data['Meta'], data.get("_stub", False))

        failed_fields = {}
        fields = {}
        stub = False

        # Now, make some fields!
        for fname, params in data.items():
            # If it's the stub marker, ignore it.
            if fname == "_stub":
                stub = bool(params)
                continue
            elif fname == "Meta":
                continue
            elif not params:
                raise ValueError("Field '%s' on model '%s.%s' has no definition." % (fname, app, name))
            elif isinstance(params, (str, unicode)):
                # It's a premade definition string! Let's hope it works...
                code = params
                extra_imports = {}
            else:
                # If there's only one parameter (backwards compat), make it 3.
                if len(params) == 1:
                    params = (params[0], [], {})
                # There should be 3 parameters. Code is a tuple of (code, what-to-import)
                if len(params) == 3:
                    code = "SouthFieldClass(%s)" % ", ".join(
                        params[1] +
                        ["%s=%s" % (n, v) for n, v in params[2].items()]
                    )
                    extra_imports = {"SouthFieldClass": params[0]}
                else:
                    raise ValueError("Field '%s' on model '%s.%s' has a weird definition length (should be 1 or 3 items)." % (fname, app, name))

            try:
                # Execute it in a probably-correct context.
                field = self.eval_in_context(code, app, extra_imports)
            except (NameError, AttributeError, AssertionError, KeyError):
                # It might rely on other models being around. Add it to the
                # model for the second pass.
                failed_fields[fname] = (code, extra_imports)
            else:
                fields[fname] = field

        # Find the app in the Django core, and get its module
        more_kwds = {}
        try:
            app_module = models.get_app(app)
            more_kwds['__module__'] = app_module.__name__
        except ImproperlyConfigured:
            # The app this belonged to has vanished, but thankfully we can still
            # make a mock model, so ignore the error.
            more_kwds['__module__'] = '_south_mock'

        more_kwds['Meta'] = meta

        # Make our model
        fields.update(more_kwds)

        model = type(
            str(name),
            tuple(bases),
            fields,
        )

        # If this is a stub model, change Objects to a whiny class
        if stub:
            model.objects = WhinyManager()
            # Also, make sure they can't instantiate it
            model.__init__ = whiny_method
        else:
            model.objects = NoDryRunManager(model.objects)

        if failed_fields:
            model._failed_fields = failed_fields

        return model

    def retry_failed_fields(self):
        "Tries to re-evaluate the _failed_fields for each model."
        for modelkey, model in self.models.items():
            app, modelname = modelkey.split(".", 1)
            if hasattr(model, "_failed_fields"):
                for fname, (code, extra_imports) in model._failed_fields.items():
                    try:
                        field = self.eval_in_context(code, app, extra_imports)
                    except (NameError, AttributeError, AssertionError, KeyError), e:
                        # It's failed again. Complain.
                        raise ValueError("Cannot successfully create field '%s' for model '%s': %s." % (
                            fname, modelname, e
                        ))
                    else:
                        # Startup that field.
                        model.add_to_class(fname, field)

class WhinyManager(object):
    "A fake manager that whines whenever you try to touch it. For stub models."

    def __getattr__(self, key):
        raise AttributeError("You cannot use items from a stub model.")

class NoDryRunManager(object):
    """
    A manager that always proxies through to the real manager,
    unless a dry run is in progress.
    """

    def __init__(self, real):
        self.real = real

    def __getattr__(self, name):
        if db.dry_run:
            raise AttributeError("You are in a dry run, and cannot access the ORM.\nWrap ORM sections in 'if not db.dry_run:', or if the whole migration is only a data migration, set no_dry_run = True on the Migration class.")
        return getattr(self.real, name)

def whiny_method(*a, **kw):
    raise ValueError("You cannot instantiate a stub model.")
<commit_msg>Fix for #373 (bad model inheritance)<commit_after>"""
South's fake ORM; lets you not have to write SQL inside migrations.
Roughly emulates the real Django ORM, to a point.
"""

import inspect
import datetime

from django.db import models
from django.db.models.loading import cache
from django.core.exceptions import ImproperlyConfigured

from south.db import db
from south.utils import ask_for_it_by_name
from south.hacks import hacks
from south.exceptions import UnfreezeMeLater, ORMBaseNotIncluded, ImpossibleORMUnfreeze

class ModelsLocals(object):

    """
    Custom dictionary-like class to be locals();
    falls back to lowercase search for items that don't exist
    (because we store model names as lowercase).
    """

    def __init__(self, data):
        self.data = data

    def __getitem__(self, key):
        try:
            return self.data[key]
        except KeyError:
            return self.data[key.lower()]

# Stores already-created ORMs.
_orm_cache = {}

def FakeORM(*args):
    """
    Creates a Fake Django ORM.
    This is actually a memoised constructor; the real class is _FakeORM.
    """
    if not args in _orm_cache:
        _orm_cache[args] = _FakeORM(*args)
    return _orm_cache[args]

class LazyFakeORM(object):
    """
    In addition to memoising the ORM call, this function lazily generates them
    for a Migration class. Assign the result of this to (for example)
    .orm, and as soon as .orm is accessed the ORM will be created.
    """

    def __init__(self, *args):
        self._args = args
        self.orm = None

    def __get__(self, obj, type=None):
        if not self.orm:
            self.orm = FakeORM(*self._args)
        return self.orm

class _FakeORM(object):

    """
    Simulates the Django ORM at some point in time,
    using a frozen definition on the Migration class.
    """

    def __init__(self, cls, app):
        self.default_app = app
        self.cls = cls
        # Try loading the models off the migration class; default to no models.
        self.models = {}
        try:
            self.models_source = cls.models
        except AttributeError:
            return

        # Start a 'new' AppCache
        hacks.clear_app_cache()

        # Now, make each model's data into a FakeModel
        # We first make entries for each model that are just its name
        # This allows us to have circular model dependency loops
        model_names = []
        for name, data in self.models_source.items():
            # Make sure there's some kind of Meta
            if "Meta" not in data:
                data['Meta'] = {}
            try:
                app_label, model_name = name.split(".", 1)
            except ValueError:
                app_label = self.default_app
                model_name = name

            # If there's an object_name in the Meta, use it and remove it
            if "object_name" in data['Meta']:
                model_name = data['Meta']['object_name']
                del data['Meta']['object_name']

            name = "%s.%s" % (app_label, model_name)
            self.models[name.lower()] = name
            model_names.append((name.lower(), app_label, model_name, data))

        # Loop until model_names is entry, or hasn't shrunk in size since
        # last iteration.
        # The make_model method can ask to postpone a model; it's then pushed
        # to the back of the queue. Because this is currently only used for
        # inheritance, it should thus theoretically always decrease by one.
        last_size = None
        while model_names:
            # First, make sure we've shrunk.
            if len(model_names) == last_size:
                raise ImpossibleORMUnfreeze()
            last_size = len(model_names)
            # Make one run through
            postponed_model_names = []
            for name, app_label, model_name, data in model_names:
                try:
                    self.models[name] = self.make_model(app_label, model_name, data)
                except UnfreezeMeLater:
                    postponed_model_names.append((name, app_label, model_name, data))
            # Reset
            model_names = postponed_model_names

        # And perform the second run to iron out any circular/backwards depends.
        self.retry_failed_fields()

        # Force evaluation of relations on the models now
        for model in self.models.values():
            model._meta.get_all_field_names()

        # Reset AppCache
        hacks.unclear_app_cache()

    def __iter__(self):
        return iter(self.models.values())

    def __getattr__(self, key):
        fullname = (self.default_app+"."+key).lower()
        try:
            return self.models[fullname]
        except KeyError:
            raise AttributeError("The model '%s' from the app '%s' is not available in this migration." % (key, self.default_app))

    def __getitem__(self, key):
        # Detect if they asked for a field on a model or not.
        if ":" in key:
            key, fname = key.split(":")
        else:
            fname = None
        # Now, try getting the model
        key = key.lower()
        try:
            model = self.models[key]
        except KeyError:
            try:
                app, model = key.split(".", 1)
            except ValueError:
                raise KeyError("The model '%s' is not in appname.modelname format." % key)
            else:
                raise KeyError("The model '%s' from the app '%s' is not available in this migration." % (model, app))
        # If they asked for a field, get it.
        if fname:
            return model._meta.get_field_by_name(fname)[0]
        else:
            return model

    def eval_in_context(self, code, app, extra_imports={}):
        "Evaluates the given code in the context of the migration file."

        # Drag in the migration module's locals (hopefully including models.py)
        fake_locals = dict(inspect.getmodule(self.cls).__dict__)

        # Remove all models from that (i.e. from modern models.py), to stop pollution
        for key, value in fake_locals.items():
            if isinstance(value, type) and issubclass(value, models.Model) and hasattr(value, "_meta"):
                del fake_locals[key]

        # We add our models into the locals for the eval
        fake_locals.update(dict([
            (name.split(".")[-1], model)
            for name, model in self.models.items()
        ]))

        # Make sure the ones for this app override.
        fake_locals.update(dict([
            (name.split(".")[-1], model)
            for name, model in self.models.items()
            if name.split(".")[0] == app
        ]))

        # Ourselves as orm, to allow non-fail cross-app referencing
        fake_locals['orm'] = self

        # And a fake _ function
        fake_locals['_'] = lambda x: x

        # Datetime; there should be no datetime direct accesses
        fake_locals['datetime'] = datetime

        # Now, go through the requested imports and import them.
        for name, value in extra_imports.items():
            # First, try getting it out of locals.
            parts = value.split(".")
            try:
                obj = fake_locals[parts[0]]
                for part in parts[1:]:
                    obj = getattr(obj, part)
            except (KeyError, AttributeError):
                pass
            else:
                fake_locals[name] = obj
                continue
            # OK, try to import it directly
            try:
                fake_locals[name] = ask_for_it_by_name(value)
            except ImportError:
                if name == "SouthFieldClass":
                    raise ValueError("Cannot import the required field '%s'" % value)
                else:
                    print "WARNING: Cannot import '%s'" % value

        # Use ModelsLocals to make lookups work right for CapitalisedModels
        fake_locals = ModelsLocals(fake_locals)

        return eval(code, globals(), fake_locals)

    def make_meta(self, app, model, data, stub=False):
        "Makes a Meta class out of a dict of eval-able arguments."
        results = {'app_label': app}
        for key, code in data.items():
            # Some things we never want to use.
            if key in ["_bases", "_ormbases"]:
                continue
            # Some things we don't want with stubs.
            if stub and key in ["order_with_respect_to"]:
                continue
            # OK, add it.
            try:
                results[key] = self.eval_in_context(code, app)
            except (NameError, AttributeError), e:
                raise ValueError("Cannot successfully create meta field '%s' for model '%s.%s': %s." % (
                    key, app, model, e
                ))
        return type("Meta", tuple(), results)

    def make_model(self, app, name, data):
        "Makes a Model class out of the given app name, model name and pickled data."

        # Extract any bases out of Meta
        if "_ormbases" in data['Meta']:
            # Make sure everything we depend on is done already; otherwise, wait.
            for key in data['Meta']['_ormbases']:
                key = key.lower()
                if key not in self.models:
                    raise ORMBaseNotIncluded("Cannot find ORM base %s" % key)
                elif isinstance(self.models[key], basestring):
                    # Then the other model hasn't been unfrozen yet.
                    # We postpone ourselves; the situation will eventually resolve.
                    raise UnfreezeMeLater()
            bases = [self.models[key.lower()] for key in data['Meta']['_ormbases']]
        # Perhaps the old style?
        elif "_bases" in data['Meta']:
            bases = map(ask_for_it_by_name, data['Meta']['_bases'])
        # Ah, bog standard, then.
        else:
            bases = [models.Model]

        # Turn the Meta dict into a basic class
        meta = self.make_meta(app, name, data['Meta'], data.get("_stub", False))

        failed_fields = {}
        fields = {}
        stub = False

        # Now, make some fields!
        for fname, params in data.items():
            # If it's the stub marker, ignore it.
            if fname == "_stub":
                stub = bool(params)
                continue
            elif fname == "Meta":
                continue
            elif not params:
                raise ValueError("Field '%s' on model '%s.%s' has no definition." % (fname, app, name))
            elif isinstance(params, (str, unicode)):
                # It's a premade definition string! Let's hope it works...
                code = params
                extra_imports = {}
            else:
                # If there's only one parameter (backwards compat), make it 3.
                if len(params) == 1:
                    params = (params[0], [], {})
                # There should be 3 parameters. Code is a tuple of (code, what-to-import)
                if len(params) == 3:
                    code = "SouthFieldClass(%s)" % ", ".join(
                        params[1] +
                        ["%s=%s" % (n, v) for n, v in params[2].items()]
                    )
                    extra_imports = {"SouthFieldClass": params[0]}
                else:
                    raise ValueError("Field '%s' on model '%s.%s' has a weird definition length (should be 1 or 3 items)." % (fname, app, name))

            try:
                # Execute it in a probably-correct context.
                field = self.eval_in_context(code, app, extra_imports)
            except (NameError, AttributeError, AssertionError, KeyError):
                # It might rely on other models being around. Add it to the
                # model for the second pass.
                failed_fields[fname] = (code, extra_imports)
            else:
                fields[fname] = field

        # Find the app in the Django core, and get its module
        more_kwds = {}
        try:
            app_module = models.get_app(app)
            more_kwds['__module__'] = app_module.__name__
        except ImproperlyConfigured:
            # The app this belonged to has vanished, but thankfully we can still
            # make a mock model, so ignore the error.
            more_kwds['__module__'] = '_south_mock'

        more_kwds['Meta'] = meta

        # Make our model
        fields.update(more_kwds)

        model = type(
            str(name),
            tuple(bases),
            fields,
        )

        # If this is a stub model, change Objects to a whiny class
        if stub:
            model.objects = WhinyManager()
            # Also, make sure they can't instantiate it
            model.__init__ = whiny_method
        else:
            model.objects = NoDryRunManager(model.objects)

        if failed_fields:
            model._failed_fields = failed_fields

        return model

    def retry_failed_fields(self):
        "Tries to re-evaluate the _failed_fields for each model."
        for modelkey, model in self.models.items():
            app, modelname = modelkey.split(".", 1)
            if hasattr(model, "_failed_fields"):
                for fname, (code, extra_imports) in model._failed_fields.items():
                    try:
                        field = self.eval_in_context(code, app, extra_imports)
                    except (NameError, AttributeError, AssertionError, KeyError), e:
                        # It's failed again. Complain.
                        raise ValueError("Cannot successfully create field '%s' for model '%s': %s." % (
                            fname, modelname, e
                        ))
                    else:
                        # Startup that field.
                        model.add_to_class(fname, field)

class WhinyManager(object):
    "A fake manager that whines whenever you try to touch it. For stub models."

    def __getattr__(self, key):
        raise AttributeError("You cannot use items from a stub model.")

class NoDryRunManager(object):
    """
    A manager that always proxies through to the real manager,
    unless a dry run is in progress.
    """

    def __init__(self, real):
        self.real = real

    def __getattr__(self, name):
        if db.dry_run:
            raise AttributeError("You are in a dry run, and cannot access the ORM.\nWrap ORM sections in 'if not db.dry_run:', or if the whole migration is only a data migration, set no_dry_run = True on the Migration class.")
        return getattr(self.real, name)

def whiny_method(*a, **kw):
    raise ValueError("You cannot instantiate a stub model.")
<|endoftext|>


<commit_before>#!/usr/bin/env python

# Built-in packages
import re

# Add-on packages
import pandas as pd

class wideToDesign:
    """ Class to handle generic data in a wide format with an associated design file. """
    def __init__(self, wide, design, uniqID, group=False, clean_string=False):
        """ Import and set-up data.

        Import data both wide formated data and a design file. Set-up basic
        attributes.

        Args:
            wide (TSV): A table in wide format with compounds/genes as rows and
                samples as columns.

                Name     sample1   sample2   sample3
                ------------------------------------
                one      10        20        10
                two      10        20        10

            design (TSV): A table relating samples ('sampleID') to groups or
                treatments.

                sampleID   group1  group2
                -------------------------
                sample1    g1      t1
                sample2    g1      t1
                sample3    g1      t1

            uniqID (str): The name of the unique identifier column in 'wide'
                (i.e. The column with compound/gene names).

            group (str): The name of column names in 'design' that give
                group information. For example: treatment

            clean_string (bool): If True remove special characters from strings
                in dataset.

        Returns:
            **Attribute**

            self.uniqID (str): The name of the unique identifier column in 'wide'
                (i.e. The column with compound/gene names).

            self.wide (pd.DataFrame): A wide formatted table with compound/gene
                as row and sample as columns.

            self.sampleIDs (list): An list of sampleIDs. These will correspond
                to columns in self.wide.

            self.design (pd.DataFrame): A table relating sampleID to groups.

            self.group (list): A list of column names in self.design that give
                group information. For example: treatment, tissue

            self.levels (list): A list of levels in self.group. For example:
                trt1, tr2, control.

        """
        self.origString = dict()

        # Import wide formatted data file
        try:
            self.uniqID = uniqID
            self.wide = pd.read_table(wide)
            if clean_string:
                self.wide[self.uniqID] = self.wide[self.uniqID].apply(lambda x: self._cleanStr(x))
            self.wide.set_index(self.uniqID, inplace=True)
        except:
            print "Please make sure that your data file has a column called '{0}'.".format(uniqID)
            raise ValueError

        # Import design file
        try:
            self.design = pd.read_table(design, index_col='sampleID')

            # Create a list of sampleIDs, but first check that they are present
            # in the wide data.
            self.sampleIDs = list()

            for sample in self.design.index.tolist():
                if str(sample) in self.wide.columns:
                    self.sampleIDs.append(sample)
        except:
            print "Please make sure that your design file has a column called 'sampleID'."
            raise ValueError

        # Set up group information
        if group:
            if clean_string:
                self.group = self._cleanStr(group)
                self.design.columns = [self._cleanStr(x) for x in self.design.columns]
            else:
                self.group = group

            self.design = self.design[[self.group, ]]   # Only keep group columns in the design file
            grp = self.design.groupby(self.group)
            self.levels = sorted(grp.groups.keys())  # Get a list of group levels

    def _cleanStr(self, x):
        """ Clean strings so they behave.

        For some modules, uniqIDs and groups cannot contain spaces, '-', '*',
        '/', '+', or '()'. For example, statsmodel parses the strings and interprets
        them in the model.

        Args:
            x (str): A string that needs cleaning

        Returns:
            x (str): The cleaned string.

            self.origString (dict): A dictionary where the key is the new
                string and the value is the original string. This will be useful
                for reverting back to original values.

        """
        if isinstance(x, str):
            val = x
            x = x.replace(' ', '_')
            x = x.replace('-', '_')
            x = x.replace('*', '_')
            x = x.replace('/', '_')
            x = x.replace('+', '_')
            x = x.replace('(', '_')
            x = x.replace(')', '_')
            x = x.replace(')', '_')
            x = re.sub(r'^([0-9].*)', r'_\1', x)
            self.origString[x] = val
        return x

    def revertStr(self, x):
        """ Revert strings back to their original value so they behave well.

        Clean strings may need to be revertd back to original values for
        convience.

        Args:
            x (str): A string that needs cleaning

            self.origString (dict): A dictionary where the key is the cleaned
                string and the value is the original string.

        Returns:
            x (str): Original string.

        """
        if isinstance(x, str) and x in self.origString:
            x = self.origString[x]
        return x

    def melt(self):
        """ Convert a wide formated table to a long formated table.

        Args:
            self.wide (pd.DataFrame): A wide formatted table with compound/gene
                as row and sample as columns.

            self.uniqID (str): The name of the unique identifier column in 'wide'
                (i.e. The column with compound/gene names).

            self.sampleIDs (list): An list of sampleIDs. These will correspond
                to columns in self.wide.

        Returns:
            **Attributes**

            self.long (pd.DataFrame): Creates a new attribute called self.long
                that also has group information merged to the dataset.

        """
        melted = pd.melt(self.wide.reset_index(), id_vars=self.uniqID, value_vars=self.sampleIDs,
                         var_name='sampleID')
        melted.set_index('sampleID', inplace=True)
        self.long = melted.join(self.design).reset_index()   # merge on group information using sampleIDs as key

    def transpose(self):
        """ Transpose the wide table and merge on treatment information.

        Args:
            self.wide (pd.DataFrame): A wide formatted table with compound/gene
                as row and sample as columns.

            self.design (pd.DataFrame): A table relating sampleID to groups.

        Returns:
            merged (pd.DataFrame): A wide formatted table with sampleID as row
                and compound/gene as column. Also has column with group ID.

        """
        trans = self.wide[self.sampleIDs].T

        # Merge on group information using table index (aka 'sampleID')
        merged = trans.join(self.design)
        merged.index.name = 'sampleID'
        return merged

    def getRow(self, ID):
        """ Get a row corresponding to a uniqID.

        Args:
            self.wide (pd.DataFrame): A wide formatted table with compound/gene
                as row and sample as columns.

            self.uniqID (str): The name of the unique identifier column in 'wide'
                (i.e. The column with compound/gene names).

            ID (str): A string refering to a uniqID in the dataset.

        Returns:
            (pd.DataFrame): with only the corresponding rows from the uniqID.

        """
        return self.wide[self.wide[self.uniqID] == ID]

if __name__ == '__main__':
    pass
<commit_msg>Added another string.<commit_after>#!/usr/bin/env python

# Built-in packages
import re

# Add-on packages
import pandas as pd

class wideToDesign:
    """ Class to handle generic data in a wide format with an associated design file. """
    def __init__(self, wide, design, uniqID, group=False, clean_string=False):
        """ Import and set-up data.

        Import data both wide formated data and a design file. Set-up basic
        attributes.

        Args:
            wide (TSV): A table in wide format with compounds/genes as rows and
                samples as columns.

                Name     sample1   sample2   sample3
                ------------------------------------
                one      10        20        10
                two      10        20        10

            design (TSV): A table relating samples ('sampleID') to groups or
                treatments.

                sampleID   group1  group2
                -------------------------
                sample1    g1      t1
                sample2    g1      t1
                sample3    g1      t1

            uniqID (str): The name of the unique identifier column in 'wide'
                (i.e. The column with compound/gene names).

            group (str): The name of column names in 'design' that give
                group information. For example: treatment

            clean_string (bool): If True remove special characters from strings
                in dataset.

        Returns:
            **Attribute**

            self.uniqID (str): The name of the unique identifier column in 'wide'
                (i.e. The column with compound/gene names).

            self.wide (pd.DataFrame): A wide formatted table with compound/gene
                as row and sample as columns.

            self.sampleIDs (list): An list of sampleIDs. These will correspond
                to columns in self.wide.

            self.design (pd.DataFrame): A table relating sampleID to groups.

            self.group (list): A list of column names in self.design that give
                group information. For example: treatment, tissue

            self.levels (list): A list of levels in self.group. For example:
                trt1, tr2, control.

        """
        self.origString = dict()

        # Import wide formatted data file
        try:
            self.uniqID = uniqID
            self.wide = pd.read_table(wide)
            if clean_string:
                self.wide[self.uniqID] = self.wide[self.uniqID].apply(lambda x: self._cleanStr(x))
            self.wide.set_index(self.uniqID, inplace=True)
        except:
            print "Please make sure that your data file has a column called '{0}'.".format(uniqID)
            raise ValueError

        # Import design file
        try:
            self.design = pd.read_table(design, index_col='sampleID')

            # Create a list of sampleIDs, but first check that they are present
            # in the wide data.
            self.sampleIDs = list()

            for sample in self.design.index.tolist():
                if str(sample) in self.wide.columns:
                    self.sampleIDs.append(str(sample))
        except:
            print "Please make sure that your design file has a column called 'sampleID'."
            raise ValueError

        # Set up group information
        if group:
            if clean_string:
                self.group = self._cleanStr(group)
                self.design.columns = [self._cleanStr(x) for x in self.design.columns]
            else:
                self.group = group

            self.design = self.design[[self.group, ]]   # Only keep group columns in the design file
            grp = self.design.groupby(self.group)
            self.levels = sorted(grp.groups.keys())  # Get a list of group levels

    def _cleanStr(self, x):
        """ Clean strings so they behave.

        For some modules, uniqIDs and groups cannot contain spaces, '-', '*',
        '/', '+', or '()'. For example, statsmodel parses the strings and interprets
        them in the model.

        Args:
            x (str): A string that needs cleaning

        Returns:
            x (str): The cleaned string.

            self.origString (dict): A dictionary where the key is the new
                string and the value is the original string. This will be useful
                for reverting back to original values.

        """
        if isinstance(x, str):
            val = x
            x = x.replace(' ', '_')
            x = x.replace('-', '_')
            x = x.replace('*', '_')
            x = x.replace('/', '_')
            x = x.replace('+', '_')
            x = x.replace('(', '_')
            x = x.replace(')', '_')
            x = x.replace(')', '_')
            x = re.sub(r'^([0-9].*)', r'_\1', x)
            self.origString[x] = val
        return x

    def revertStr(self, x):
        """ Revert strings back to their original value so they behave well.

        Clean strings may need to be revertd back to original values for
        convience.

        Args:
            x (str): A string that needs cleaning

            self.origString (dict): A dictionary where the key is the cleaned
                string and the value is the original string.

        Returns:
            x (str): Original string.

        """
        if isinstance(x, str) and x in self.origString:
            x = self.origString[x]
        return x

    def melt(self):
        """ Convert a wide formated table to a long formated table.

        Args:
            self.wide (pd.DataFrame): A wide formatted table with compound/gene
                as row and sample as columns.

            self.uniqID (str): The name of the unique identifier column in 'wide'
                (i.e. The column with compound/gene names).

            self.sampleIDs (list): An list of sampleIDs. These will correspond
                to columns in self.wide.

        Returns:
            **Attributes**

            self.long (pd.DataFrame): Creates a new attribute called self.long
                that also has group information merged to the dataset.

        """
        melted = pd.melt(self.wide.reset_index(), id_vars=self.uniqID, value_vars=self.sampleIDs,
                         var_name='sampleID')
        melted.set_index('sampleID', inplace=True)
        self.long = melted.join(self.design).reset_index()   # merge on group information using sampleIDs as key

    def transpose(self):
        """ Transpose the wide table and merge on treatment information.

        Args:
            self.wide (pd.DataFrame): A wide formatted table with compound/gene
                as row and sample as columns.

            self.design (pd.DataFrame): A table relating sampleID to groups.

        Returns:
            merged (pd.DataFrame): A wide formatted table with sampleID as row
                and compound/gene as column. Also has column with group ID.

        """
        trans = self.wide[self.sampleIDs].T

        # Merge on group information using table index (aka 'sampleID')
        merged = trans.join(self.design)
        merged.index.name = 'sampleID'
        return merged

    def getRow(self, ID):
        """ Get a row corresponding to a uniqID.

        Args:
            self.wide (pd.DataFrame): A wide formatted table with compound/gene
                as row and sample as columns.

            self.uniqID (str): The name of the unique identifier column in 'wide'
                (i.e. The column with compound/gene names).

            ID (str): A string refering to a uniqID in the dataset.

        Returns:
            (pd.DataFrame): with only the corresponding rows from the uniqID.

        """
        return self.wide[self.wide[self.uniqID] == ID]

if __name__ == '__main__':
    pass
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright 2015 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

def post_init(cr, registry):
    """Install hook
    fill website_publication_date field with create_date value for published
    blog.post
    """
    cr.execute("""
        UPDATE
            blog_post
        SET
            website_publication_date = create_date
        WHERE
            website_published = true
    """)
<commit_msg>[MIG][9.0] website_blog_mgmt: Fix Flake8<commit_after># -*- coding: utf-8 -*-
# Copyright 2015 ACSONE SA/NV (<http://acsone.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

def post_init(cr, registry):
    """Install hook
    fill website_publication_date field with create_date value for published
    blog.post
    """
    cr.execute("""
        UPDATE
            blog_post
        SET
            website_publication_date = create_date
        WHERE
            website_published = true
    """)
<|endoftext|>


<commit_before># -*- coding:utf-8 -*-
import os
import time

def dev_robot():
    path = "/home/pi/code/wechatserver/wechatclient/"
    os.chdir(path)
    try:
        os.system("git fetch  --all")
        os.system("git reset --hard tornado")
        os.system("git pull")
    except Exception as e:
        raise

    try:
        os.system("python clinet.py")
    except Exception as e:
        raise e

def timer():
	while True:
		time.sleep(10)
		cas_val = dev_robot()
		print (cas_val)

if __name__ == '__main__':
	timer()
<commit_msg>fix bug<commit_after># -*- coding:utf-8 -*-
import os
import time

def dev_robot():
    path = "/home/pi/code/wechatserver/wechatclient/"
    os.chdir(path)
    try:
        os.system("git fetch  --all")
        os.system("git reset --hard tornado")
        os.system("git pull")
        os.system("python clinet.py")
    except Exception as e:
        raise

def timer():
	while True:
		time.sleep(10)
		cas_val = dev_robot()
		print (cas_val)

if __name__ == '__main__':
	timer()
<|endoftext|>


<commit_before>#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
Statistical analysis and graphical representations of PBs.

Compute Neq, PBs distribution and draw logo representation of PBs.

2013 - P. Poulain, A. G. de Brevern
"""

# ============================================================================
# Modules
# ============================================================================
# Use print as a function for python 3 compatibility
from __future__ import print_function

# Standard modules
import os
import sys
import argparse

# Local module
import pbxplore as pbx

# Weblogolib is an optional requirement
try:
    import weblogolib
except:
    IS_WEBLOGO = False
else:
    IS_WEBLOGO = True

# ============================================================================
# Python2/Python3 compatibility
# ============================================================================

# The range function in python 3 behaves as the range function in python 2
# and returns a generator rather than a list. To produce a list in python 3,
# one should use list(range). Here we change range to behave the same in
# python 2 and in python 3. In both cases, range will return a generator.
try:
    range = xrange
except NameError:
    pass

def user_inputs():
    """
    Handle the user parameters for PBstat.py.

    Returns
    -------
    options : the parsed arguments as parsed by `argparse`.
    """

    parser = argparse.ArgumentParser(
        description="Statistical analysis and graphical representations of PBs.")

    # mandatory arguments
    parser.add_argument("-f", action="store", required=True,
                        help="name of file that contains PBs frequency (count)")
    parser.add_argument("-o", action="store", required=True,
                        help="name for results")

    # optional arguments
    parser.add_argument("--map", action="store_true", default=False, dest="mapdist",
                        help="generate map of the distribution of PBs along protein sequence")
    parser.add_argument("--neq", action="store_true", default=False, dest="neq",
                        help="compute Neq and generate Neq plot along protein sequence")
    parser.add_argument("--logo", action="store_true", default=False, dest="logo",
                        help="generate logo representation of PBs frequency along protein sequence")
    parser.add_argument("--image-format", action='store', type=str,
                        dest='image_format', default='png',
                        choices=['pdf', 'png', 'jpg'],
                        help='File format for all image output.')
    parser.add_argument("--residue-min", action="store", type=int,
                        dest="residue_min", help="defines lower bound of residue frame")
    parser.add_argument("--residue-max", action="store", type=int,
                        dest="residue_max", help="defines upper bound of residue frame")

    # get all parameters
    options = parser.parse_args()

    # check file
    if not os.path.isfile(options.f):
        parser.error("{0}: not a valid file".format(options.f))

    # Check residues min/max
    residues = [options.residue_min, options.residue_max]
    for residue in residues:
        if residue is not None and residue < 0:
            parser.error("residue argument must be >=0")
    if None not in residues and options.residue_min >= options.residue_max:
        parser.error("residue-min must be < residue-max.")

    # Check weblogo
    if options.logo:
        if not IS_WEBLOGO:
            parser.error("Weblogo is not installed; cannot generate the logo image.")

    return options

def check_residue_range(residues, residue_min, residue_max):
    """"
    Ensure that the lower bound and the upper bound parameters are in the range of
    the list `residues`.

    Parameters
    ----------
    residues : list
        the list of residues indexes
    residue_min:
        the lower bound of residue
    residue_max:
        the upper bound of residue

    Exceptions
    ----------
    IndexError : if `residue_min` or `residue_max` is not in the range
    """

    if residue_min is None:
        residue_min = residues[0]

    if residue_max is None:
        residue_max = residues[-1]

    if residue_min not in residues:
        raise IndexError("residue_min does not belong to the residue range")

    if residue_max not in residues:
        raise IndexError("residue_max does not belong to the residue range")

    if residue_min >= residue_max:
        raise IndexError("Lower bound > upper bound")

    return residue_min, residue_max

def pbstat_cli():
    """
    PBstat command line.
    """

    options = user_inputs()

    try:
        count, residues = pbx.analysis.read_occurence_file(options.f)
        residue_min, residue_max = check_residue_range(residues,
                                                       options.residue_min, options.residue_max)
    except Exception as e:
        sys.exit("ERROR: {0}".format(e))

    print("Index of first residue is: {0}".format(residue_min))

    # Handle output file name...
    output_file_name = options.o + ".PB.{0}"
    if options.residue_min or options.residue_max:
        output_file_name = "{0}.{1}-{2}".format(output_file_name, residue_min, residue_max)
    # ... and figure name
    output_fig_name = output_file_name + "." + options.image_format

    # -------------------------------------------------------------------------------
    # generates map of the distribution of PBs along protein sequence
    # -------------------------------------------------------------------------------
    if options.mapdist:
        file_fig_name = output_fig_name.format("map")
        pbx.analysis.plot_map(file_fig_name, count, residue_min, residue_max)
        print("wrote " + file_fig_name)

    # -------------------------------------------------------------------------------
    # computes Neq and generates neq plot along protein sequence
    # -------------------------------------------------------------------------------
    if options.neq:
        # compute Neq
        neq = pbx.analysis.compute_neq(count)

        # write Neq
        neq_file_name = output_file_name.format("Neq")
        with open(neq_file_name, "w") as outfile:
            pbx.io.write_neq(outfile, neq, residue_min, residue_max)
        print("wrote {0}".format(neq_file_name))

        # draw Neq
        file_fig_name = output_fig_name.format("Neq")
        pbx.analysis.plot_neq(file_fig_name, neq, residue_min, residue_max)
        print("wrote {}".format(file_fig_name))

    # -------------------------------------------------------------------------------
    # generates logo representation of PBs frequency along protein sequence
    # -------------------------------------------------------------------------------
    if options.logo:
        file_fig_name = output_fig_name.format("logo")
        title = options.f.replace(".PB.count", "")
        pbx.analysis.generate_weblogo(file_fig_name, count, residue_min, residue_max, title)
        print("wrote {}".format(file_fig_name))

if __name__ == '__main__':
    pbstat_cli()
<commit_msg>Check if matplotlib is present<commit_after>#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
Statistical analysis and graphical representations of PBs.

Compute Neq, PBs distribution and draw logo representation of PBs.

2013 - P. Poulain, A. G. de Brevern
"""

# ============================================================================
# Modules
# ============================================================================
# Use print as a function for python 3 compatibility
from __future__ import print_function

# Standard modules
import os
import sys
import argparse

# Local module
import pbxplore as pbx

# Weblogolib is an optional requirement
try:
    import weblogolib
except:
    IS_WEBLOGO = False
else:
    IS_WEBLOGO = True

# matplotlib is an optional requirement
try:
    import matplotlib
except:
    IS_MATPLOTLIB = False
else:
    IS_MATPLOTLIB = True

# ============================================================================
# Python2/Python3 compatibility
# ============================================================================

# The range function in python 3 behaves as the range function in python 2
# and returns a generator rather than a list. To produce a list in python 3,
# one should use list(range). Here we change range to behave the same in
# python 2 and in python 3. In both cases, range will return a generator.
try:
    range = xrange
except NameError:
    pass

def user_inputs():
    """
    Handle the user parameters for PBstat.py.

    Returns
    -------
    options : the parsed arguments as parsed by `argparse`.
    """

    parser = argparse.ArgumentParser(
        description="Statistical analysis and graphical representations of PBs.")

    # mandatory arguments
    parser.add_argument("-f", action="store", required=True,
                        help="name of file that contains PBs frequency (count)")
    parser.add_argument("-o", action="store", required=True,
                        help="name for results")

    # optional arguments
    parser.add_argument("--map", action="store_true", default=False, dest="mapdist",
                        help="generate map of the distribution of PBs along protein sequence")
    parser.add_argument("--neq", action="store_true", default=False, dest="neq",
                        help="compute Neq and generate Neq plot along protein sequence")
    parser.add_argument("--logo", action="store_true", default=False, dest="logo",
                        help="generate logo representation of PBs frequency along protein sequence")
    parser.add_argument("--image-format", action='store', type=str,
                        dest='image_format', default='png',
                        choices=['pdf', 'png', 'jpg'],
                        help='File format for all image output.')
    parser.add_argument("--residue-min", action="store", type=int,
                        dest="residue_min", help="defines lower bound of residue frame")
    parser.add_argument("--residue-max", action="store", type=int,
                        dest="residue_max", help="defines upper bound of residue frame")

    # get all parameters
    options = parser.parse_args()

    # check file
    if not os.path.isfile(options.f):
        parser.error("{0}: not a valid file".format(options.f))

    # Check residues min/max
    residues = [options.residue_min, options.residue_max]
    for residue in residues:
        if residue is not None and residue < 0:
            parser.error("residue argument must be >=0")
    if None not in residues and options.residue_min >= options.residue_max:
        parser.error("residue-min must be < residue-max.")

    # Check weblogo
    if options.logo:
        if not IS_WEBLOGO:
            parser.error("Weblogo is not installed; cannot generate the logo image.")

    # Check matplotlib
    if options.mapdist or options.neq:
        if not IS_MATPLOTLIB:
            parser.error("matplotlib is not installed; cannot generate plots.")

    return options

def check_residue_range(residues, residue_min, residue_max):
    """"
    Ensure that the lower bound and the upper bound parameters are in the range of
    the list `residues`.

    Parameters
    ----------
    residues : list
        the list of residues indexes
    residue_min:
        the lower bound of residue
    residue_max:
        the upper bound of residue

    Exceptions
    ----------
    IndexError : if `residue_min` or `residue_max` is not in the range
    """

    if residue_min is None:
        residue_min = residues[0]

    if residue_max is None:
        residue_max = residues[-1]

    if residue_min not in residues:
        raise IndexError("residue_min does not belong to the residue range")

    if residue_max not in residues:
        raise IndexError("residue_max does not belong to the residue range")

    if residue_min >= residue_max:
        raise IndexError("Lower bound > upper bound")

    return residue_min, residue_max

def pbstat_cli():
    """
    PBstat command line.
    """

    options = user_inputs()

    try:
        count, residues = pbx.analysis.read_occurence_file(options.f)
        residue_min, residue_max = check_residue_range(residues,
                                                       options.residue_min, options.residue_max)
    except Exception as e:
        sys.exit("ERROR: {0}".format(e))

    print("Index of first residue is: {0}".format(residue_min))

    # Handle output file name...
    output_file_name = options.o + ".PB.{0}"
    if options.residue_min or options.residue_max:
        output_file_name = "{0}.{1}-{2}".format(output_file_name, residue_min, residue_max)
    # ... and figure name
    output_fig_name = output_file_name + "." + options.image_format

    # -------------------------------------------------------------------------------
    # generates map of the distribution of PBs along protein sequence
    # -------------------------------------------------------------------------------
    if options.mapdist:
        file_fig_name = output_fig_name.format("map")
        pbx.analysis.plot_map(file_fig_name, count, residue_min, residue_max)
        print("wrote " + file_fig_name)

    # -------------------------------------------------------------------------------
    # computes Neq and generates neq plot along protein sequence
    # -------------------------------------------------------------------------------
    if options.neq:
        # compute Neq
        neq = pbx.analysis.compute_neq(count)

        # write Neq
        neq_file_name = output_file_name.format("Neq")
        with open(neq_file_name, "w") as outfile:
            pbx.io.write_neq(outfile, neq, residue_min, residue_max)
        print("wrote {0}".format(neq_file_name))

        # draw Neq
        file_fig_name = output_fig_name.format("Neq")
        pbx.analysis.plot_neq(file_fig_name, neq, residue_min, residue_max)
        print("wrote {}".format(file_fig_name))

    # -------------------------------------------------------------------------------
    # generates logo representation of PBs frequency along protein sequence
    # -------------------------------------------------------------------------------
    if options.logo:
        file_fig_name = output_fig_name.format("logo")
        title = options.f.replace(".PB.count", "")
        pbx.analysis.generate_weblogo(file_fig_name, count, residue_min, residue_max, title)
        print("wrote {}".format(file_fig_name))

if __name__ == '__main__':
    pbstat_cli()
<|endoftext|>


<commit_before>#
#   This file is part of m.css.
#
#   Copyright © 2017, 2018 Vladimír Vondruš <mosra@centrum.cz>
#
#   Permission is hereby granted, free of charge, to any person obtaining a
#   copy of this software and associated documentation files (the "Software"),
#   to deal in the Software without restriction, including without limitation
#   the rights to use, copy, modify, merge, publish, distribute, sublicense,
#   and/or sell copies of the Software, and to permit persons to whom the
#   Software is furnished to do so, subject to the following conditions:
#
#   The above copyright notice and this permission notice shall be included
#   in all copies or substantial portions of the Software.
#
#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
#   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#   DEALINGS IN THE SOFTWARE.
#

import re

from docutils import nodes, utils
from docutils.parsers import rst
from docutils.parsers.rst import directives
from docutils.parsers.rst.roles import set_classes

import matplotlib as mpl
mpl.use('Agg') # otherwise it will attempt to use X11
import matplotlib.pyplot as plt
import numpy as np
import io

import pelican.signals

mpl.rcParams['font.size'] = '11'
mpl.rcParams['axes.titlesize'] = '13'

# Plot background. Replaced with .m-plot .m-background later, equivalent to
# --default-filled-background-color
mpl.rcParams['axes.facecolor'] = '#cafe01'

# All of these should match --color, replaced with .m-plot .m-text
mpl.rcParams['text.color'] = '#cafe02'
mpl.rcParams['axes.labelcolor'] = '#cafe02'
mpl.rcParams['xtick.color'] = '#cafe02'
mpl.rcParams['ytick.color'] = '#cafe02'

# no need to have a border around the plot
mpl.rcParams['axes.spines.left'] = False
mpl.rcParams['axes.spines.right'] = False
mpl.rcParams['axes.spines.top'] = False
mpl.rcParams['axes.spines.bottom'] = False

mpl.rcParams['svg.fonttype'] = 'none' # otherwise it renders text to paths
mpl.rcParams['figure.autolayout'] = True # so it relayouts everything to fit

# Gets increased for every graph on a page to (hopefully) ensure unique SVG IDs
mpl.rcParams['svg.hashsalt'] = 0

# Color codes for bars
style_mapping = {
    'default': '#cafe03',
    'primary': '#cafe04',
    'success': '#cafe05',
    'warning': '#cafe06',
    'danger': '#cafe07',
    'info': '#cafe08',
    'dim': '#cafe09'
}

# Patch to remove preamble and hardcoded sizes
_patch_src = re.compile(r"""<\?xml version="1\.0" encoding="utf-8" standalone="no"\?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1\.1//EN"
  "http://www\.w3\.org/Graphics/SVG/1\.1/DTD/svg11\.dtd">
<!-- Created with matplotlib \(http://matplotlib.org/\) -->
<svg height="\d+(\.\d+)?pt" version="1.1" (?P<viewBox>viewBox="0 0 \d+ \d+(\.\d+)?") width="\d+(\.\d+)?pt" xmlns="http://www\.w3\.org/2000/svg" xmlns:xlink="http://www\.w3\.org/1999/xlink">
""")
_patch_dst = r"""<svg \g<viewBox>>
"""

# Remove needless newlines and trailing space in path data
_path_patch_src = re.compile('(?P<prev>[\\dz]) ?\n(?P<next>[LMz])', re.MULTILINE)
_path_patch_dst = '\\g<prev> \\g<next>'
_path_patch2_src = re.compile(' ?\n"')
_path_patch2_dst = '"'

# Mapping from color codes to CSS classes
_class_mapping = [
    # Graph background
    ('style="fill:#cafe01;"', 'class="m-background"'),

    # Tick <path> definition in <defs>
    ('style="stroke:#cafe02;stroke-width:0.8;"', 'class="m-line"'),
    # <use>, everything is defined in <defs>, no need to repeat
    ('<use style="fill:#cafe02;stroke:#cafe02;stroke-width:0.8;"', '<use'),

    # Label text on left
    ('style="fill:#cafe02;font-family:{font};font-size:11px;font-style:normal;font-weight:normal;"', 'class="m-label"'),
    # Label text on bottom (has extra style params)
    ('style="fill:#cafe02;font-family:{font};font-size:11px;font-style:normal;font-weight:normal;', 'class="m-label" style="'),
    # Secondary label text
    ('style="fill:#cafe0b;font-family:{font};font-size:11px;font-style:normal;font-weight:normal;"', 'class="m-label m-dim"'),
    # Title text
    ('style="fill:#cafe02;font-family:{font};font-size:13px;font-style:normal;font-weight:normal;', 'class="m-title" style="'),

    # Bar colors
    ('style="fill:#cafe03;"', 'class="m-bar m-default"'),
    ('style="fill:#cafe04;"', 'class="m-bar m-primary"'),
    ('style="fill:#cafe05;"', 'class="m-bar m-success"'),
    ('style="fill:#cafe06;"', 'class="m-bar m-warning"'),
    ('style="fill:#cafe07;"', 'class="m-bar m-danger"'),
    ('style="fill:#cafe08;"', 'class="m-bar m-info"'),
    ('style="fill:#cafe09;"', 'class="m-bar m-dim"'),

    # Error bar line
    ('style="fill:none;stroke:#cafe0a;stroke-width:1.5;"', 'class="m-error"'),
    # Error bar <path> definition in <defs>
    ('style="stroke:#cafe0a;"', 'class="m-error"'),
    # <use>, everything is defined in <defs>, no need to repeat
    ('<use style="fill:#cafe0a;stroke:#cafe0a;"', '<use'),
]

# Titles for bars
_bar_titles_src = '<g id="plot{}-value{}">'
_bar_titles_dst = '<g id="plot{}-value{}"><title>{} {}</title>'
_bar_titles_dst_error = '<g id="plot{}-value{}"><title>{} ± {} {}</title>'

class Plot(rst.Directive):
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {'class': directives.class_option,
                   'name': directives.unchanged,
                   'type': directives.unchanged_required,
                   'labels': directives.unchanged_required,
                   'labels_extra': directives.unchanged,
                   'units': directives.unchanged_required,
                   'values': directives.unchanged_required,
                   'errors': directives.unchanged,
                   'colors': directives.unchanged,
                   'bar_height': directives.unchanged}
    has_content = False

    def run(self):
        set_classes(self.options)

        # Type
        assert self.options['type'] == 'barh'

        # Graph title and axis labels. Value labels are one per line.
        title = self.arguments[0]
        units = self.options['units']
        labels = self.options['labels'].split('\n')

        # Optional extra labels
        if 'labels_extra' in self.options:
            labels_extra = self.options['labels_extra'].split('\n')
            assert len(labels_extra) == len(labels)
        else:
            labels_extra = None

        # Values. Should be one for each label.
        values = [float(v) for v in self.options['values'].split()]
        assert len(values) == len(labels)

        # Optional errors
        if 'errors' in self.options:
            errors = [float(e) for e in self.options['errors'].split()]
        else:
            errors = None

        # Colors. Should be either one for all or one for every value
        colors = [style_mapping[c] for c in self.options.get('colors', 'default').split()]
        if len(colors) == 1: colors = colors[0]
        else: assert len(colors) == len(labels)

        # Bar height
        bar_height = float(self.options.get('bar_height', '0.4'))

        # Increase hashsalt for every plot to ensure (hopefully) unique SVG IDs
        mpl.rcParams['svg.hashsalt'] = int(mpl.rcParams['svg.hashsalt']) + 1

        # Setup the graph
        fig, ax = plt.subplots()
        # TODO: let matplotlib calculate the height somehow
        fig.set_size_inches(8, 0.78 + len(values)*bar_height)
        yticks = np.arange(len(labels))
        plot = ax.barh(yticks, values, xerr=errors,
                       align='center', color=colors, ecolor='#cafe0a', capsize=5*bar_height/0.4)
        for i, v in enumerate(plot):
            v.set_gid('plot{}-value{}'.format(mpl.rcParams['svg.hashsalt'], i))
        ax.set_yticks(yticks)
        ax.invert_yaxis() # top-to-bottom
        ax.set_xlabel(units)
        ax.set_title(title)

        # Value labels. If extra label is specified, create two multiline texts
        # with first having the second line empty and second having the first
        # line empty.
        if labels_extra:
            ax.set_yticklabels([y + '\n' for y in labels])
            for i, label in enumerate(ax.get_yticklabels()):
                ax.text(0, i + 0.05, '\n' + labels_extra[i],
                        va='center', ha='right',
                        transform=label.get_transform(), color='#cafe0b')
        else: ax.set_yticklabels(labels)

        # Export to SVG
        fig.patch.set_visible(False) # hide the white background
        imgdata = io.StringIO()
        fig.savefig(imgdata, format='svg')

        # Patch the rendered output: remove preable and hardcoded size
        imgdata = _patch_src.sub(_patch_dst, imgdata.getvalue())
        # Remove needless newlines and trailing whitespace in path data
        imgdata = _path_patch2_src.sub(_path_patch2_dst, _path_patch_src.sub(_path_patch_dst, imgdata))
        # Replace color codes with CSS classes
        for src, dst in _class_mapping: imgdata = imgdata.replace(src, dst)
        # Add titles for bars
        for i in range(len(values)):
            if errors: imgdata = imgdata.replace(
                _bar_titles_src.format(mpl.rcParams['svg.hashsalt'], i),
                _bar_titles_dst_error.format(mpl.rcParams['svg.hashsalt'], i, values[i], errors[i], units))
            else: imgdata = imgdata.replace(
                _bar_titles_src.format(mpl.rcParams['svg.hashsalt'], i),
                _bar_titles_dst.format(mpl.rcParams['svg.hashsalt'], i, values[i], units))

        container = nodes.container(**self.options)
        container['classes'] += ['m-plot']
        node = nodes.raw('', imgdata, format='html')
        container.append(node)
        return [container]

def new_page(content):
    mpl.rcParams['svg.hashsalt'] = 0

def configure(pelicanobj):
    font = pelicanobj.settings.get('M_PLOTS_FONT', 'Source Sans Pro')
    for i in range(len(_class_mapping)):
        src, dst = _class_mapping[i]
        _class_mapping[i] = (src.format(font=font), dst)
    mpl.rcParams['font.family'] = font

def register():
    pelican.signals.initialized.connect(configure)
    pelican.signals.content_object_init.connect(new_page)
    rst.directives.register_directive('plot', Plot)
<commit_msg>m.plots: matplotlib otherwise keeps everything in memory.<commit_after>#
#   This file is part of m.css.
#
#   Copyright © 2017, 2018 Vladimír Vondruš <mosra@centrum.cz>
#
#   Permission is hereby granted, free of charge, to any person obtaining a
#   copy of this software and associated documentation files (the "Software"),
#   to deal in the Software without restriction, including without limitation
#   the rights to use, copy, modify, merge, publish, distribute, sublicense,
#   and/or sell copies of the Software, and to permit persons to whom the
#   Software is furnished to do so, subject to the following conditions:
#
#   The above copyright notice and this permission notice shall be included
#   in all copies or substantial portions of the Software.
#
#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
#   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#   DEALINGS IN THE SOFTWARE.
#

import re

from docutils import nodes, utils
from docutils.parsers import rst
from docutils.parsers.rst import directives
from docutils.parsers.rst.roles import set_classes

import matplotlib as mpl
mpl.use('Agg') # otherwise it will attempt to use X11
import matplotlib.pyplot as plt
import numpy as np
import io

import pelican.signals

mpl.rcParams['font.size'] = '11'
mpl.rcParams['axes.titlesize'] = '13'

# Plot background. Replaced with .m-plot .m-background later, equivalent to
# --default-filled-background-color
mpl.rcParams['axes.facecolor'] = '#cafe01'

# All of these should match --color, replaced with .m-plot .m-text
mpl.rcParams['text.color'] = '#cafe02'
mpl.rcParams['axes.labelcolor'] = '#cafe02'
mpl.rcParams['xtick.color'] = '#cafe02'
mpl.rcParams['ytick.color'] = '#cafe02'

# no need to have a border around the plot
mpl.rcParams['axes.spines.left'] = False
mpl.rcParams['axes.spines.right'] = False
mpl.rcParams['axes.spines.top'] = False
mpl.rcParams['axes.spines.bottom'] = False

mpl.rcParams['svg.fonttype'] = 'none' # otherwise it renders text to paths
mpl.rcParams['figure.autolayout'] = True # so it relayouts everything to fit

# Gets increased for every graph on a page to (hopefully) ensure unique SVG IDs
mpl.rcParams['svg.hashsalt'] = 0

# Color codes for bars
style_mapping = {
    'default': '#cafe03',
    'primary': '#cafe04',
    'success': '#cafe05',
    'warning': '#cafe06',
    'danger': '#cafe07',
    'info': '#cafe08',
    'dim': '#cafe09'
}

# Patch to remove preamble and hardcoded sizes
_patch_src = re.compile(r"""<\?xml version="1\.0" encoding="utf-8" standalone="no"\?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1\.1//EN"
  "http://www\.w3\.org/Graphics/SVG/1\.1/DTD/svg11\.dtd">
<!-- Created with matplotlib \(http://matplotlib.org/\) -->
<svg height="\d+(\.\d+)?pt" version="1.1" (?P<viewBox>viewBox="0 0 \d+ \d+(\.\d+)?") width="\d+(\.\d+)?pt" xmlns="http://www\.w3\.org/2000/svg" xmlns:xlink="http://www\.w3\.org/1999/xlink">
""")
_patch_dst = r"""<svg \g<viewBox>>
"""

# Remove needless newlines and trailing space in path data
_path_patch_src = re.compile('(?P<prev>[\\dz]) ?\n(?P<next>[LMz])', re.MULTILINE)
_path_patch_dst = '\\g<prev> \\g<next>'
_path_patch2_src = re.compile(' ?\n"')
_path_patch2_dst = '"'

# Mapping from color codes to CSS classes
_class_mapping = [
    # Graph background
    ('style="fill:#cafe01;"', 'class="m-background"'),

    # Tick <path> definition in <defs>
    ('style="stroke:#cafe02;stroke-width:0.8;"', 'class="m-line"'),
    # <use>, everything is defined in <defs>, no need to repeat
    ('<use style="fill:#cafe02;stroke:#cafe02;stroke-width:0.8;"', '<use'),

    # Label text on left
    ('style="fill:#cafe02;font-family:{font};font-size:11px;font-style:normal;font-weight:normal;"', 'class="m-label"'),
    # Label text on bottom (has extra style params)
    ('style="fill:#cafe02;font-family:{font};font-size:11px;font-style:normal;font-weight:normal;', 'class="m-label" style="'),
    # Secondary label text
    ('style="fill:#cafe0b;font-family:{font};font-size:11px;font-style:normal;font-weight:normal;"', 'class="m-label m-dim"'),
    # Title text
    ('style="fill:#cafe02;font-family:{font};font-size:13px;font-style:normal;font-weight:normal;', 'class="m-title" style="'),

    # Bar colors
    ('style="fill:#cafe03;"', 'class="m-bar m-default"'),
    ('style="fill:#cafe04;"', 'class="m-bar m-primary"'),
    ('style="fill:#cafe05;"', 'class="m-bar m-success"'),
    ('style="fill:#cafe06;"', 'class="m-bar m-warning"'),
    ('style="fill:#cafe07;"', 'class="m-bar m-danger"'),
    ('style="fill:#cafe08;"', 'class="m-bar m-info"'),
    ('style="fill:#cafe09;"', 'class="m-bar m-dim"'),

    # Error bar line
    ('style="fill:none;stroke:#cafe0a;stroke-width:1.5;"', 'class="m-error"'),
    # Error bar <path> definition in <defs>
    ('style="stroke:#cafe0a;"', 'class="m-error"'),
    # <use>, everything is defined in <defs>, no need to repeat
    ('<use style="fill:#cafe0a;stroke:#cafe0a;"', '<use'),
]

# Titles for bars
_bar_titles_src = '<g id="plot{}-value{}">'
_bar_titles_dst = '<g id="plot{}-value{}"><title>{} {}</title>'
_bar_titles_dst_error = '<g id="plot{}-value{}"><title>{} ± {} {}</title>'

class Plot(rst.Directive):
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {'class': directives.class_option,
                   'name': directives.unchanged,
                   'type': directives.unchanged_required,
                   'labels': directives.unchanged_required,
                   'labels_extra': directives.unchanged,
                   'units': directives.unchanged_required,
                   'values': directives.unchanged_required,
                   'errors': directives.unchanged,
                   'colors': directives.unchanged,
                   'bar_height': directives.unchanged}
    has_content = False

    def run(self):
        set_classes(self.options)

        # Type
        assert self.options['type'] == 'barh'

        # Graph title and axis labels. Value labels are one per line.
        title = self.arguments[0]
        units = self.options['units']
        labels = self.options['labels'].split('\n')

        # Optional extra labels
        if 'labels_extra' in self.options:
            labels_extra = self.options['labels_extra'].split('\n')
            assert len(labels_extra) == len(labels)
        else:
            labels_extra = None

        # Values. Should be one for each label.
        values = [float(v) for v in self.options['values'].split()]
        assert len(values) == len(labels)

        # Optional errors
        if 'errors' in self.options:
            errors = [float(e) for e in self.options['errors'].split()]
        else:
            errors = None

        # Colors. Should be either one for all or one for every value
        colors = [style_mapping[c] for c in self.options.get('colors', 'default').split()]
        if len(colors) == 1: colors = colors[0]
        else: assert len(colors) == len(labels)

        # Bar height
        bar_height = float(self.options.get('bar_height', '0.4'))

        # Increase hashsalt for every plot to ensure (hopefully) unique SVG IDs
        mpl.rcParams['svg.hashsalt'] = int(mpl.rcParams['svg.hashsalt']) + 1

        # Setup the graph
        fig, ax = plt.subplots()
        # TODO: let matplotlib calculate the height somehow
        fig.set_size_inches(8, 0.78 + len(values)*bar_height)
        yticks = np.arange(len(labels))
        plot = ax.barh(yticks, values, xerr=errors,
                       align='center', color=colors, ecolor='#cafe0a', capsize=5*bar_height/0.4)
        for i, v in enumerate(plot):
            v.set_gid('plot{}-value{}'.format(mpl.rcParams['svg.hashsalt'], i))
        ax.set_yticks(yticks)
        ax.invert_yaxis() # top-to-bottom
        ax.set_xlabel(units)
        ax.set_title(title)

        # Value labels. If extra label is specified, create two multiline texts
        # with first having the second line empty and second having the first
        # line empty.
        if labels_extra:
            ax.set_yticklabels([y + '\n' for y in labels])
            for i, label in enumerate(ax.get_yticklabels()):
                ax.text(0, i + 0.05, '\n' + labels_extra[i],
                        va='center', ha='right',
                        transform=label.get_transform(), color='#cafe0b')
        else: ax.set_yticklabels(labels)

        # Export to SVG
        fig.patch.set_visible(False) # hide the white background
        imgdata = io.StringIO()
        fig.savefig(imgdata, format='svg')
        plt.close() # otherwise it consumes a lot of memory in autoreload mode

        # Patch the rendered output: remove preable and hardcoded size
        imgdata = _patch_src.sub(_patch_dst, imgdata.getvalue())
        # Remove needless newlines and trailing whitespace in path data
        imgdata = _path_patch2_src.sub(_path_patch2_dst, _path_patch_src.sub(_path_patch_dst, imgdata))
        # Replace color codes with CSS classes
        for src, dst in _class_mapping: imgdata = imgdata.replace(src, dst)
        # Add titles for bars
        for i in range(len(values)):
            if errors: imgdata = imgdata.replace(
                _bar_titles_src.format(mpl.rcParams['svg.hashsalt'], i),
                _bar_titles_dst_error.format(mpl.rcParams['svg.hashsalt'], i, values[i], errors[i], units))
            else: imgdata = imgdata.replace(
                _bar_titles_src.format(mpl.rcParams['svg.hashsalt'], i),
                _bar_titles_dst.format(mpl.rcParams['svg.hashsalt'], i, values[i], units))

        container = nodes.container(**self.options)
        container['classes'] += ['m-plot']
        node = nodes.raw('', imgdata, format='html')
        container.append(node)
        return [container]

def new_page(content):
    mpl.rcParams['svg.hashsalt'] = 0

def configure(pelicanobj):
    font = pelicanobj.settings.get('M_PLOTS_FONT', 'Source Sans Pro')
    for i in range(len(_class_mapping)):
        src, dst = _class_mapping[i]
        _class_mapping[i] = (src.format(font=font), dst)
    mpl.rcParams['font.family'] = font

def register():
    pelican.signals.initialized.connect(configure)
    pelican.signals.content_object_init.connect(new_page)
    rst.directives.register_directive('plot', Plot)
<|endoftext|>


<commit_before>from unittest import TestCase
from mock import Mock, MagicMock

from cloudshell.cp.azure.domain.services.network_service import NetworkService
from cloudshell.cp.azure.domain.services.tags import TagService
from cloudshell.cp.azure.domain.services.virtual_machine_service import VirtualMachineService
from cloudshell.cp.azure.domain.vm_management.operations.delete_operation import DeleteAzureVMOperation
from tests.helpers.test_helper import TestHelper

class TestDeleteOperation(TestCase):
    def setUp(self):
        self.logger = Mock()
        self.vm_service = VirtualMachineService()
        self.network_service = NetworkService()
        self.tags_service = TagService()
        self.delete_operation = DeleteAzureVMOperation(logger=self.logger,
                                                       vm_service=self.vm_service,
                                                       network_service=self.network_service,
                                                       tags_service=self.tags_service)

    def test_delete_operation(self):
        """
        :return:
        """

        # Arrange
        self.vm_service.delete_vm = Mock()
        self.network_service.delete_nic = Mock()
        self.network_service.delete_ip = Mock()

        # Act
        self.delete_operation.delete(compute_client=Mock(),
                                     network_client=Mock(),
                                     group_name="AzureTestGroup",
                                     vm_name="AzureTestVM"
                                     )

        # Verify
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.vm_service.delete_vm))
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.network_service.delete_nic))
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.network_service.delete_ip))

    def test_delete_operation_on_error(self):
        # Arrange
        self.vm_service.delete_vm = Mock(side_effect=Exception("Boom!"))

        # Act
        self.assertRaises(Exception,
                          self.delete_operation.delete,
                          Mock(),
                          Mock(),
                          "AzureTestGroup",
                          "AzureTestVM")

        # Verify
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.logger.info))

    def test_delete_resource_group_operation_on_error(self):
        # Arrange
        self.vm_service.delete_resource_group = Mock(side_effect=Exception("Boom!"))

        # Act
        self.assertRaises(Exception,
                          self.delete_operation.delete_resource_group,
                          Mock(),
                          "group_name_test")

    def test_delete_resource_group(self):
        # Arrange
        resource_management_client = Mock()
        group_name = "test_group_name"

        # Act
        self.vm_service.delete_resource_group(resource_management_client, group_name)

        # Verify
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(resource_management_client.resource_groups.delete))
        resource_management_client.resource_groups.delete.assert_called_with(group_name)

    def test_delete_vm(self):
        # Arrange
        compute_management_client = MagicMock()
        group_name = "test_group_name"
        vm_name = "test_group_name"

        # Act
        res = self.vm_service.delete_vm(compute_management_client, group_name, vm_name)

        # Verify
        compute_management_client.virtual_machines.delete.assert_called_with(resource_group_name=group_name,
                                                                             vm_name=vm_name)
<commit_msg>Tests added to a new code.<commit_after>from unittest import TestCase
from mock import Mock, MagicMock
from msrestazure.azure_exceptions import CloudError
from requests import Response

from cloudshell.cp.azure.domain.services.network_service import NetworkService
from cloudshell.cp.azure.domain.services.tags import TagService
from cloudshell.cp.azure.domain.services.virtual_machine_service import VirtualMachineService
from cloudshell.cp.azure.domain.vm_management.operations.delete_operation import DeleteAzureVMOperation
from tests.helpers.test_helper import TestHelper

class TestDeleteOperation(TestCase):
    def setUp(self):
        self.logger = Mock()
        self.vm_service = VirtualMachineService()
        self.network_service = NetworkService()
        self.tags_service = TagService()
        self.delete_operation = DeleteAzureVMOperation(logger=self.logger,
                                                       vm_service=self.vm_service,
                                                       network_service=self.network_service,
                                                       tags_service=self.tags_service)

    def test_delete_operation(self):
        """
        :return:
        """

        # Arrange
        self.vm_service.delete_vm = Mock()
        self.network_service.delete_nic = Mock()
        self.network_service.delete_ip = Mock()

        # Act
        self.delete_operation.delete(compute_client=Mock(),
                                     network_client=Mock(),
                                     group_name="AzureTestGroup",
                                     vm_name="AzureTestVM"
                                     )

        # Verify
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.vm_service.delete_vm))
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.network_service.delete_nic))
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.network_service.delete_ip))

    def test_delete_operation_on_error(self):
        # Arrange
        self.vm_service.delete_vm = Mock(side_effect=Exception("Boom!"))

        # Act
        self.assertRaises(Exception,
                          self.delete_operation.delete,
                          Mock(),
                          Mock(),
                          "AzureTestGroup",
                          "AzureTestVM")

        # Verify
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.logger.info))

    def test_delete_operation_on_cloud_error_not_found_no_exception(self):
        # Arrange
        response = Response()
        response.reason = "Not Found"
        error = CloudError(response)
        self.vm_service.delete_vm = Mock(side_effect=error)

        # Act
        self.delete_operation.delete(
            Mock(),
            Mock(),
            "group_name",
            "vm_name"
        )

        # Verify
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(self.logger.info))

    def test_delete_operation_on_cloud_any_error_throws_exception(self):
        # Arrange
        response = Response()
        response.reason = "Bla bla error"
        error = CloudError(response)
        self.vm_service.delete_vm = Mock(side_effect=error)

        # Act
        self.assertRaises(Exception,
                          self.delete_operation.delete,
                          Mock(),
                          Mock(),
                          "AzureTestGroup",
                          "AzureTestVM")

    def test_delete_resource_group_operation_on_error(self):
        # Arrange
        self.vm_service.delete_resource_group = Mock(side_effect=Exception("Boom!"))

        # Act
        self.assertRaises(Exception,
                          self.delete_operation.delete_resource_group,
                          Mock(),
                          "group_name_test")

    def test_delete_resource_group(self):
        # Arrange
        resource_management_client = Mock()
        group_name = "test_group_name"

        # Act
        self.vm_service.delete_resource_group(resource_management_client, group_name)

        # Verify
        self.assertTrue(TestHelper.CheckMethodCalledXTimes(resource_management_client.resource_groups.delete))
        resource_management_client.resource_groups.delete.assert_called_with(group_name)

    def test_delete_vm(self):
        # Arrange
        compute_management_client = MagicMock()
        group_name = "test_group_name"
        vm_name = "test_group_name"

        # Act
        res = self.vm_service.delete_vm(compute_management_client, group_name, vm_name)

        # Verify
        compute_management_client.virtual_machines.delete.assert_called_with(resource_group_name=group_name,
                                                                             vm_name=vm_name)
<|endoftext|>