<commit_before>import pickle
from uuid import uuid4

import mock

from corehq.apps.smsforms.models import XFormsSessionSynchronization, RunningSessionInfo, SMSChannel, SQLXFormsSession

@mock.patch.object(SQLXFormsSession, 'by_session_id',
                   lambda session_id: FakeSession.by_session_id(session_id))
def test_session_synchronization():
    phone_number_a = _clean_up_number('15555555555')
    phone_number_b = _clean_up_number('15555555554')
    session_a_1 = FakeSession(session_id=str(uuid4()), phone_number=phone_number_a, connection_id='Alpha')
    session_a_2 = FakeSession(session_id=str(uuid4()), phone_number=phone_number_a, connection_id='Beta')
    session_b_1 = FakeSession(session_id=str(uuid4()), phone_number=phone_number_b, connection_id='Kappa')

    # Nothing set yet, so it can be claimed
    assert XFormsSessionSynchronization.channel_is_available_for_session(session_a_1)
    # And so can the other one
    assert XFormsSessionSynchronization.channel_is_available_for_session(session_a_2)
    # Claim succeeds
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_1)
    # Claim for same channel fails
    assert not XFormsSessionSynchronization.channel_is_available_for_session(session_a_2)
    assert not XFormsSessionSynchronization.claim_channel_for_session(session_a_2)
    # But same session can re-claim it
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_1)
    # And another session can claim another channel
    assert XFormsSessionSynchronization.claim_channel_for_session(session_b_1)
    # And if the first session releases the channel
    XFormsSessionSynchronization.release_channel_for_session(session_a_1)
    # Then the contact is still set
    assert XFormsSessionSynchronization.get_running_session_info_for_channel(
        SMSChannel(BACKEND_ID, phone_number_a)
    ).contact_id == 'Alpha'
    # But the other session (that couldn't before) can claim it now
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_2)
    # Trying to clear the channel claim will fail because the session is still open
    assert not XFormsSessionSynchronization.clear_stale_channel_claim(SMSChannel(BACKEND_ID, phone_number_a))
    # But if we close the session first
    session_a_2.close()
    # The session is now "stale" so we can clear that stale channel claim
    assert XFormsSessionSynchronization.clear_stale_channel_claim(SMSChannel(BACKEND_ID, phone_number_a))
    # If we try to clear it again it'll be a no-op and return false, since it's already cleared
    assert not XFormsSessionSynchronization.clear_stale_channel_claim(SMSChannel(BACKEND_ID, phone_number_a))

class FakeSession:
    expire_after = 60
    _global_objects = {}

    def __init__(self, session_id, phone_number, connection_id, session_is_open=True):
        self.session_id = session_id
        self.phone_number = phone_number
        self.connection_id = connection_id
        self.session_is_open = session_is_open
        self._global_objects[session_id] = self

    def get_channel(self):
        return SMSChannel(BACKEND_ID, self.phone_number)

    def close(self):
        self.session_is_open = False

    @classmethod
    def by_session_id(cls, session_id):
        return cls._global_objects[session_id]

def _clean_up_number(phone_number):
    XFormsSessionSynchronization._set_running_session_info_for_channel(
        SMSChannel(BACKEND_ID, phone_number), RunningSessionInfo(None, None),
        expiry=10
    )
    return phone_number

BACKEND_ID = '2b00042f7481c7b056c4b410d28f33cf'

def test_pickle_roundtrip():
    assert pickle.loads(pickle.dumps(SMSChannel('abc', '123'))) == SMSChannel('abc', '123')
    assert pickle.loads(pickle.dumps(RunningSessionInfo('xxx', 'yyy'))) == RunningSessionInfo('xxx', 'yyy')
<commit_msg>Respond to stickler<commit_after>import pickle
from uuid import uuid4

import mock

from corehq.apps.smsforms.models import XFormsSessionSynchronization, RunningSessionInfo, SMSChannel, \
    SQLXFormsSession

@mock.patch.object(SQLXFormsSession, 'by_session_id',
                   lambda session_id: FakeSession.by_session_id(session_id))
def test_session_synchronization():
    phone_number_a = _clean_up_number('15555555555')
    phone_number_b = _clean_up_number('15555555554')
    session_a_1 = FakeSession(session_id=str(uuid4()), phone_number=phone_number_a, connection_id='Alpha')
    session_a_2 = FakeSession(session_id=str(uuid4()), phone_number=phone_number_a, connection_id='Beta')
    session_b_1 = FakeSession(session_id=str(uuid4()), phone_number=phone_number_b, connection_id='Kappa')

    # Nothing set yet, so it can be claimed
    assert XFormsSessionSynchronization.channel_is_available_for_session(session_a_1)
    # And so can the other one
    assert XFormsSessionSynchronization.channel_is_available_for_session(session_a_2)
    # Claim succeeds
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_1)
    # Claim for same channel fails
    assert not XFormsSessionSynchronization.channel_is_available_for_session(session_a_2)
    assert not XFormsSessionSynchronization.claim_channel_for_session(session_a_2)
    # But same session can re-claim it
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_1)
    # And another session can claim another channel
    assert XFormsSessionSynchronization.claim_channel_for_session(session_b_1)
    # And if the first session releases the channel
    XFormsSessionSynchronization.release_channel_for_session(session_a_1)
    # Then the contact is still set
    assert XFormsSessionSynchronization.get_running_session_info_for_channel(
        SMSChannel(BACKEND_ID, phone_number_a)
    ).contact_id == 'Alpha'
    # But the other session (that couldn't before) can claim it now
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_2)
    # Trying to clear the channel claim will fail because the session is still open
    assert not XFormsSessionSynchronization.clear_stale_channel_claim(SMSChannel(BACKEND_ID, phone_number_a))
    # But if we close the session first
    session_a_2.close()
    # The session is now "stale" so we can clear that stale channel claim
    assert XFormsSessionSynchronization.clear_stale_channel_claim(SMSChannel(BACKEND_ID, phone_number_a))
    # If we try to clear it again it'll be a no-op and return false, since it's already cleared
    assert not XFormsSessionSynchronization.clear_stale_channel_claim(SMSChannel(BACKEND_ID, phone_number_a))

class FakeSession:
    expire_after = 60
    _global_objects = {}

    def __init__(self, session_id, phone_number, connection_id, session_is_open=True):
        self.session_id = session_id
        self.phone_number = phone_number
        self.connection_id = connection_id
        self.session_is_open = session_is_open
        self._global_objects[session_id] = self

    def get_channel(self):
        return SMSChannel(BACKEND_ID, self.phone_number)

    def close(self):
        self.session_is_open = False

    @classmethod
    def by_session_id(cls, session_id):
        return cls._global_objects[session_id]

def _clean_up_number(phone_number):
    XFormsSessionSynchronization._set_running_session_info_for_channel(
        SMSChannel(BACKEND_ID, phone_number), RunningSessionInfo(None, None),
        expiry=10
    )
    return phone_number

BACKEND_ID = '2b00042f7481c7b056c4b410d28f33cf'

def test_pickle_roundtrip():
    assert pickle.loads(pickle.dumps(SMSChannel('abc', '123'))) == SMSChannel('abc', '123')
    assert pickle.loads(pickle.dumps(RunningSessionInfo('xxx', 'yyy'))) == RunningSessionInfo('xxx', 'yyy')
<|endoftext|>


<commit_before>"""
Interface between loop object and ebtel++ simulation
"""

import os
import logging
import copy
import warnings
import toolz
import pickle

import numpy as np
import h5py
import astropy.units as u
try:
    import distributed
except ImportError:
    warnings.warn('Dask library required for NEI calculation')

from synthesizAR.util import InputHandler, OutputHandler
from synthesizAR.atomic import Element

class EbtelInterface(object):
    """
    Interface to the EBTEL model

    Interface between synthesizAR and the Enthalpy-Based Thermal Evolution of Loops code for
    computing time-dependent solutions of spatially-averaged loops.

    Parameters
    ----------
    base_config : `dict`
        Config dictionary with default parameters for all loops.
    heating_model : object
        Heating model class for configuring event times and rates
    parent_config_dir : `str`
        Path to configuration file directory
    parent_results_dir : `str`
        Path to results file directory
    """

    def __init__(self, base_config, heating_model, parent_config_dir, parent_results_dir):
        """
        Create EBTEL interface
        """
        self.logger = logging.getLogger(name=type(self).__name__)
        self.name = 'EBTEL'
        self.base_config = base_config
        self.heating_model = heating_model
        self.heating_model.base_config = base_config
        self.parent_config_dir = parent_config_dir
        self.parent_results_dir = parent_results_dir

    def configure_input(self, loop):
        """
        Configure EBTEL input for a given loop object.
        """
        oh = OutputHandler(os.path.join(self.parent_config_dir, loop.name+'.xml'),
                           copy.deepcopy(self.base_config))
        oh.output_dict['output_filename'] = os.path.join(self.parent_results_dir, loop.name)
        oh.output_dict['loop_length'] = loop.full_length.value/2.0
        event_properties = self.heating_model.calculate_event_properties(loop)
        events = []
        for i in range(event_properties['magnitude'].shape):
            events.append({'event': {
                'magnitude': event_properties['magnitude'][i],
                'rise_start': event_properties['rise_start'][i],
                'rise_end': event_properties['rise_end'][i],
                'decay_start': event_properties['decay_start'][i],
                'decay_end': event_properties['decay_end'][i]}})
        oh.output_dict['heating']['events'] = events
        oh.print_to_xml()
        oh.output_dict['config_filename'] = oh.output_filename
        loop.hydro_configuration = oh.output_dict

    def load_results(self, loop):
        """
        Load EBTEL output for a given loop object.

        Parameters
        ----------
        loop : `synthesizAR.Loop` object
        """
        # load text
        N_s = loop.field_aligned_coordinate.shape[0]
        _tmp = np.loadtxt(loop.hydro_configuration['output_filename'])

        # reshape into a 1D loop structure with units
        time = _tmp[:, 0]*u.s
        electron_temperature = np.outer(_tmp[:, 1], np.ones(N_s))*u.K
        ion_temperature = np.outer(_tmp[:, 2], np.ones(N_s))*u.K
        density = np.outer(_tmp[:, 3], np.ones(N_s))*(u.cm**(-3))
        velocity = np.outer(_tmp[:, -2], np.ones(N_s))*u.cm/u.s
        # flip sign of velocity where the radial distance from center is maximum
        # FIXME: this is probably not the best way to do this...
        r = np.sqrt(np.sum(loop.coordinates.value**2, axis=1))
        i_mirror = np.where(np.diff(np.sign(np.gradient(r))))[0]
        if i_mirror.shape[0] > 0:
            i_mirror = i_mirror[0] + 1
        else:
            # If the first method fails, just set it at the midpoint
            i_mirror = int(N_s / 2) if N_s % 2 == 0 else int((N_s - 1) / 2)
        velocity[:, i_mirror:] = -velocity[:, i_mirror:]

        return time, electron_temperature, ion_temperature, density, velocity

    @staticmethod
    def calculate_ionization_fraction(field, emission_model, **kwargs):
        """
        Solve the time-dependent ionization balance equation for all loops and all elements

        This method computes the time dependent ion population fractions for each element in
        the emission model and each loop in the active region and compiles the results to a single
        HDF5 file. To do this efficiently, it uses the dask.distributed library to take advantage of
        multiple processes/cores/machines and compute the population fractions in parallel. It returns
        an asynchronous `~distributed.client.Future` object which holds the state of the submitted
        tasks.

        Parameters
        ----------
        field : `~synthesizAR.Field`
        emission_model : `~synthesizAR.atomic.EmissionModel`

        Other Parameters
        ---------------------
        temperature : `~astropy.units.Quantity`

        Returns
        --------
        future : `~distributed.client.Future`
        """
        client = distributed.get_client()
        tmpdir = os.path.join(os.path.dirname(emission_model.ionization_fraction_savefile),
                              'tmp_nei')
        if not os.path.exists(tmpdir):
            os.makedirs(tmpdir)
        unique_elements = list(set([ion.element_name for ion in emission_model]))
        temperature = kwargs.get('temperature', emission_model.temperature)

        el_futures = []
        for el_name in unique_elements:
            el = Element(el_name, temperature)
            rate_matrix = el._rate_matrix()
            ioneq = el.equilibrium_ionization(rate_matrix)
            partial_nei = toolz.curry(EbtelInterface.compute_and_save_nei)(
                el, rate_matrix=rate_matrix, initial_condition=ioneq, save_dir=tmpdir)
            loop_futures = client.map(partial_nei, field.loops)
            distributed.wait(loop_futures)
            el_futures += loop_futures

        store_future = client.submit(EbtelInterface.slice_and_store, el_futures,
                                     emission_model.ionization_fraction_savefile)
        future = client.submit(EbtelInterface._cleanup, store_future)

        return future

    @staticmethod
    def compute_and_save_nei(element, loop, rate_matrix, initial_condition, save_dir):
        """
        Compute and save NEI populations for a given element and loop
        """
        y_nei = element.non_equilibrium_ionization(loop.time, loop.electron_temperature[:, 0],
                                                   loop.density[:, 0], rate_matrix,
                                                   initial_condition)
        save_path = os.path.join(save_dir, f'{element.element_name}_{loop.name}.pkl')
        with open(save_path, 'wb') as f:
            pickle.dump((y_nei.value, loop.field_aligned_coordinate.shape[0],
                         element.element_name, loop.name), f)

        return save_path

    @staticmethod
    def slice_and_store(filenames, savefile):
        """
        Collect and store all NEI populations in a single HDF5 file
        """
        with h5py.File(savefile, 'a') as hf:
            for fn in filenames:
                with open(fn, 'rb') as f:
                    y_nei, n_s, element_name, loop_name = pickle.load(f)
                grp = hf.create_group(loop_name) if loop_name not in hf else hf[loop_name]
                data = np.repeat(y_nei[:, np.newaxis, :], n_s, axis=1)
                if element_name not in grp:
                    dset = grp.create_dataset(element_name, data=data)
                else:
                    dset = grp[element_name]
                    dset[:, :, :] = data
                dset.attrs['units'] = ''
                dset.attrs['description'] = 'non-equilibrium ionization fractions'

        return filenames

    @staticmethod
    def _cleanup(filenames):
        for f in filenames:
            os.remove(f)
        os.rmdir(os.path.dirname(f))
<commit_msg>bug fix<commit_after>"""
Interface between loop object and ebtel++ simulation
"""

import os
import logging
import copy
import warnings
import toolz
import pickle

import numpy as np
import h5py
import astropy.units as u
try:
    import distributed
except ImportError:
    warnings.warn('Dask library required for NEI calculation')

from synthesizAR.util import InputHandler, OutputHandler
from synthesizAR.atomic import Element

class EbtelInterface(object):
    """
    Interface to the EBTEL model

    Interface between synthesizAR and the Enthalpy-Based Thermal Evolution of Loops code for
    computing time-dependent solutions of spatially-averaged loops.

    Parameters
    ----------
    base_config : `dict`
        Config dictionary with default parameters for all loops.
    heating_model : object
        Heating model class for configuring event times and rates
    parent_config_dir : `str`
        Path to configuration file directory
    parent_results_dir : `str`
        Path to results file directory
    """

    def __init__(self, base_config, heating_model, parent_config_dir, parent_results_dir):
        """
        Create EBTEL interface
        """
        self.logger = logging.getLogger(name=type(self).__name__)
        self.name = 'EBTEL'
        self.base_config = base_config
        self.heating_model = heating_model
        self.heating_model.base_config = base_config
        self.parent_config_dir = parent_config_dir
        self.parent_results_dir = parent_results_dir

    def configure_input(self, loop):
        """
        Configure EBTEL input for a given loop object.
        """
        oh = OutputHandler(os.path.join(self.parent_config_dir, loop.name+'.xml'),
                           copy.deepcopy(self.base_config))
        oh.output_dict['output_filename'] = os.path.join(self.parent_results_dir, loop.name)
        oh.output_dict['loop_length'] = loop.full_length.value/2.0
        event_properties = self.heating_model.calculate_event_properties(loop)
        events = []
        for i in range(event_properties['magnitude'].shape[0]):
            events.append({'event': {
                'magnitude': event_properties['magnitude'][i],
                'rise_start': event_properties['rise_start'][i],
                'rise_end': event_properties['rise_end'][i],
                'decay_start': event_properties['decay_start'][i],
                'decay_end': event_properties['decay_end'][i]}})
        oh.output_dict['heating']['events'] = events
        oh.print_to_xml()
        oh.output_dict['config_filename'] = oh.output_filename
        loop.hydro_configuration = oh.output_dict

    def load_results(self, loop):
        """
        Load EBTEL output for a given loop object.

        Parameters
        ----------
        loop : `synthesizAR.Loop` object
        """
        # load text
        N_s = loop.field_aligned_coordinate.shape[0]
        _tmp = np.loadtxt(loop.hydro_configuration['output_filename'])

        # reshape into a 1D loop structure with units
        time = _tmp[:, 0]*u.s
        electron_temperature = np.outer(_tmp[:, 1], np.ones(N_s))*u.K
        ion_temperature = np.outer(_tmp[:, 2], np.ones(N_s))*u.K
        density = np.outer(_tmp[:, 3], np.ones(N_s))*(u.cm**(-3))
        velocity = np.outer(_tmp[:, -2], np.ones(N_s))*u.cm/u.s
        # flip sign of velocity where the radial distance from center is maximum
        # FIXME: this is probably not the best way to do this...
        r = np.sqrt(np.sum(loop.coordinates.value**2, axis=1))
        i_mirror = np.where(np.diff(np.sign(np.gradient(r))))[0]
        if i_mirror.shape[0] > 0:
            i_mirror = i_mirror[0] + 1
        else:
            # If the first method fails, just set it at the midpoint
            i_mirror = int(N_s / 2) if N_s % 2 == 0 else int((N_s - 1) / 2)
        velocity[:, i_mirror:] = -velocity[:, i_mirror:]

        return time, electron_temperature, ion_temperature, density, velocity

    @staticmethod
    def calculate_ionization_fraction(field, emission_model, **kwargs):
        """
        Solve the time-dependent ionization balance equation for all loops and all elements

        This method computes the time dependent ion population fractions for each element in
        the emission model and each loop in the active region and compiles the results to a single
        HDF5 file. To do this efficiently, it uses the dask.distributed library to take advantage of
        multiple processes/cores/machines and compute the population fractions in parallel. It returns
        an asynchronous `~distributed.client.Future` object which holds the state of the submitted
        tasks.

        Parameters
        ----------
        field : `~synthesizAR.Field`
        emission_model : `~synthesizAR.atomic.EmissionModel`

        Other Parameters
        ---------------------
        temperature : `~astropy.units.Quantity`

        Returns
        --------
        future : `~distributed.client.Future`
        """
        client = distributed.get_client()
        tmpdir = os.path.join(os.path.dirname(emission_model.ionization_fraction_savefile),
                              'tmp_nei')
        if not os.path.exists(tmpdir):
            os.makedirs(tmpdir)
        unique_elements = list(set([ion.element_name for ion in emission_model]))
        temperature = kwargs.get('temperature', emission_model.temperature)

        el_futures = []
        for el_name in unique_elements:
            el = Element(el_name, temperature)
            rate_matrix = el._rate_matrix()
            ioneq = el.equilibrium_ionization(rate_matrix)
            partial_nei = toolz.curry(EbtelInterface.compute_and_save_nei)(
                el, rate_matrix=rate_matrix, initial_condition=ioneq, save_dir=tmpdir)
            loop_futures = client.map(partial_nei, field.loops)
            distributed.wait(loop_futures)
            el_futures += loop_futures

        store_future = client.submit(EbtelInterface.slice_and_store, el_futures,
                                     emission_model.ionization_fraction_savefile)
        future = client.submit(EbtelInterface._cleanup, store_future)

        return future

    @staticmethod
    def compute_and_save_nei(element, loop, rate_matrix, initial_condition, save_dir):
        """
        Compute and save NEI populations for a given element and loop
        """
        y_nei = element.non_equilibrium_ionization(loop.time, loop.electron_temperature[:, 0],
                                                   loop.density[:, 0], rate_matrix,
                                                   initial_condition)
        save_path = os.path.join(save_dir, f'{element.element_name}_{loop.name}.pkl')
        with open(save_path, 'wb') as f:
            pickle.dump((y_nei.value, loop.field_aligned_coordinate.shape[0],
                         element.element_name, loop.name), f)

        return save_path

    @staticmethod
    def slice_and_store(filenames, savefile):
        """
        Collect and store all NEI populations in a single HDF5 file
        """
        with h5py.File(savefile, 'a') as hf:
            for fn in filenames:
                with open(fn, 'rb') as f:
                    y_nei, n_s, element_name, loop_name = pickle.load(f)
                grp = hf.create_group(loop_name) if loop_name not in hf else hf[loop_name]
                data = np.repeat(y_nei[:, np.newaxis, :], n_s, axis=1)
                if element_name not in grp:
                    dset = grp.create_dataset(element_name, data=data)
                else:
                    dset = grp[element_name]
                    dset[:, :, :] = data
                dset.attrs['units'] = ''
                dset.attrs['description'] = 'non-equilibrium ionization fractions'

        return filenames

    @staticmethod
    def _cleanup(filenames):
        for f in filenames:
            os.remove(f)
        os.rmdir(os.path.dirname(f))
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import corehq.form_processor.models
from corehq.sql_db.operations import HqRunSQL

class Migration(migrations.Migration):

    dependencies = [
        ('form_processor', '0053_add_deletion_fields'),
    ]

    operations = [
        migrations.CreateModel(
            name='LedgerTransaction',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('form_id', models.CharField(max_length=255)),
                ('server_date', models.DateTimeField()),
                ('report_date', models.DateTimeField()),
                ('type', models.PositiveSmallIntegerField(choices=[(1, b'balance'), (2, b'transfer')])),
                ('case_id', models.CharField(default=None, max_length=255)),
                ('entry_id', models.CharField(default=None, max_length=100)),
                ('section_id', models.CharField(default=None, max_length=100)),
                ('user_defined_type', corehq.form_processor.models.TruncatingCharField(max_length=20, null=True, blank=True)),
                ('delta', models.IntegerField(default=0)),
                ('updated_balance', models.IntegerField(default=0)),
            ],
            options={
                'db_table': 'form_processor_ledgertransaction',
            },
            bases=(corehq.util.mixin.DisabledDbMixin, models.Model),
        ),
        migrations.AlterField(
            model_name='ledgertransaction',
            name='case_id',
            field=models.CharField(default=None, max_length=255, db_index=True),
            preserve_default=True,
        ),
        migrations.AlterIndexTogether(
            name='ledgertransaction',
            index_together=set([('case_id', 'entry_id', 'section_id')]),
        ),
        # drop unused indexes
        HqRunSQL(
            "DROP INDEX IF EXISTS form_processor_ledgervalue_case_id_6787b84005e3c4e0_like",
            "SELECT 1"
        ),
        HqRunSQL(
            "DROP INDEX IF EXISTS form_processor_ledgervalue_entry_id_7ba5b60783fc16d1_like",
            "SELECT 1"
        ),
        HqRunSQL(
            "DROP INDEX IF EXISTS form_processor_ledgervalue_section_id_7e237eaaa0c800ea_like",
            "SELECT 1"
        )
    ]
<commit_msg>Fix reference<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import corehq.form_processor.models
from corehq.sql_db.operations import HqRunSQL
from corehq.util.mixin import DisabledDbMixin

class Migration(migrations.Migration):

    dependencies = [
        ('form_processor', '0053_add_deletion_fields'),
    ]

    operations = [
        migrations.CreateModel(
            name='LedgerTransaction',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('form_id', models.CharField(max_length=255)),
                ('server_date', models.DateTimeField()),
                ('report_date', models.DateTimeField()),
                ('type', models.PositiveSmallIntegerField(choices=[(1, b'balance'), (2, b'transfer')])),
                ('case_id', models.CharField(default=None, max_length=255)),
                ('entry_id', models.CharField(default=None, max_length=100)),
                ('section_id', models.CharField(default=None, max_length=100)),
                ('user_defined_type', corehq.form_processor.models.TruncatingCharField(max_length=20, null=True, blank=True)),
                ('delta', models.IntegerField(default=0)),
                ('updated_balance', models.IntegerField(default=0)),
            ],
            options={
                'db_table': 'form_processor_ledgertransaction',
            },
            bases=(DisabledDbMixin, models.Model),
        ),
        migrations.AlterField(
            model_name='ledgertransaction',
            name='case_id',
            field=models.CharField(default=None, max_length=255, db_index=True),
            preserve_default=True,
        ),
        migrations.AlterIndexTogether(
            name='ledgertransaction',
            index_together=set([('case_id', 'entry_id', 'section_id')]),
        ),
        # drop unused indexes
        HqRunSQL(
            "DROP INDEX IF EXISTS form_processor_ledgervalue_case_id_6787b84005e3c4e0_like",
            "SELECT 1"
        ),
        HqRunSQL(
            "DROP INDEX IF EXISTS form_processor_ledgervalue_entry_id_7ba5b60783fc16d1_like",
            "SELECT 1"
        ),
        HqRunSQL(
            "DROP INDEX IF EXISTS form_processor_ledgervalue_section_id_7e237eaaa0c800ea_like",
            "SELECT 1"
        )
    ]
<|endoftext|>


<commit_before>__version__ = (2, 0, 1)
version = '.'.join(map(str, __version__))
<commit_msg>Bump version to 2.1.0<commit_after>__version__ = (2, 1, 0)
version = '.'.join(map(str, __version__))
<|endoftext|>


<commit_before>"""Module for the custom Django makeresources command."""

import os
import os.path
import importlib
import itertools
from time import time
from django.core.management.base import BaseCommand
from django.http.request import HttpRequest
from resources.models import Resource
from resources.views.generate_resource_pdf import generate_resource_pdf

class Command(BaseCommand):
    """Required command class for the custom Django makestaticresources command."""

    help = "Creates static PDF files of resource combinations."

    def add_arguments(self, parser):
        """Add optional parameter to makeresources command."""
        parser.add_argument(
            "resource_name",
            nargs="?",
            default=None,
            help="The resource name to generate",
        )

    def handle(self, *args, **options):
        """Automatically called when the makeresources command is given."""
        BASE_PATH = "staticfiles/resources/"
        if not os.path.exists(BASE_PATH):
            os.makedirs(BASE_PATH)

        if options["resource_name"]:
            resources = [Resource.objects.get(name=options["resource_name"])]
        else:
            resources = Resource.objects.order_by("name")

        for resource in resources:
            resource_start_time = time()
            print("Creating {}...".format(resource.name))
            # Get path to resource module
            resource_view = resource.generation_view
            if resource_view.endswith(".py"):
                resource_view = resource_view[:-3]
            module_path = "resources.views.{}".format(resource_view)
            # Save resource module
            resource_module = importlib.import_module(module_path)
            parameter_options = resource_module.valid_options()
            parameter_option_keys = sorted(parameter_options)
            combinations = [dict(zip(parameter_option_keys, product)) for product in itertools.product(*(parameter_options[parameter_option_key] for parameter_option_key in parameter_option_keys))]  # noqa: E501
            # Create PDF for all possible combinations
            for number, combination in enumerate(combinations):
                start_time = time()
                request = HttpRequest()
                if resource.copies:
                    combination["copies"] = 30
                request.GET = combination
                (pdf_file, filename) = generate_resource_pdf(request, resource, module_path)
                filename = "{}.pdf".format(filename)
                pdf_file_output = open(os.path.join(BASE_PATH, filename), "wb")
                pdf_file_output.write(pdf_file)
                pdf_file_output.close()
                print("Created {}".format(filename))
                print("{}{:.1f} secs".format(" " * 4, time() - start_time))
                print("{}{:.0f}% of {} generated".format(" " * 4, (number + 1) / len(combinations) * 100, resource.name))
            print("\n{} took {:.1f} secs to generate all combinations\n".format(resource.name, time() - resource_start_time))
<commit_msg>PEP8 fix<commit_after>"""Module for the custom Django makeresources command."""

import os
import os.path
import importlib
import itertools
from time import time
from django.core.management.base import BaseCommand
from django.http.request import HttpRequest
from resources.models import Resource
from resources.views.generate_resource_pdf import generate_resource_pdf

class Command(BaseCommand):
    """Required command class for the custom Django makestaticresources command."""

    help = "Creates static PDF files of resource combinations."

    def add_arguments(self, parser):
        """Add optional parameter to makeresources command."""
        parser.add_argument(
            "resource_name",
            nargs="?",
            default=None,
            help="The resource name to generate",
        )

    def handle(self, *args, **options):
        """Automatically called when the makeresources command is given."""
        BASE_PATH = "staticfiles/resources/"
        if not os.path.exists(BASE_PATH):
            os.makedirs(BASE_PATH)

        if options["resource_name"]:
            resources = [Resource.objects.get(name=options["resource_name"])]
        else:
            resources = Resource.objects.order_by("name")

        for resource in resources:
            resource_start_time = time()
            print("Creating {}...".format(resource.name))
            # Get path to resource module
            resource_view = resource.generation_view
            if resource_view.endswith(".py"):
                resource_view = resource_view[:-3]
            module_path = "resources.views.{}".format(resource_view)
            # Save resource module
            resource_module = importlib.import_module(module_path)
            parameter_options = resource_module.valid_options()
            parameter_option_keys = sorted(parameter_options)
            combinations = [dict(zip(parameter_option_keys, product)) for product in itertools.product(*(parameter_options[parameter_option_key] for parameter_option_key in parameter_option_keys))]  # noqa: E501
            # Create PDF for all possible combinations
            for number, combination in enumerate(combinations):
                start_time = time()
                request = HttpRequest()
                if resource.copies:
                    combination["copies"] = 30
                request.GET = combination
                (pdf_file, filename) = generate_resource_pdf(request, resource, module_path)
                filename = "{}.pdf".format(filename)
                pdf_file_output = open(os.path.join(BASE_PATH, filename), "wb")
                pdf_file_output.write(pdf_file)
                pdf_file_output.close()
                print("Created {}".format(filename))
                print("{}{:.1f} secs".format(" " * 4, time() - start_time))
                print("{}{:.0f}% of {} generated".format(
                    " " * 4,
                    (number + 1) / len(combinations) * 100, resource.name
                ))
            print("\n{} took {:.1f} secs to generate all combinations\n".format(
                resource.name,
                time() - resource_start_time
            ))
<|endoftext|>


<commit_before>from scapy.all import *

packetCount = 0
message = False
messageend = False

def customAction(packet):
	global packetCount
	packetCount += 1
	print "Packet #" + str(packetCount) + ": " +packet[0][1].src + "==>" + packet[0][1].dst + ": " + str(packet[0].SUFFIX)
	suffixnum = packet[0].SUFFIX
	if(suffixnum == 23130):
		message = True
	if(suffixnum == 23130):
		messageend = True
	if(messageend1 and suffixnum == 23130):
		message = False
	asciivalue = hex(suffixnum)[4:]
	asciivalue = int(asciivalue, 16)
	character = chr(asciivalue)
	if(message):
		return "! " + character + " !"

sniff(filter="port 137", prn=customAction)
<commit_msg>misspelling<commit_after>from scapy.all import *

packetCount = 0
message = False
messageend = False

def customAction(packet):
	global packetCount
	packetCount += 1
	print "Packet #" + str(packetCount) + ": " +packet[0][1].src + "==>" + packet[0][1].dst + ": " + str(packet[0].SUFFIX)
	suffixnum = packet[0].SUFFIX
	if(suffixnum == 23130):
		message = True
	if(suffixnum == 23130):
		messageend = True
	if(messageend and suffixnum == 23130):
		message = False
	asciivalue = hex(suffixnum)[4:]
	asciivalue = int(asciivalue, 16)
	character = chr(asciivalue)
	if(message):
		return "! " + character + " !"

sniff(filter="port 137", prn=customAction)
<|endoftext|>


<commit_before>"""
``nanshe`` package, an image processing toolkit.

===============================================================================
Overview
===============================================================================

The ``nanshe`` package is an image processing package that contains a variety
of different techniques, which are used primarily to assemble the ADINA
algorithm proposed by Diego, et al.
( doi:`10.1109/ISBI.2013.6556660`_ ) to extract active neurons from
an image sequence. This algorithm uses online dictionary learning (a form of
matrix factorization) at its heart as implemented by Marial, et al.
( doi:`10.1145/1553374.1553463`_ ) to find a set of atoms (or basis
images) that are representative of an image sequence and can be used to
approximately reconstruct the sequence. However, it is designed in a modular
way so that a different matrix factorization could be swapped in and
appropriately parameterized. Other portions of the algorithm include a
preprocessing phase that has a variety of different techniques that can be
applied optionally. For example, removing registration artifacts from
a line-by-line registration algorithm, background subtraction, and a wavelet
transform to filter objects in a particular size.

===============================================================================
 Installation
===============================================================================

-------------------------------------------------------------------------------
 Dependencies
-------------------------------------------------------------------------------
Implementation of the algorithm has been done here in pure Python. However, a
few dependencies are required to get started. These include NumPy_, SciPy_,
h5py_, scikit-image_, SPAMS_, VIGRA_, and rank_filter_. The first 4 can be
found in standard distributions like Anaconda_. Installing VIGRA and
rank_filter can be done by using CMake_. SPAMS requires an existing BLAS/LAPACK
implementation. On Mac and Linux, this can be anything. Typically ATLAS_ is
used, but OpenBLAS_ or `Intel MKL`_ (if available) can be used, as well. This
will require modifying the setup.py script. On Windows, the setup.py links to
R_, which should be changed if another BLAS is available.

-------------------------------------------------------------------------------
 Building
-------------------------------------------------------------------------------
As this package is pure Python, building follows through the standard method.
Currently, we require setuptools_ for installation; so, make sure it is
installed. Then simply issue the following command to build and install.

.. code-block:: sh

    python setup.py install

Alternatively, one can build and then install in two steps if that is
preferable.

.. code-block:: sh

    python setup.py build
    python setup.py install

-------------------------------------------------------------------------------
 Testing
-------------------------------------------------------------------------------
Running the test suite is fairly straightforward. Testing is done using nose_;
so, make sure you have a running copy if you wish to run the tests. Some of the
tests require drmaa_ installed and properly configured. If that is not the
case, those tests will be skipped automatically. To run the test suite, one
must be in the source directory. Then simply run the following command. This
will run all the tests and doctests. Depending on your machine, this will take
a few minutes to complete.

.. code-block:: sh

    nosetests

The full test suite includes 3D tests, which are very slow to run and so are
not run by default. As the code has been written to be dimensionally agnostic,
these tests don't cover anything that the 2D tests don't already cover. To run
the 3D tests, simply use ``setup.all.cfg``.

.. code-block:: sh

    nosetests -c setup.all.cfg

It is also possible to run this as part of the setup.py process. In which case,
this can be done as shown below. If 3D tests are required for this portion, one
need only replace ``setup.cfg`` with ``setup.all.cfg``.

.. code-block:: sh

    python setup.py nosetests

-------------------------------------------------------------------------------
 Documentation
-------------------------------------------------------------------------------
Current documentation can be found on the GitHub page
( http://jakirkham.github.io/nanshe/ ). A new copy is rebuilt any time there is
a passing commit is added to the ``master`` branch. Each documentation commit
is added to ``gh-pages`` branch with a reference to the commit in ``master``
that triggered the build as well as the tag (version) if provided.

It is also possible to build the documentation from source. This project uses
Sphinx_ for generating documentation. Please make sure you have it installed.
In particular, a version from 1.3 or later is required. Additionally, the
`Cloud Sphinx Theme`_ is required for generating the documentation and is used
in the HTML layout.

The ``rst`` files (outside of ``index.rst`` are not distributed with the source
code. This is because it is trivial to generate them and it is to easy for the
code to become out of sync with documentation if they are distributed. However,
building ``rst`` files has been made a dependency of all other documentation
build steps so one does not have to think about this. The preferred method for
building documentation is to use the ``setup.py`` hook as shown below. This
will build the RST files and place them in ``docs/``. It will also build the
HTML files by default and put them in the directory ``build/sphinx/html/``.
Simply open the ``index.html`` file to take a look.

.. code-block:: sh

    python setup.py build_sphinx

 More build options can be determined by running the help command.

.. code-block:: sh

    python setup.py build_sphinx --help

-------------------------------------------------------------------------------
 Cleaning
-------------------------------------------------------------------------------
After any building operation a number of undesirable intermediate files are
created and left behind that one may wish to remove. To do this one merely
needs to run the clean command.

.. code-block:: sh

    python setup.py clean

This has been modified to also remove RST files generated when building
documentation. However, it will leave any final build products like HTML files.
If one wishes to remove everything built (including final build products), the
clean all command will do this.

.. code-block:: sh

    python setup.py clean --all

.. _`10.1109/ISBI.2013.6556660`: http://dx.doi.org/10.1109/ISBI.2013.6556660
.. _`10.1145/1553374.1553463`: http://dx.doi.org/10.1145/1553374.1553463
.. _NumPy: http://www.numpy.org/
.. _SciPy: http://www.scipy.org/
.. _h5py: http://www.h5py.org/
.. _scikit-image: http://scikit-image.org/
.. _SPAMS: http://spams-devel.gforge.inria.fr/
.. _VIGRA: http://ukoethe.github.io/vigra/
.. _rank_filter: http://github.com/jakirkham/rank_filter/
.. _Anaconda: http://store.continuum.io/cshop/anaconda/
.. _CMake: http://www.cmake.org/
.. _ATLAS: http://math-atlas.sourceforge.net/
.. _OpenBLAS: http://www.openblas.net/
.. _`Intel MKL`: http://software.intel.com/en-us/intel-mkl
.. _R: http://www.r-project.org/
.. _setuptools: http://pythonhosted.org/setuptools/
.. _nose: http://nose.readthedocs.org/en/latest/
.. _drmaa: http://github.com/pygridtools/drmaa-python
.. _Sphinx: http://sphinx-doc.org/
.. _`Cloud Sphinx Theme`: https://pythonhosted.org/cloud_sptheme/
"""
__author__ = "John Kirkham <kirkhamj@janelia.hhmi.org>"
__date__ = "$Dec 22, 2014 08:46:12 EST$"

from ._version import get_versions
__version__ = get_versions()['version']
del get_versions

__all__ = [
    "box", "converter", "io", "imp", "learner", "registerer", "syn", "util",
    # "viewer"
]

import box
import converter
import io
import imp
import learner
import registerer
import syn
import util
# import viewer           # Must be commented as there is some segfault
                          # coming from Volumina.

<commit_msg>nanshe/__init__.py: Provide information about building packages with conda. Fixes #197.<commit_after>"""
``nanshe`` package, an image processing toolkit.

===============================================================================
Overview
===============================================================================

The ``nanshe`` package is an image processing package that contains a variety
of different techniques, which are used primarily to assemble the ADINA
algorithm proposed by Diego, et al.
( doi:`10.1109/ISBI.2013.6556660`_ ) to extract active neurons from
an image sequence. This algorithm uses online dictionary learning (a form of
matrix factorization) at its heart as implemented by Marial, et al.
( doi:`10.1145/1553374.1553463`_ ) to find a set of atoms (or basis
images) that are representative of an image sequence and can be used to
approximately reconstruct the sequence. However, it is designed in a modular
way so that a different matrix factorization could be swapped in and
appropriately parameterized. Other portions of the algorithm include a
preprocessing phase that has a variety of different techniques that can be
applied optionally. For example, removing registration artifacts from
a line-by-line registration algorithm, background subtraction, and a wavelet
transform to filter objects in a particular size.

===============================================================================
 Installation
===============================================================================

-------------------------------------------------------------------------------
 Dependencies
-------------------------------------------------------------------------------
Implementation of the algorithm has been done here in pure Python. However, a
few dependencies are required to get started. These include NumPy_, SciPy_,
h5py_, scikit-image_, SPAMS_, VIGRA_, and rank_filter_. The first 4 can be
found in standard distributions like Anaconda_. Installing VIGRA and
rank_filter can be done by using CMake_. SPAMS requires an existing BLAS/LAPACK
implementation. On Mac and Linux, this can be anything. Typically ATLAS_ is
used, but OpenBLAS_ or `Intel MKL`_ (if available) can be used, as well. This
will require modifying the setup.py script. On Windows, the setup.py links to
R_, which should be changed if another BLAS is available.

-------------------------------------------------------------------------------
 Building
-------------------------------------------------------------------------------
Python
===============================================================================
As this package is pure Python, building follows through the standard method.
Currently, we require setuptools_ for installation; so, make sure it is
installed. Then simply issue the following command to build and install.

.. code-block:: sh

    python setup.py install

Alternatively, one can build and then install in two steps if that is
preferable.

.. code-block:: sh

    python setup.py build
    python setup.py install

Conda
===============================================================================
Current packages can be found on our binstar_ channel
( https://binstar.org/jakirkham/nanshe ). New ones are released every time a
passing tagged release is pushed to the ``master`` branch on GitHub. It is also
possible to build packages for conda_ for non-release commits as we do in our
continuous integration strategy.

To do this one requires the dependencies be installed or be available from a
binstar channel. Additionally, one must be using the conda's ``root``
environment and have conda-build installed. Once this is done one need
only the run the following command with ``setup.py``.

.. code-block:: sh

    python setup.py bdist_conda

Assuming this completes successfully, conda will provide the path to the built
package.

-------------------------------------------------------------------------------
 Testing
-------------------------------------------------------------------------------
Running the test suite is fairly straightforward. Testing is done using nose_;
so, make sure you have a running copy if you wish to run the tests. Some of the
tests require drmaa_ installed and properly configured. If that is not the
case, those tests will be skipped automatically. To run the test suite, one
must be in the source directory. Then simply run the following command. This
will run all the tests and doctests. Depending on your machine, this will take
a few minutes to complete.

.. code-block:: sh

    nosetests

The full test suite includes 3D tests, which are very slow to run and so are
not run by default. As the code has been written to be dimensionally agnostic,
these tests don't cover anything that the 2D tests don't already cover. To run
the 3D tests, simply use ``setup.all.cfg``.

.. code-block:: sh

    nosetests -c setup.all.cfg

It is also possible to run this as part of the setup.py process. In which case,
this can be done as shown below. If 3D tests are required for this portion, one
need only replace ``setup.cfg`` with ``setup.all.cfg``.

.. code-block:: sh

    python setup.py nosetests

-------------------------------------------------------------------------------
 Documentation
-------------------------------------------------------------------------------
Current documentation can be found on the GitHub page
( http://jakirkham.github.io/nanshe/ ). A new copy is rebuilt any time there is
a passing commit is added to the ``master`` branch. Each documentation commit
is added to ``gh-pages`` branch with a reference to the commit in ``master``
that triggered the build as well as the tag (version) if provided.

It is also possible to build the documentation from source. This project uses
Sphinx_ for generating documentation. Please make sure you have it installed.
In particular, a version from 1.3 or later is required. Additionally, the
`Cloud Sphinx Theme`_ is required for generating the documentation and is used
in the HTML layout.

The ``rst`` files (outside of ``index.rst`` are not distributed with the source
code. This is because it is trivial to generate them and it is to easy for the
code to become out of sync with documentation if they are distributed. However,
building ``rst`` files has been made a dependency of all other documentation
build steps so one does not have to think about this. The preferred method for
building documentation is to use the ``setup.py`` hook as shown below. This
will build the RST files and place them in ``docs/``. It will also build the
HTML files by default and put them in the directory ``build/sphinx/html/``.
Simply open the ``index.html`` file to take a look.

.. code-block:: sh

    python setup.py build_sphinx

 More build options can be determined by running the help command.

.. code-block:: sh

    python setup.py build_sphinx --help

-------------------------------------------------------------------------------
 Cleaning
-------------------------------------------------------------------------------
After any building operation a number of undesirable intermediate files are
created and left behind that one may wish to remove. To do this one merely
needs to run the clean command.

.. code-block:: sh

    python setup.py clean

This has been modified to also remove RST files generated when building
documentation. However, it will leave any final build products like HTML files.
If one wishes to remove everything built (including final build products), the
clean all command will do this.

.. code-block:: sh

    python setup.py clean --all

.. _`10.1109/ISBI.2013.6556660`: http://dx.doi.org/10.1109/ISBI.2013.6556660
.. _`10.1145/1553374.1553463`: http://dx.doi.org/10.1145/1553374.1553463
.. _NumPy: http://www.numpy.org/
.. _SciPy: http://www.scipy.org/
.. _h5py: http://www.h5py.org/
.. _scikit-image: http://scikit-image.org/
.. _SPAMS: http://spams-devel.gforge.inria.fr/
.. _VIGRA: http://ukoethe.github.io/vigra/
.. _rank_filter: http://github.com/jakirkham/rank_filter/
.. _Anaconda: http://store.continuum.io/cshop/anaconda/
.. _CMake: http://www.cmake.org/
.. _ATLAS: http://math-atlas.sourceforge.net/
.. _OpenBLAS: http://www.openblas.net/
.. _`Intel MKL`: http://software.intel.com/en-us/intel-mkl
.. _R: http://www.r-project.org/
.. _setuptools: http://pythonhosted.org/setuptools/
.. _binstar: https://binstar.org/
.. _conda: http://conda.pydata.org/
.. _nose: http://nose.readthedocs.org/en/latest/
.. _drmaa: http://github.com/pygridtools/drmaa-python
.. _Sphinx: http://sphinx-doc.org/
.. _`Cloud Sphinx Theme`: https://pythonhosted.org/cloud_sptheme/
"""
__author__ = "John Kirkham <kirkhamj@janelia.hhmi.org>"
__date__ = "$Dec 22, 2014 08:46:12 EST$"

from ._version import get_versions
__version__ = get_versions()['version']
del get_versions

__all__ = [
    "box", "converter", "io", "imp", "learner", "registerer", "syn", "util",
    # "viewer"
]

import box
import converter
import io
import imp
import learner
import registerer
import syn
import util
# import viewer           # Must be commented as there is some segfault
                          # coming from Volumina.

<|endoftext|>


<commit_before>#!/usr/bin/python

import Adafruit_DHT
from sensor import Sensor

class Dht(Sensor):

    SENSOR = Adafruit_DHT.DHT22
    NAME = 'DHT'
    PIN = 21
    SLEEP = 10

    temperature = None
    humidity = None

    def sensor_read_callback(self):
        # Try to grab a sensor reading.  Use the read_retry method which will retry up
        # to 15 times to get a sensor reading (waiting 2 seconds between each retry).
        self.humidity, self.temperature = Adafruit_DHT.read_retry(self.SENSOR, self.PIN)
        self.notify(topic='%s/temperature' % self.mqtt_topic, payload=self.temperature)
        self.notify(topic='%s/humidity' % self.mqtt_topic, payload=self.humidity)
        self.logger.info('Sensor %s read data: %s *C and %s', self.NAME, self.temperature, self.humidity)

        if self.humidity is not None and self.temperature is not None:
            self.FAILED = 0
        else:
            self.FAILED += 1

    def output(self, desc=True, temp=False, hum=False):
        # Note that sometimes you won't get a reading and the results will be null
        # (because Linux can't guarantee the timing of calls to read the sensor).
        # If this happens try again!
        out = ''

        if desc and (self.temperature or self.humidity):
            out += 'Temperature = {0:0.1f}*C\n'.format(self.temperature)
            out += 'Humidity = {0:0.1f}%'.format(self.humidity)
        else:
            out = 'Failed to get reading. Try again!'

        if temp and self.temperature:
            out = str(self.temperature)

        if hum and self.humidity:
            out = str(self.humidity)

        return out
<commit_msg>Updated notify only on successful read and round the value<commit_after>#!/usr/bin/python

import Adafruit_DHT
from sensor import Sensor

class Dht(Sensor):

    SENSOR = Adafruit_DHT.DHT22
    NAME = 'DHT'
    PIN = 21
    SLEEP = 10

    temperature = None
    humidity = None

    def sensor_read_callback(self):
        # Try to grab a sensor reading.  Use the read_retry method which will retry up
        # to 15 times to get a sensor reading (waiting 2 seconds between each retry).
        self.humidity, self.temperature = Adafruit_DHT.read_retry(self.SENSOR, self.PIN)
        self.logger.info('Sensor %s read data: %s *C and %s', self.NAME, self.temperature, self.humidity)

        if self.humidity is not None and self.temperature is not None:
            self.FAILED = 0
            self.notify(topic='%s/temperature' % self.mqtt_topic, payload="{0:0.1f}".format(self.temperature))
            self.notify(topic='%s/humidity' % self.mqtt_topic, payload="{0:0.1f}".format(self.humidity))
        else:
            self.FAILED += 1

    def output(self, desc=True, temp=False, hum=False):
        # Note that sometimes you won't get a reading and the results will be null
        # (because Linux can't guarantee the timing of calls to read the sensor).
        # If this happens try again!
        out = ''

        if desc and (self.temperature or self.humidity):
            out += 'Temperature = {0:0.1f}*C\n'.format(self.temperature)
            out += 'Humidity = {0:0.1f}%'.format(self.humidity)
        else:
            out = 'Failed to get reading. Try again!'

        if temp and self.temperature:
            out = str(self.temperature)

        if hum and self.humidity:
            out = str(self.humidity)

        return out
<|endoftext|>


<commit_before>from __future__ import absolute_import

import functools
import os
import re
import tempfile
import traceback
import platform
import subprocess
import datetime

from PyQt4 import QtCore, QtGui
Qt = QtCore.Qt

from sgfs import SGFS
from sgactions.ticketui import ticket_ui_context

from sgpublish.uiutils import ComboBox, hbox, vbox, icon

class TimeSpinner(QtGui.QSpinBox):

    def __init__(self):
        super(TimeSpinner, self).__init__(
            singleStep=15,
            maximum=60*8*5,
        )

    def textFromValue(self, value):
        return '%d:%02d' % (value / 60, value % 60)

    def valueFromText(self, text, strict=False):
        m = re.match('(\d+):(\d{,2})', text)
        if m:
            return 60 * int(m.group(1)) + int(m.group(2) or 0)
        try:
            return int(text)
        except ValueError:
            pass
        try:
            return int(60 * float(text))
        except ValueError:
            pass

        if strict:
            return None
        else:
            return 0

    def validate(self, text, pos):
        if self.valueFromText(text) is not None:
            return QtGui.QValidator.Acceptable, pos
        else:
            return QtGui.QValidator.Invalid, pos

class Widget(QtGui.QWidget):

    # Windows should hide on these.
    beforeScreenshot = QtCore.pyqtSignal()
    afterScreenshot = QtCore.pyqtSignal()

    # Need a signal to communicate across threads.
    loaded_publishes = QtCore.pyqtSignal(object, object)

    def __init__(self, exporter):
        super(Widget, self).__init__()

        self._exporter = exporter

        self._existing_names = set()

        basename = os.path.basename(exporter.filename_hint)
        basename = os.path.splitext(basename)[0]
        self._basename = re.sub(r'_*[rv]\d+', '', basename)

        self._setup_ui()

        # First screenshot.
        self.take_full_screenshot()

    def _setup_ui(self):

        self.setLayout(QtGui.QVBoxLayout())

        self._task_combo = ComboBox()
        self._task_combo.addItem('Loading...', {'loading': True})
        self._task_combo.currentIndexChanged.connect(self._task_changed)

        self._name_combo = ComboBox()
        self._name_combo.addItem('Loading...', {'loading': True})
        self._name_combo.addItem('Create new stream...', {'new': True})
        self._name_combo.currentIndexChanged.connect(self._name_changed)

        self.layout().addLayout(hbox(
            vbox("Task", self._task_combo),
            vbox("Publish Stream", self._name_combo),
            spacing=4
        ))

        self._name_field = QtGui.QLineEdit(self._basename)
        self._name_field.setEnabled(False)
        self._name_field.editingFinished.connect(self._on_name_edited)

        self._version_spinbox = QtGui.QSpinBox()
        self._version_spinbox.setMinimum(1)
        self._version_spinbox.setMaximum(9999)
        self._version_spinbox.valueChanged.connect(self._on_version_changed)
        self._version_warning_issued = False

        self.layout().addLayout(hbox(
            vbox("Name", self._name_field),
            vbox("Version", self._version_spinbox),
            spacing=4
        ))

        # Get publish data in the background.
        self.loaded_publishes.connect(self._populate_existing_data)
        self._thread = QtCore.QThread()
        self._thread.run = self._fetch_existing_data
        self._thread.start()

        self._description = QtGui.QTextEdit('')
        self._description.setMaximumHeight(100)

        self._thumbnail_path = None
        self._thumbnail_canvas = QtGui.QLabel()
        self._thumbnail_canvas.setFrameShadow(QtGui.QFrame.Sunken)
        self._thumbnail_canvas.setFrameShape(QtGui.QFrame.Panel)
        self._thumbnail_canvas.setToolTip("Click to specify part of screen.")
        self._thumbnail_canvas.mouseReleaseEvent = self.take_partial_screenshot

        self.layout().addLayout(hbox(
            vbox("Describe Your Changes", self._description),
            vbox("Thumbnail", self._thumbnail_canvas),
        ))

        self._movie_path = QtGui.QLineEdit()
        self._movie_browse = QtGui.QPushButton(icon('silk/folder', size=12, as_icon=True), "Browse")
        self._movie_browse.clicked.connect(self._on_movie_browse)
        self._movie_layout = hbox(self._movie_path, self._movie_browse)
        self.layout().addLayout(vbox("Path to Movie or Frames (to be copied to publish)", self._movie_layout, spacing=4))
        self._movie_browse.setFixedHeight(self._movie_path.sizeHint().height())
        self._movie_browse.setFixedWidth(self._movie_browse.sizeHint().width() + 2)

        self._promote_checkbox = QtGui.QCheckBox("Promote to 'Version' for review")
        # self.layout().addWidget(self._promote_checkbox)

        self._timelog_spinbox = TimeSpinner()
        add_hour = QtGui.QPushButton("+1 Hour")
        add_hour.setFixedHeight(self._timelog_spinbox.sizeHint().height())
        @add_hour.clicked.connect
        def on_add_hour():
            self._timelog_spinbox.setValue(self._timelog_spinbox.value() + 60)
        add_day = QtGui.QPushButton("+1 Day")
        add_day.setFixedHeight(self._timelog_spinbox.sizeHint().height())
        @add_day.clicked.connect
        def on_add_day():
            self._timelog_spinbox.setValue(self._timelog_spinbox.value() + 60 * 8)

        self.layout().addLayout(hbox(
            vbox("Time to Log", hbox(self._timelog_spinbox, "hrs:mins", add_hour, add_day)),
            vbox("Review", self._promote_checkbox),
        ))

    def _fetch_existing_data(self):
        try:
            sgfs = SGFS()
            tasks = sgfs.entities_from_path(self._exporter.workspace)
            if not tasks:
                raise ValueError('No entities in workspace %r' % self._exporter.workspace)
            if any(x['type'] != 'Task' for x in tasks):
                raise ValueError('Non-Task entity in workspace %r' % self._exporter.workspace)
            publishes = sgfs.session.find(
                'PublishEvent',
                [
                    ('sg_link.Task.id', 'in') + tuple(x['id'] for x in tasks),
                    ('sg_type', 'is', self._exporter.publish_type),
                    ('sg_version', 'greater_than', 0), # Skipped failures.
                ], [
                    'code',
                    'sg_version'
                ]
            )

        except Exception as e:
            self._task_combo.clear()
            self._task_combo.addItem('Loading Error! %s' % e, {})
            raise

        else:
            self.loaded_publishes.emit(tasks, publishes)

    def _populate_existing_data(self, tasks, publishes):

        history = self._exporter.get_previous_publish_ids()

        select = None

        self._existing_names.update(p['code'] for p in publishes)
        publishes.sort(key=lambda p: p['sg_version'])

        for t_i, task in enumerate(tasks):
            name_to_publish = {}
            for publish in publishes:
                if publish['sg_link'] is not task:
                    continue
                name = publish['code']
                name_to_publish[name] = publish

                if publish['id'] in history:
                    select = t_i, name

            self._task_combo.addItem('%s - %s' % task.fetch(('step.Step.short_name', 'content')), {
                'task': task,
                'publishes': name_to_publish,
            })

        if 'loading' in self._task_combo.itemData(0):
            if self._task_combo.currentIndex() == 0:
                self._task_combo.setCurrentIndex(1)
            self._task_combo.removeItem(0)

        if select:
            self._task_combo.setCurrentIndex(select[0])
            for i in xrange(self._name_combo.count()):
                data = self._name_combo.itemData(i)
                if data and data.get('name') == select[1]:
                    self._name_combo.setCurrentIndex(i)
                    break

    def _task_changed(self, index):
        data = self._name_combo.currentData()
        if not data:
            return
        was_new = 'new' in data or 'loading' in data
        self._name_combo.clear()
        data = self._task_combo.currentData() or {}

        for name, publish in sorted(data.get('publishes', {}).iteritems()):
            self._name_combo.addItem('%s (v%04d)' % (name, publish['sg_version']), {'name': name, 'publish': publish})
        self._name_combo.addItem('Create New Stream...', {'new': True})
        if was_new:
            self._name_combo.setCurrentIndex(self._name_combo.count() - 1)
        else:
            self._name_combo.setCurrentIndex(0)

    def _name_changed(self, index):
        data = self._name_combo.itemData(index)
        if not data:
            return
        self._name_field.setEnabled('new' in data)
        self._name_field.setText(data.get('name', self._basename))
        self._version_spinbox.setValue(data.get('publish', {}).get('sg_version', 0) + 1)

    def _on_name_edited(self):
        name = str(self._name_field.text())
        name = re.sub(r'\W+', '_', name).strip('_')
        self._name_field.setText(name)

    def _on_version_changed(self, new_value):
        data = self._name_combo.itemData(self._name_combo.currentIndex())
        if data.get('publish') and new_value != data['publish']['sg_version'] + 1 and not self._version_warning_issued:
            res = QtGui.QMessageBox.warning(None,
                "Manual Versions?",
                "Are you sure you want to change the version?\n"
                "The next one has already been selected for you...",
                QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel,
                QtGui.QMessageBox.Cancel
            )
            if res & QtGui.QMessageBox.Cancel:
                self._version_spinbox.setValue(data['publish']['sg_version'] + 1)
                return
            self._version_warning_issued = True

    def _on_movie_browse(self):

        existing = str(self._movie_path.text())

        dialog = QtGui.QFileDialog(None, "Select Movie or First Frame")
        dialog.setFilter('Movie or Frame (*.mov *.exr *.tif *.tiff *.jpg *.jpeg)')
        dialog.setFileMode(dialog.ExistingFile)
        dialog.setDirectory(os.path.dirname(existing) if existing else os.getcwd())
        if existing:
            dialog.selectFile(existing)

        if not dialog.exec_():
            return

        files = dialog.selectedFiles()
        path = str(files.First())
        self.setFrames(path)

    def setFrames(self, path):
        self._movie_path.setText(path)
        if path:
            self._promote_checkbox.setCheckState(Qt.Checked)

    def take_full_screenshot(self):
        pass

    def take_partial_screenshot(self, *args):
        path = tempfile.NamedTemporaryFile(suffix=".png", prefix="screenshot", delete=False).name

        self.beforeScreenshot.emit()

        if platform.system() == "Darwin":
            # use built-in screenshot command on the mac
            proc = subprocess.Popen(['screencapture', '-mis', path])
        else:
            proc = subprocess.Popen(['import', path])
        proc.wait()

        self.afterScreenshot.emit()

        if os.stat(path).st_size:
            self.setThumbnail(path)

    def setThumbnail(self, path):
        self._thumbnail_path = path
        pixmap = QtGui.QPixmap(path).scaled(200, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self._thumbnail_canvas.setPixmap(pixmap)
        self._thumbnail_canvas.setFixedSize(pixmap.size())

    def name(self):
        data = self._name_combo.currentData()
        return data.get('name', str(self._name_field.text()))

    def description(self):
        return str(self._description.toPlainText())

    def version(self):
        return self._version_spinbox.value()

    def thumbnail_path(self):
        return self._thumbnail_path

    def _path_is_image(self, path):
        if os.path.splitext(path)[1][1:].lower() in (
            'jpg', 'jpeg', 'tif', 'tiff', 'exr',
        ):
            return path

    def frames_path(self):
        path = str(self._movie_path.text())
        if path and self._path_is_image(path):
            return path
        return None

    def movie_path(self):
        path = str(self._movie_path.text())
        if path and not self._path_is_image(path):
            return path
        return None

    def safety_check(self, **kwargs):

        # Colliding names.
        data = self._name_combo.currentData()
        if 'name' not in data and str(self._name_field.text()) in self._existing_names:
            QtGui.QMessageBox.critical(self,
                "Name Collision",
                "You cannot create a new stream with the same name as an"
                " existing one. Please select the existing stream or enter a"
                " unique name.",
            )
            # Fatal.
            return False

        # Promoting to version without a movie.
        if self._promote_checkbox.isChecked() and not (self.frames_path() or self.movie_path()):
            QtGui.QMessageBox.critical(self,
                "Review Version Without Movie",
                "You cannot promote a publish for review without frames or a"
                " movie.",
            )
            # Fatal.
            return False

        # Promoting to version without a timelog.
        if self._promote_checkbox.isChecked() and not self._timelog_spinbox.value():
            res = QtGui.QMessageBox.warning(self,
                "Version without Time Log",
                "Are you sure that this version did not take you any time?",
                QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                QtGui.QMessageBox.No,
            )
            if res & QtGui.QMessageBox.No:
                return False

        return True

    def export(self, **kwargs):
        with ticket_ui_context():
            return self.__export(kwargs)

    def __export(self, kwargs):

        if not self.safety_check(**kwargs):
            return

        task_data = self._task_combo.currentData()
        task = task_data.get('task')
        if not task:
            sgfs = SGFS()
            tasks = sgfs.entities_from_path(self._exporter.workspace, 'Task')
            if not tasks:
                raise ValueError('Could not find SGFS tagged entities')
            task = tasks[0]

        stream_data = self._name_combo.currentData()
        parent = stream_data.get('publish')

        publisher = self._exporter.publish(task,
            name=self.name(),
            description=self.description(),
            version=self.version(),
            parent=parent,
            thumbnail_path=self.thumbnail_path(),
            frames_path=self.frames_path(),
            movie_path=self.movie_path(),
            export_kwargs=kwargs,
        )

        if self._promote_checkbox.isChecked():
            # progress.setLabelText('Creating Version for Review...')
            promotion_fields = self._exporter.fields_for_review_version(publisher, **kwargs)
            print "PROMOTE", promotion_fields
            publisher.promote_for_review(**promotion_fields)

        # Create the timelog.
        minutes = self._timelog_spinbox.value()
        if minutes:
            # progress.setLabelText('Logging time...')
            publisher.sgfs.session.create('TimeLog', {
                'project': publisher.entity.project(),
                'entity': publisher.link,
                'user': publisher.sgfs.session.guess_user(),
                'duration': minutes,
                'description': '%s_v%04d' % (publisher.name, publisher.version),
                'date': datetime.datetime.utcnow().date(),
            })

        # progress.hide()

        return publisher

<commit_msg>Add entity name to task label<commit_after>from __future__ import absolute_import

import functools
import os
import re
import tempfile
import traceback
import platform
import subprocess
import datetime

from PyQt4 import QtCore, QtGui
Qt = QtCore.Qt

from sgfs import SGFS
from sgactions.ticketui import ticket_ui_context

from sgpublish.uiutils import ComboBox, hbox, vbox, icon

class TimeSpinner(QtGui.QSpinBox):

    def __init__(self):
        super(TimeSpinner, self).__init__(
            singleStep=15,
            maximum=60*8*5,
        )

    def textFromValue(self, value):
        return '%d:%02d' % (value / 60, value % 60)

    def valueFromText(self, text, strict=False):
        m = re.match('(\d+):(\d{,2})', text)
        if m:
            return 60 * int(m.group(1)) + int(m.group(2) or 0)
        try:
            return int(text)
        except ValueError:
            pass
        try:
            return int(60 * float(text))
        except ValueError:
            pass

        if strict:
            return None
        else:
            return 0

    def validate(self, text, pos):
        if self.valueFromText(text) is not None:
            return QtGui.QValidator.Acceptable, pos
        else:
            return QtGui.QValidator.Invalid, pos

class Widget(QtGui.QWidget):

    # Windows should hide on these.
    beforeScreenshot = QtCore.pyqtSignal()
    afterScreenshot = QtCore.pyqtSignal()

    # Need a signal to communicate across threads.
    loaded_publishes = QtCore.pyqtSignal(object, object)

    def __init__(self, exporter):
        super(Widget, self).__init__()

        self._exporter = exporter

        self._existing_names = set()

        basename = os.path.basename(exporter.filename_hint)
        basename = os.path.splitext(basename)[0]
        self._basename = re.sub(r'_*[rv]\d+', '', basename)

        self._setup_ui()

        # First screenshot.
        self.take_full_screenshot()

    def _setup_ui(self):

        self.setLayout(QtGui.QVBoxLayout())

        self._task_combo = ComboBox()
        self._task_combo.addItem('Loading...', {'loading': True})
        self._task_combo.currentIndexChanged.connect(self._task_changed)

        self._name_combo = ComboBox()
        self._name_combo.addItem('Loading...', {'loading': True})
        self._name_combo.addItem('Create new stream...', {'new': True})
        self._name_combo.currentIndexChanged.connect(self._name_changed)

        self._tasksLabel = QtGui.QLabel("Task")
        self.layout().addLayout(hbox(
            vbox(self._tasksLabel, self._task_combo),
            vbox("Publish Stream", self._name_combo),
            spacing=4
        ))

        self._name_field = QtGui.QLineEdit(self._basename)
        self._name_field.setEnabled(False)
        self._name_field.editingFinished.connect(self._on_name_edited)

        self._version_spinbox = QtGui.QSpinBox()
        self._version_spinbox.setMinimum(1)
        self._version_spinbox.setMaximum(9999)
        self._version_spinbox.valueChanged.connect(self._on_version_changed)
        self._version_warning_issued = False

        self.layout().addLayout(hbox(
            vbox("Name", self._name_field),
            vbox("Version", self._version_spinbox),
            spacing=4
        ))

        # Get publish data in the background.
        self.loaded_publishes.connect(self._populate_existing_data)
        self._thread = QtCore.QThread()
        self._thread.run = self._fetch_existing_data
        self._thread.start()

        self._description = QtGui.QTextEdit('')
        self._description.setMaximumHeight(100)

        self._thumbnail_path = None
        self._thumbnail_canvas = QtGui.QLabel()
        self._thumbnail_canvas.setFrameShadow(QtGui.QFrame.Sunken)
        self._thumbnail_canvas.setFrameShape(QtGui.QFrame.Panel)
        self._thumbnail_canvas.setToolTip("Click to specify part of screen.")
        self._thumbnail_canvas.mouseReleaseEvent = self.take_partial_screenshot

        self.layout().addLayout(hbox(
            vbox("Describe Your Changes", self._description),
            vbox("Thumbnail", self._thumbnail_canvas),
        ))

        self._movie_path = QtGui.QLineEdit()
        self._movie_browse = QtGui.QPushButton(icon('silk/folder', size=12, as_icon=True), "Browse")
        self._movie_browse.clicked.connect(self._on_movie_browse)
        self._movie_layout = hbox(self._movie_path, self._movie_browse)
        self.layout().addLayout(vbox("Path to Movie or Frames (to be copied to publish)", self._movie_layout, spacing=4))
        self._movie_browse.setFixedHeight(self._movie_path.sizeHint().height())
        self._movie_browse.setFixedWidth(self._movie_browse.sizeHint().width() + 2)

        self._promote_checkbox = QtGui.QCheckBox("Promote to 'Version' for review")
        # self.layout().addWidget(self._promote_checkbox)

        self._timelog_spinbox = TimeSpinner()
        add_hour = QtGui.QPushButton("+1 Hour")
        add_hour.setFixedHeight(self._timelog_spinbox.sizeHint().height())
        @add_hour.clicked.connect
        def on_add_hour():
            self._timelog_spinbox.setValue(self._timelog_spinbox.value() + 60)
        add_day = QtGui.QPushButton("+1 Day")
        add_day.setFixedHeight(self._timelog_spinbox.sizeHint().height())
        @add_day.clicked.connect
        def on_add_day():
            self._timelog_spinbox.setValue(self._timelog_spinbox.value() + 60 * 8)

        self.layout().addLayout(hbox(
            vbox("Time to Log", hbox(self._timelog_spinbox, "hrs:mins", add_hour, add_day)),
            vbox("Review", self._promote_checkbox),
        ))

    def _fetch_existing_data(self):
        try:
            sgfs = SGFS()
            tasks = sgfs.entities_from_path(self._exporter.workspace)
            if not tasks:
                raise ValueError('No entities in workspace %r' % self._exporter.workspace)
            if any(x['type'] != 'Task' for x in tasks):
                raise ValueError('Non-Task entity in workspace %r' % self._exporter.workspace)
            publishes = sgfs.session.find(
                'PublishEvent',
                [
                    ('sg_link.Task.id', 'in') + tuple(x['id'] for x in tasks),
                    ('sg_type', 'is', self._exporter.publish_type),
                    ('sg_version', 'greater_than', 0), # Skipped failures.
                ], [
                    'code',
                    'sg_version'
                ]
            )

        except Exception as e:
            self._task_combo.clear()
            self._task_combo.addItem('Loading Error! %s' % e, {})
            raise

        else:
            self.loaded_publishes.emit(tasks, publishes)

    def _populate_existing_data(self, tasks, publishes):

        if tasks:
            entity = tasks[0].fetch('entity')
            name = entity.get('code') or entity.get('name')
            if name:
                self._tasksLabel.setText('Task on %s %s' % (entity['type'], name))

        history = self._exporter.get_previous_publish_ids()

        select = None

        self._existing_names.update(p['code'] for p in publishes)
        publishes.sort(key=lambda p: p['sg_version'])

        for t_i, task in enumerate(tasks):
            name_to_publish = {}
            for publish in publishes:
                if publish['sg_link'] is not task:
                    continue
                name = publish['code']
                name_to_publish[name] = publish

                if publish['id'] in history:
                    select = t_i, name

            self._task_combo.addItem('%s - %s' % task.fetch(('step.Step.short_name', 'content')), {
                'task': task,
                'publishes': name_to_publish,
            })

        if 'loading' in self._task_combo.itemData(0):
            if self._task_combo.currentIndex() == 0:
                self._task_combo.setCurrentIndex(1)
            self._task_combo.removeItem(0)

        if select:
            self._task_combo.setCurrentIndex(select[0])
            for i in xrange(self._name_combo.count()):
                data = self._name_combo.itemData(i)
                if data and data.get('name') == select[1]:
                    self._name_combo.setCurrentIndex(i)
                    break

    def _task_changed(self, index):
        data = self._name_combo.currentData()
        if not data:
            return
        was_new = 'new' in data or 'loading' in data
        self._name_combo.clear()
        data = self._task_combo.currentData() or {}

        for name, publish in sorted(data.get('publishes', {}).iteritems()):
            self._name_combo.addItem('%s (v%04d)' % (name, publish['sg_version']), {'name': name, 'publish': publish})
        self._name_combo.addItem('Create New Stream...', {'new': True})
        if was_new:
            self._name_combo.setCurrentIndex(self._name_combo.count() - 1)
        else:
            self._name_combo.setCurrentIndex(0)

    def _name_changed(self, index):
        data = self._name_combo.itemData(index)
        if not data:
            return
        self._name_field.setEnabled('new' in data)
        self._name_field.setText(data.get('name', self._basename))
        self._version_spinbox.setValue(data.get('publish', {}).get('sg_version', 0) + 1)

    def _on_name_edited(self):
        name = str(self._name_field.text())
        name = re.sub(r'\W+', '_', name).strip('_')
        self._name_field.setText(name)

    def _on_version_changed(self, new_value):
        data = self._name_combo.itemData(self._name_combo.currentIndex())
        if data.get('publish') and new_value != data['publish']['sg_version'] + 1 and not self._version_warning_issued:
            res = QtGui.QMessageBox.warning(None,
                "Manual Versions?",
                "Are you sure you want to change the version?\n"
                "The next one has already been selected for you...",
                QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel,
                QtGui.QMessageBox.Cancel
            )
            if res & QtGui.QMessageBox.Cancel:
                self._version_spinbox.setValue(data['publish']['sg_version'] + 1)
                return
            self._version_warning_issued = True

    def _on_movie_browse(self):

        existing = str(self._movie_path.text())

        dialog = QtGui.QFileDialog(None, "Select Movie or First Frame")
        dialog.setFilter('Movie or Frame (*.mov *.exr *.tif *.tiff *.jpg *.jpeg)')
        dialog.setFileMode(dialog.ExistingFile)
        dialog.setDirectory(os.path.dirname(existing) if existing else os.getcwd())
        if existing:
            dialog.selectFile(existing)

        if not dialog.exec_():
            return

        files = dialog.selectedFiles()
        path = str(files.First())
        self.setFrames(path)

    def setFrames(self, path):
        self._movie_path.setText(path)
        if path:
            self._promote_checkbox.setCheckState(Qt.Checked)

    def take_full_screenshot(self):
        pass

    def take_partial_screenshot(self, *args):
        path = tempfile.NamedTemporaryFile(suffix=".png", prefix="screenshot", delete=False).name

        self.beforeScreenshot.emit()

        if platform.system() == "Darwin":
            # use built-in screenshot command on the mac
            proc = subprocess.Popen(['screencapture', '-mis', path])
        else:
            proc = subprocess.Popen(['import', path])
        proc.wait()

        self.afterScreenshot.emit()

        if os.stat(path).st_size:
            self.setThumbnail(path)

    def setThumbnail(self, path):
        self._thumbnail_path = path
        pixmap = QtGui.QPixmap(path).scaled(200, 100, Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self._thumbnail_canvas.setPixmap(pixmap)
        self._thumbnail_canvas.setFixedSize(pixmap.size())

    def name(self):
        data = self._name_combo.currentData()
        return data.get('name', str(self._name_field.text()))

    def description(self):
        return str(self._description.toPlainText())

    def version(self):
        return self._version_spinbox.value()

    def thumbnail_path(self):
        return self._thumbnail_path

    def _path_is_image(self, path):
        if os.path.splitext(path)[1][1:].lower() in (
            'jpg', 'jpeg', 'tif', 'tiff', 'exr',
        ):
            return path

    def frames_path(self):
        path = str(self._movie_path.text())
        if path and self._path_is_image(path):
            return path
        return None

    def movie_path(self):
        path = str(self._movie_path.text())
        if path and not self._path_is_image(path):
            return path
        return None

    def safety_check(self, **kwargs):

        # Colliding names.
        data = self._name_combo.currentData()
        if 'name' not in data and str(self._name_field.text()) in self._existing_names:
            QtGui.QMessageBox.critical(self,
                "Name Collision",
                "You cannot create a new stream with the same name as an"
                " existing one. Please select the existing stream or enter a"
                " unique name.",
            )
            # Fatal.
            return False

        # Promoting to version without a movie.
        if self._promote_checkbox.isChecked() and not (self.frames_path() or self.movie_path()):
            QtGui.QMessageBox.critical(self,
                "Review Version Without Movie",
                "You cannot promote a publish for review without frames or a"
                " movie.",
            )
            # Fatal.
            return False

        # Promoting to version without a timelog.
        if self._promote_checkbox.isChecked() and not self._timelog_spinbox.value():
            res = QtGui.QMessageBox.warning(self,
                "Version without Time Log",
                "Are you sure that this version did not take you any time?",
                QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                QtGui.QMessageBox.No,
            )
            if res & QtGui.QMessageBox.No:
                return False

        return True

    def export(self, **kwargs):
        with ticket_ui_context():
            return self.__export(kwargs)

    def __export(self, kwargs):

        if not self.safety_check(**kwargs):
            return

        task_data = self._task_combo.currentData()
        task = task_data.get('task')
        if not task:
            sgfs = SGFS()
            tasks = sgfs.entities_from_path(self._exporter.workspace, 'Task')
            if not tasks:
                raise ValueError('Could not find SGFS tagged entities')
            task = tasks[0]

        stream_data = self._name_combo.currentData()
        parent = stream_data.get('publish')

        publisher = self._exporter.publish(task,
            name=self.name(),
            description=self.description(),
            version=self.version(),
            parent=parent,
            thumbnail_path=self.thumbnail_path(),
            frames_path=self.frames_path(),
            movie_path=self.movie_path(),
            export_kwargs=kwargs,
        )

        if self._promote_checkbox.isChecked():
            # progress.setLabelText('Creating Version for Review...')
            promotion_fields = self._exporter.fields_for_review_version(publisher, **kwargs)
            print "PROMOTE", promotion_fields
            publisher.promote_for_review(**promotion_fields)

        # Create the timelog.
        minutes = self._timelog_spinbox.value()
        if minutes:
            # progress.setLabelText('Logging time...')
            publisher.sgfs.session.create('TimeLog', {
                'project': publisher.entity.project(),
                'entity': publisher.link,
                'user': publisher.sgfs.session.guess_user(),
                'duration': minutes,
                'description': '%s_v%04d' % (publisher.name, publisher.version),
                'date': datetime.datetime.utcnow().date(),
            })

        # progress.hide()

        return publisher

<|endoftext|>