<commit_before>'''
@date 2014-12-17
@author Hong-She Liang <starofrainnight@gmail.com>
'''

from selenium.common.exceptions import *
from selenium.webdriver import *
from rabird.core.configparser import ConfigParser
from multiprocessing import Queue
from rabird.core.exceptions import *
from six.moves import queue
import sys
import os
import os.path
import time
import traceback
import threading

class WatchDog(object):
    (FEED, STOP) = range(0, 2)

    def __init__(self):
        self.__queue = Queue()
        # Default timeout value set to 3 minute, if we does not have any
        # action at 3 minutes, we terminate the monitoring process
        self.__timeout = 3 * 60.0

    @property
    def timeout(self):
        """Get current timeout value."""
        return self.__timeout

    @timeout.setter
    def timeout(self, value):
        """Set current timeout value"""
        self.__timeout = value

    def _internal_daemon(self, process):
        process.join()

        # Force watcher exit queue waitting...
        self.feed()

    def watch(self, process):

        # Daemon thread to force watch loop exit after process exited.
        daemon_thread = threading.Thread(target=self._internal_daemon, args=[process])
        daemon_thread.start()

        last_formatted_stack = [None]
        while True:
            # Process normal exited, we exit too
            if not process.is_alive():
                break

            try:
                # Feeder enter message needs not timeout value, we just wait until an
                # enter command
                item = self.__queue.get(True, self.__timeout)

                if item[0] == self.STOP:
                    # Successed to stop watchdog ...
                    break

                # Command format :
                # [self.FEED, formatted_stack]
                last_formatted_stack = item[1]

            except queue.Empty:
                try:
                    # Ignored all exceptions during terminate, so that
                    # we could raise our timeout error exception.
                    process.terminate()
                except:
                    pass
                raise TimeoutError(''.join(last_formatted_stack[:-1]))

    def feed(self):
        self.__queue.put([self.FEED, traceback.format_stack()])

    def stop(self):
        self.__queue.put([self.STOP])

def switch_to_frame(self, frame):
    '''
    Added support switch to a frame where guide by the path.

    @param frame_path An array that contained frame ids how we could get to the
    final frame.
    '''
    if isinstance(frame, list):

        # First we should switch to frame window, the first element of frame
        # path is the window's handle!
        frame_window = frame[0]
        self.switch_to_window(frame_window)
        self.switch_to_default_content()

        frame_path = frame[1:]
        for frame in frame_path:
            self._old_switch_to_frame(frame)
    else:
        self._old_switch_to_frame(frame)

    return self

def force_get(self, url):
    '''
    Loads a web page in the current browser session.

    The differents between get() and force_get() is force_get() will stop
    the page loading process after page load timeout happened and
    pretend the page already loaded. And force_get() will also ignored
    all popup windows that stop next page loading.

    @warning: If you want this method to works as expected, you must set the
    correct page load timeout value by set_page_load_timeout() before
    using. Otherwise, it works just like the get() and block there
    infinite.
    '''
    try:
        # Ignore all popup windows and force to load the url.
        self.execute_script("window.onbeforeunload = function(e){};")
        self.get(url)
    except TimeoutException as e:
        # Stop the page loading if timeout already happened.
        self.execute_script("window.stop()")

    return self

def get_xpath_wait_timeout(self):
    """
    Get xpath wait timeout value, default to 30 seconds
    """

    if "_xpath_wait_timeout" in self.__dict__:
        return self._xpath_wait_timeout
    else:
        return 30.0

def set_xpath_wait_timeout(self, timeout):
    self._xpath_wait_timeout = timeout

def set_watchdog(self, watchdog):
    self._watchdog = watchdog

def get_watchdog(self):
    if hasattr(self, "_watchdog"):
        return self._watchdog

    return None

def execute(self, driver_command, params=None):
    '''
    All commands will pass to this function. So we just need to feed the
    watchdog here to avoid doing execution infinite here ...
    '''
    if self.get_watchdog() is not None:
        self.get_watchdog().feed()

    return self._old_execute(driver_command, params)

def get_chrome_default_arguments():
    options = ChromeOptions()
    options.add_argument("--user-data-dir=%s" % os.path.normpath(os.path.expanduser("~/.config/chromium/Default")))
    return {"chrome_options":options}

def get_firefox_default_arguments():
    if sys.platform == "win32":
        firefox_config_path = os.path.normpath(os.path.expandvars("$APPDATA/Mozilla/Firefox/profiles.ini"))
    else:
        firefox_config_path = os.path.normpath(os.path.expanduser("~/.mozilla/firefox/profiles.ini"))

    config = ConfigParser()
    config.readfp(open(firefox_config_path))
    profile_path = ""
    for section_name in config.sections():
        if not section_name.startswith("Profile"):
            continue

        if (config.has_option(section_name, "Default") and
            (int(config.get(section_name, "Default")) == 1)):
            if config.get(section_name, "IsRelative") == 1:
                profile_path = os.path.join(
                    os.path.dirname(firefox_config_path),
                    config.get(section_name, "Path"))
            else:
                profile_path = config.get(section_name, "Path")

            break

    profile = FirefoxProfile(profile_path)
    return {"firefox_profile":profile}

def set_recommend_preferences(self):
    # A page load behavior use 30 seconds at maximum
    self.set_page_load_timeout(30)
    self.set_window_position(0, 0)
    self.set_window_size(800, 600)
    # Command timeout value be setted to 30 seconds
    self.command_executor.set_timeout(15)
    self.set_xpath_wait_timeout(20)<commit_msg>Fixed it always exit the watchdog looping even if process exited abnormally<commit_after>'''
@date 2014-12-17
@author Hong-She Liang <starofrainnight@gmail.com>
'''

from selenium.common.exceptions import *
from selenium.webdriver import *
from rabird.core.configparser import ConfigParser
from multiprocessing import Queue
from rabird.core.exceptions import *
from six.moves import queue
import sys
import os
import os.path
import time
import traceback
import threading

class WatchDog(object):
    (FEED, STOP) = range(0, 2)

    def __init__(self):
        self.__queue = Queue()
        # Default timeout value set to 3 minute, if we does not have any
        # action at 3 minutes, we terminate the monitoring process
        self.__timeout = 3 * 60.0

    @property
    def timeout(self):
        """Get current timeout value."""
        return self.__timeout

    @timeout.setter
    def timeout(self, value):
        """Set current timeout value"""
        self.__timeout = value

    def _internal_daemon(self, process):
        process.join()

        # Force watcher exit queue waitting...
        self.feed()

    def watch(self, process):

        # Daemon thread to force watch loop exit after process exited.
        daemon_thread = threading.Thread(target=self._internal_daemon, args=[process])
        daemon_thread.start()

        last_formatted_stack = [None]
        while True:
            # Process exited, we continue loop until it send STOP command
            if not process.is_alive():
                time.sleep(3) # Wait for a while
                continue

            try:
                # Feeder enter message needs not timeout value, we just wait until an
                # enter command
                item = self.__queue.get(True, self.__timeout)

                if item[0] == self.STOP:
                    # Successed to stop watchdog ...
                    break

                # Command format :
                # [self.FEED, formatted_stack]
                last_formatted_stack = item[1]

            except queue.Empty:
                try:
                    # Ignored all exceptions during terminate, so that
                    # we could raise our timeout error exception.
                    process.terminate()
                except:
                    pass
                raise TimeoutError(''.join(last_formatted_stack[:-1]))

    def feed(self):
        self.__queue.put([self.FEED, traceback.format_stack()])

    def stop(self):
        self.__queue.put([self.STOP])

def switch_to_frame(self, frame):
    '''
    Added support switch to a frame where guide by the path.

    @param frame_path An array that contained frame ids how we could get to the
    final frame.
    '''
    if isinstance(frame, list):

        # First we should switch to frame window, the first element of frame
        # path is the window's handle!
        frame_window = frame[0]
        self.switch_to_window(frame_window)
        self.switch_to_default_content()

        frame_path = frame[1:]
        for frame in frame_path:
            self._old_switch_to_frame(frame)
    else:
        self._old_switch_to_frame(frame)

    return self

def force_get(self, url):
    '''
    Loads a web page in the current browser session.

    The differents between get() and force_get() is force_get() will stop
    the page loading process after page load timeout happened and
    pretend the page already loaded. And force_get() will also ignored
    all popup windows that stop next page loading.

    @warning: If you want this method to works as expected, you must set the
    correct page load timeout value by set_page_load_timeout() before
    using. Otherwise, it works just like the get() and block there
    infinite.
    '''
    try:
        # Ignore all popup windows and force to load the url.
        self.execute_script("window.onbeforeunload = function(e){};")
        self.get(url)
    except TimeoutException as e:
        # Stop the page loading if timeout already happened.
        self.execute_script("window.stop()")

    return self

def get_xpath_wait_timeout(self):
    """
    Get xpath wait timeout value, default to 30 seconds
    """

    if "_xpath_wait_timeout" in self.__dict__:
        return self._xpath_wait_timeout
    else:
        return 30.0

def set_xpath_wait_timeout(self, timeout):
    self._xpath_wait_timeout = timeout

def set_watchdog(self, watchdog):
    self._watchdog = watchdog

def get_watchdog(self):
    if hasattr(self, "_watchdog"):
        return self._watchdog

    return None

def execute(self, driver_command, params=None):
    '''
    All commands will pass to this function. So we just need to feed the
    watchdog here to avoid doing execution infinite here ...
    '''
    if self.get_watchdog() is not None:
        self.get_watchdog().feed()

    return self._old_execute(driver_command, params)

def get_chrome_default_arguments():
    options = ChromeOptions()
    options.add_argument("--user-data-dir=%s" % os.path.normpath(os.path.expanduser("~/.config/chromium/Default")))
    return {"chrome_options":options}

def get_firefox_default_arguments():
    if sys.platform == "win32":
        firefox_config_path = os.path.normpath(os.path.expandvars("$APPDATA/Mozilla/Firefox/profiles.ini"))
    else:
        firefox_config_path = os.path.normpath(os.path.expanduser("~/.mozilla/firefox/profiles.ini"))

    config = ConfigParser()
    config.readfp(open(firefox_config_path))
    profile_path = ""
    for section_name in config.sections():
        if not section_name.startswith("Profile"):
            continue

        if (config.has_option(section_name, "Default") and
            (int(config.get(section_name, "Default")) == 1)):
            if config.get(section_name, "IsRelative") == 1:
                profile_path = os.path.join(
                    os.path.dirname(firefox_config_path),
                    config.get(section_name, "Path"))
            else:
                profile_path = config.get(section_name, "Path")

            break

    profile = FirefoxProfile(profile_path)
    return {"firefox_profile":profile}

def set_recommend_preferences(self):
    # A page load behavior use 30 seconds at maximum
    self.set_page_load_timeout(30)
    self.set_window_position(0, 0)
    self.set_window_size(800, 600)
    # Command timeout value be setted to 30 seconds
    self.command_executor.set_timeout(15)
    self.set_xpath_wait_timeout(20)<|endoftext|>


<commit_before>import os
import dj_database_url

from secert_key import *

here = lambda * x: os.path.join(os.path.abspath(os.path.dirname(__file__)), *x)

PROJECT_ROOT = here("..")
# root() gives us file paths from the root of the system to whatever
# folder(s) we pass it starting at the parent directory of the current file.
root = lambda * x: os.path.join(os.path.abspath(PROJECT_ROOT), *x)

DEBUG = True
TEMPLATE_DEBUG = True

ADMINS = (
    # ('Your Name', 'your_email@example.com'),
)

AUTH_USER_MODEL = "profiles.SurmandlUser"

MANAGERS = ADMINS

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'surmandlsite',                      # Or path to database file if using sqlite3.
        # The following settings are not used with sqlite3:
        'USER': '',
        'PASSWORD': '',
        'HOST': 'localhost',                      # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
        'PORT': '',                      # Set to empty string for default.
    }
}

ALLOWED_HOSTS = ['*']

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'America/New_York'

LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True

# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True

# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/var/www/example.com/media/"
MEDIA_ROOT = root("..", "uploads")

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://example.com/media/", "http://media.example.com/"
MEDIA_URL = '/media/'

# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/var/www/example.com/static/"
STATIC_ROOT = root("..", "static")

# URL prefix for static files.
# Example: "http://example.com/static/", "http://static.example.com/"
STATIC_URL = '/static/'

# Additional locations of static files
STATICFILES_DIRS = (root("..", "assets"),
    # Put strings here, like "/home/html/static" or "C:/www/django/static".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)

SECRET_KEY = generate_secret_key()

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    # Uncomment the next line for simple clickjacking protection:
    'django.middleware.clickjacking.XFrameOptionsMiddleware',

)

ROOT_URLCONF = 'teamsurmandl.urls'

WSGI_APPLICATION = 'teamsurmandl.wsgi.application'

TEMPLATE_DIRS = (root("templates"),
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

DJANGO_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'crispy_forms',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

THIRD_PARTY_APPS = ('south', 'imagekit', 'debug_toolbar', 'memcache_status', 'taggit',)

LOCAL_APPS = ('profiles','blog',)

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

LOGIN_URL = '/'
LOGIN_REDIRECT_URL = '/home/'
LOGOUT_URL = '/logout/'

# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}

CRISPY_TEMPLATE_PACK = 'bootstrap'

# Parse database configuration from $DATABASE_URL
DATABASES['default'] = dj_database_url.config(default='postgres://localhost/surmandlsite')

# Honor the 'X-Forwarded-Proto' header for request.is_secure()
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Allow all host headers
ALLOWED_HOSTS = ['*']

TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.core.context_processors.request',
'django.contrib.messages.context_processors.messages',
)<commit_msg>Added django-extensions!<commit_after>import os
import dj_database_url

from secert_key import *

here = lambda * x: os.path.join(os.path.abspath(os.path.dirname(__file__)), *x)

PROJECT_ROOT = here("..")
# root() gives us file paths from the root of the system to whatever
# folder(s) we pass it starting at the parent directory of the current file.
root = lambda * x: os.path.join(os.path.abspath(PROJECT_ROOT), *x)

DEBUG = True
TEMPLATE_DEBUG = True

ADMINS = (
    # ('Your Name', 'your_email@example.com'),
)

AUTH_USER_MODEL = "profiles.SurmandlUser"

MANAGERS = ADMINS

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'surmandlsite',                      # Or path to database file if using sqlite3.
        # The following settings are not used with sqlite3:
        'USER': '',
        'PASSWORD': '',
        'HOST': 'localhost',                      # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
        'PORT': '',                      # Set to empty string for default.
    }
}

ALLOWED_HOSTS = ['*']

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'America/New_York'

LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True

# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True

# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/var/www/example.com/media/"
MEDIA_ROOT = root("..", "uploads")

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://example.com/media/", "http://media.example.com/"
MEDIA_URL = '/media/'

# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/var/www/example.com/static/"
STATIC_ROOT = root("..", "static")

# URL prefix for static files.
# Example: "http://example.com/static/", "http://static.example.com/"
STATIC_URL = '/static/'

# Additional locations of static files
STATICFILES_DIRS = (root("..", "assets"),
    # Put strings here, like "/home/html/static" or "C:/www/django/static".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)

SECRET_KEY = generate_secret_key()

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    # Uncomment the next line for simple clickjacking protection:
    'django.middleware.clickjacking.XFrameOptionsMiddleware',

)

ROOT_URLCONF = 'teamsurmandl.urls'

WSGI_APPLICATION = 'teamsurmandl.wsgi.application'

TEMPLATE_DIRS = (root("templates"),
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

DJANGO_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'crispy_forms',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',
)

THIRD_PARTY_APPS = ('south', 'imagekit', 'debug_toolbar', 'memcache_status', 'taggit', 'django_extensions',)

LOCAL_APPS = ('profiles','blog',)

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

LOGIN_URL = '/'
LOGIN_REDIRECT_URL = '/home/'
LOGOUT_URL = '/logout/'

# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler'
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}

CRISPY_TEMPLATE_PACK = 'bootstrap'

# Parse database configuration from $DATABASE_URL
DATABASES['default'] = dj_database_url.config(default='postgres://localhost/surmandlsite')

# Honor the 'X-Forwarded-Proto' header for request.is_secure()
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Allow all host headers
ALLOWED_HOSTS = ['*']

TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.core.context_processors.request',
'django.contrib.messages.context_processors.messages',
)<|endoftext|>


<commit_before>import datetime
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import *
from django import forms

from corehq.apps.accounting.models import *

class BillingAccountForm(forms.Form):
    name = forms.CharField(label="Name")
    salesforce_account_id = forms.CharField(label="Salesforce ID")
    currency = forms.ChoiceField(label="Currency")

    billing_account_admins = forms.CharField()
    first_name = forms.CharField(label='First Name')
    last_name = forms.CharField(label='Last Name')
    company_name = forms.CharField(label='Company Name')
    phone_number = forms.CharField(label='Phone Number')
    address_line_1 = forms.CharField(label='Address Line 1')
    address_line_2 = forms.CharField(label='Address Line 2')
    city = forms.CharField()
    region = forms.CharField(label="State/Province/Region")
    postal_code = forms.CharField(label="Postal Code")
    country = forms.CharField()

    def __init__(self, account, *args, **kwargs):
        if account is not None:
            contact_info, _ = BillingContactInfo.objects.get_or_create(account=account)
            kwargs['initial'] = {'name': account.name,
                                 'salesforce_account_id': account.salesforce_account_id,
                                 'currency': account.currency.code,
                                 'billing_account_admins':
                                     ', '.join([admin.web_user for admin in account.billing_admins.all()]),
                                 'first_name': contact_info.first_name,
                                 'last_name': contact_info.last_name,
                                 'company_name': contact_info.company_name,
                                 'phone_number': contact_info.phone_number,
                                 'address_line_1': contact_info.first_line,
                                 'address_line_2': contact_info.second_line,
                                 'city': contact_info.city,
                                 'region': contact_info.state_province_region,
                                 'postal_code': contact_info.postal_code,
                                 'country': contact_info.country,
                                 }
        super(BillingAccountForm, self).__init__(*args, **kwargs)
        self.fields['currency'].choices =\
            [(cur.code, cur.code) for cur in Currency.objects.order_by('code')]
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Fieldset(
            'Basic Information',
                'name',
                'salesforce_account_id',
                'currency',
            ),
            Fieldset(
            'Contact Information',
                'billing_account_admins',
                'first_name',
                'last_name',
                'company_name',
                'phone_number',
                'address_line_1',
                'address_line_2',
                'city',
                'region',
                'postal_code',
                'country',
            ) if account is not None else None,
            FormActions(
                ButtonHolder(
                    Submit('submit', 'Update Account' if account is not None else 'Add New Account')
                )
            )
        )

class SubscriptionForm(forms.Form):
    start_date = forms.DateField(label="Start Date", widget=forms.DateInput())
    end_date = forms.DateField(label="End Date", widget=forms.DateInput())
    delay_invoice_until = forms.DateField(label="Delay Invoice Until", widget=forms.DateInput())

    def __init__(self, subscription, *args, **kwargs):
        super(SubscriptionForm, self).__init__(*args, **kwargs)

        css_class = dict(css_class='date-picker')
        disabled = dict(disabled='disabled')

        start_date_kwargs = dict(**css_class)
        end_date_kwargs = dict(**css_class)
        delay_invoice_until_kwargs = dict(**css_class)

        if subscription is not None:
            self.fields['start_date'].initial = subscription.date_start
            self.fields['end_date'].initial = subscription.date_end
            self.fields['delay_invoice_until'].initial = subscription.date_delay_invoicing
            if subscription.date_start is not None \
                and subscription.date_start <= datetime.date.today():
                start_date_kwargs.update(disabled)
            if subscription.date_end is not None \
                and subscription.date_end <= datetime.date.today():
                end_date_kwargs.update(disabled)
            if subscription.date_delay_invoicing is not None \
                and subscription.date_delay_invoicing <= datetime.date.today():
                delay_invoice_until_kwargs.update(disabled)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Fieldset(
            '%s Subscription' % ('Edit' if subscription is not None else 'New'),
                Field('start_date', **start_date_kwargs),
                Field('end_date', **end_date_kwargs),
                Field('delay_invoice_until', **delay_invoice_until_kwargs),
            ),
            FormActions(
                ButtonHolder(
                    Submit('set_subscription', 'Submit')
                )
            )
        )

class CreditForm(forms.Form):
    amount = forms.DecimalField()
    note = forms.CharField()
    rate_type = forms.ChoiceField(choices=(('Any', 'Any'),
                                           ('Product', 'Product'),
                                           ('Feature', 'Feature')))
    product = forms.ChoiceField()
    feature = forms.ChoiceField(label="Rate")

    def __init__(self, id, is_account, *args, **kwargs):
        super(CreditForm, self).__init__(*args, **kwargs)
        self.fields['product'].choices = self.get_product_choices(id, is_account)
        self.fields['feature'].choices = self.get_feature_choices(id, is_account)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Fieldset(
            'Adjust %s Credit' % 'Account Level' if is_account else 'Subscription Level',
                'amount',
                Div('note', data_bind="visible: false"),
                'rate_type',
                'product',
                'feature',
            ),
            FormActions(
                ButtonHolder(
                    Submit('adjust_credit', 'Submit')
                )
            )
        )

    def get_subscriptions(self, id, is_account):
        return Subscription.objects.filter(account=BillingAccount.objects.get(id=id))\
            if is_account else [Subscription.objects.get(id=id)]

    def get_product_choices(self, id, is_account):
        subscriptions = self.get_subscriptions(id, is_account)
        product_rate_sets = [sub.plan.product_rates for sub in subscriptions]
        products = set()
        for product_rate_set in product_rate_sets:
            for product_rate in product_rate_set.all():
                products.add(product_rate.product)
        return [(product.id, product.name) for product in products]

    def get_feature_choices(self, id, is_account):
        subscriptions = self.get_subscriptions(id, is_account)
        feature_rate_sets = [sub.plan.feature_rates for sub in subscriptions]
        features = set()
        for feature_rate_set in feature_rate_sets:
            for feature_rate in feature_rate_set.all():
                features.add(feature_rate.feature)
        return [(feature.id, feature.name) for feature in features]

class CancelForm(forms.Form):

    def __init__(self, *args, **kwargs):
        super(CancelForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            ButtonHolder(
                Submit('cancel_subscription', 'CANCEL SUBSCRIPTION')
            )
        )
<commit_msg>New Billing Acccount should show default currency<commit_after>import datetime
from crispy_forms.bootstrap import FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import *
from django import forms

from corehq.apps.accounting.models import *

class BillingAccountForm(forms.Form):
    name = forms.CharField(label="Name")
    salesforce_account_id = forms.CharField(label="Salesforce ID")
    currency = forms.ChoiceField(label="Currency")

    billing_account_admins = forms.CharField()
    first_name = forms.CharField(label='First Name')
    last_name = forms.CharField(label='Last Name')
    company_name = forms.CharField(label='Company Name')
    phone_number = forms.CharField(label='Phone Number')
    address_line_1 = forms.CharField(label='Address Line 1')
    address_line_2 = forms.CharField(label='Address Line 2')
    city = forms.CharField()
    region = forms.CharField(label="State/Province/Region")
    postal_code = forms.CharField(label="Postal Code")
    country = forms.CharField()

    def __init__(self, account, *args, **kwargs):
        if account is not None:
            contact_info, _ = BillingContactInfo.objects.get_or_create(account=account)
            kwargs['initial'] = {'name': account.name,
                                 'salesforce_account_id': account.salesforce_account_id,
                                 'currency': account.currency.code,
                                 'billing_account_admins':
                                     ', '.join([admin.web_user for admin in account.billing_admins.all()]),
                                 'first_name': contact_info.first_name,
                                 'last_name': contact_info.last_name,
                                 'company_name': contact_info.company_name,
                                 'phone_number': contact_info.phone_number,
                                 'address_line_1': contact_info.first_line,
                                 'address_line_2': contact_info.second_line,
                                 'city': contact_info.city,
                                 'region': contact_info.state_province_region,
                                 'postal_code': contact_info.postal_code,
                                 'country': contact_info.country,
                                 }
        else:
            kwargs['initial'] = {'currency': Currency.get_default().code,
                                 }
        super(BillingAccountForm, self).__init__(*args, **kwargs)
        self.fields['currency'].choices =\
            [(cur.code, cur.code) for cur in Currency.objects.order_by('code')]
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Fieldset(
            'Basic Information',
                'name',
                'salesforce_account_id',
                'currency',
            ),
            Fieldset(
            'Contact Information',
                'billing_account_admins',
                'first_name',
                'last_name',
                'company_name',
                'phone_number',
                'address_line_1',
                'address_line_2',
                'city',
                'region',
                'postal_code',
                'country',
            ) if account is not None else None,
            FormActions(
                ButtonHolder(
                    Submit('submit', 'Update Account' if account is not None else 'Add New Account')
                )
            )
        )

class SubscriptionForm(forms.Form):
    start_date = forms.DateField(label="Start Date", widget=forms.DateInput())
    end_date = forms.DateField(label="End Date", widget=forms.DateInput())
    delay_invoice_until = forms.DateField(label="Delay Invoice Until", widget=forms.DateInput())

    def __init__(self, subscription, *args, **kwargs):
        super(SubscriptionForm, self).__init__(*args, **kwargs)

        css_class = dict(css_class='date-picker')
        disabled = dict(disabled='disabled')

        start_date_kwargs = dict(**css_class)
        end_date_kwargs = dict(**css_class)
        delay_invoice_until_kwargs = dict(**css_class)

        if subscription is not None:
            self.fields['start_date'].initial = subscription.date_start
            self.fields['end_date'].initial = subscription.date_end
            self.fields['delay_invoice_until'].initial = subscription.date_delay_invoicing
            if subscription.date_start is not None \
                and subscription.date_start <= datetime.date.today():
                start_date_kwargs.update(disabled)
            if subscription.date_end is not None \
                and subscription.date_end <= datetime.date.today():
                end_date_kwargs.update(disabled)
            if subscription.date_delay_invoicing is not None \
                and subscription.date_delay_invoicing <= datetime.date.today():
                delay_invoice_until_kwargs.update(disabled)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Fieldset(
            '%s Subscription' % ('Edit' if subscription is not None else 'New'),
                Field('start_date', **start_date_kwargs),
                Field('end_date', **end_date_kwargs),
                Field('delay_invoice_until', **delay_invoice_until_kwargs),
            ),
            FormActions(
                ButtonHolder(
                    Submit('set_subscription', 'Submit')
                )
            )
        )

class CreditForm(forms.Form):
    amount = forms.DecimalField()
    note = forms.CharField()
    rate_type = forms.ChoiceField(choices=(('Any', 'Any'),
                                           ('Product', 'Product'),
                                           ('Feature', 'Feature')))
    product = forms.ChoiceField()
    feature = forms.ChoiceField(label="Rate")

    def __init__(self, id, is_account, *args, **kwargs):
        super(CreditForm, self).__init__(*args, **kwargs)
        self.fields['product'].choices = self.get_product_choices(id, is_account)
        self.fields['feature'].choices = self.get_feature_choices(id, is_account)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Fieldset(
            'Adjust %s Credit' % 'Account Level' if is_account else 'Subscription Level',
                'amount',
                Div('note', data_bind="visible: false"),
                'rate_type',
                'product',
                'feature',
            ),
            FormActions(
                ButtonHolder(
                    Submit('adjust_credit', 'Submit')
                )
            )
        )

    def get_subscriptions(self, id, is_account):
        return Subscription.objects.filter(account=BillingAccount.objects.get(id=id))\
            if is_account else [Subscription.objects.get(id=id)]

    def get_product_choices(self, id, is_account):
        subscriptions = self.get_subscriptions(id, is_account)
        product_rate_sets = [sub.plan.product_rates for sub in subscriptions]
        products = set()
        for product_rate_set in product_rate_sets:
            for product_rate in product_rate_set.all():
                products.add(product_rate.product)
        return [(product.id, product.name) for product in products]

    def get_feature_choices(self, id, is_account):
        subscriptions = self.get_subscriptions(id, is_account)
        feature_rate_sets = [sub.plan.feature_rates for sub in subscriptions]
        features = set()
        for feature_rate_set in feature_rate_sets:
            for feature_rate in feature_rate_set.all():
                features.add(feature_rate.feature)
        return [(feature.id, feature.name) for feature in features]

class CancelForm(forms.Form):

    def __init__(self, *args, **kwargs):
        super(CancelForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            ButtonHolder(
                Submit('cancel_subscription', 'CANCEL SUBSCRIPTION')
            )
        )
<|endoftext|>


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

"""This module defines and implements the Kernel class.
"""

__author__    = "Ole Weider <ole.weidner@rutgers.edu>"
__copyright__ = "Copyright 2014, http://radical.rutgers.edu"
__license__   = "MIT"

from radical.ensemblemd.engine import Engine
from radical.ensemblemd.exceptions import TypeError

# ------------------------------------------------------------------------------
#
class Kernel(object):

    #---------------------------------------------------------------------------
    #
    def __init__(self, name, args=None,instance_type=None):
        """Create a new Kernel object.
        """
        if type(name) != str:
            raise TypeError(
                expected_type=str,
                actual_type=type(name))

        self._engine = Engine()
        self._kernel = self._engine.get_kernel_plugin(name)

        if args is not None:
            self.set_args(args)

        if (instance_type == 'single'):
            self._kernel.instance_type = 'single'
        else:
            self._kernel.instance_type = 'multiple'

    #---------------------------------------------------------------------------
    #
    """
    ANTONS: COMMENTED OUT FOR NOW, HAVE NO IDEA WHAT THIS IS SUPPOSED TO DO!
    @property
    def _cu_def_pre_exec(self):

        pre_exec = list()

        # Translate upload directives into cURL command(s)
        if self._kernel._download_input_data is not None:
            for download in self._kernel._download_input_data:

                # see if a rename is requested
                dl = download.split(">")
                if len(dl) == 1:
                    # no rename
                     cmd = "curl --insecure -O {0}".format(dl[0].strip())
                elif len(dl) == 2:
                     cmd = "curl --insecure -L {0} -o {1}".format(dl[0].strip(), dl[1].strip())
                else:
                    # error
                    raise Exception("Invalid transfer directive %s" % download)

                pre_exec.append(cmd)

        # Removed other directives since you are using RP directives in the execution plugin

        # Add existing pre-exec.
        if self._kernel._pre_exec is not None:
            pre_exec.extend(self._kernel._pre_exec)

        return pre_exec
    """

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_pre_exec(self):
        return self._kernel._pre_exec

    #---------------------------------------------------------------------------
    #
    @property
    def pre_exec(self):
        return self._kernel._pre_exec

    @pre_exec.setter
    def pre_exec(self, commands):
        self._kernel._pre_exec = commands

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_post_exec(self):
        return self._kernel._post_exec

    #---------------------------------------------------------------------------
    #
    @property
    def post_exec(self):
        return self._kernel._post_exec

    @post_exec.setter
    def post_exec(self, commands):
        self._kernel._post_exec = commands

    #---------------------------------------------------------------------------
    #
    @property
    def subname(self):
        return self._kernel._subname

    @subname.setter
    def subname(self, name):
        self._kernel._subname = name

    #---------------------------------------------------------------------------
    #
    @property
    def name(self):
        return self._kernel.get_name()

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_output_data(self):
        return self._kernel._download_output_data

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_input_data(self):
        return self._kernel._upload_input_data

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_executable(self):
        return self._kernel._executable

    #---------------------------------------------------------------------------
    #
    @property
    def environment(self):
        return self._kernel._environment

    #---------------------------------------------------------------------------
    #
    @property
    def uses_mpi(self):
        return self._kernel._uses_mpi

    @uses_mpi.setter
    def uses_mpi(self, uses_mpi):

        if type(uses_mpi) != bool:
            raise TypeError(
                expected_type=bool,
                actual_type=type(uses_mpi))

        # Call the validate_args() method of the plug-in.
        self._kernel._uses_mpi = uses_mpi

    #---------------------------------------------------------------------------
    #
    @property
    def arguments(self):
        return self._kernel._arguments

    @arguments.setter
    def arguments(self, args):
        """Sets the arguments for the kernel.
        """
        if type(args) != list:
            raise TypeError(
                expected_type=list,
                actual_type=type(args))

        # Call the validate_args() method of the plug-in.
        self._kernel.validate_args(args)

    #---------------------------------------------------------------------------
    #
    @property
    def cores(self):
        """The number of cores the kernel is using.
        """
        return self._kernel._cores

    @cores.setter
    def cores(self, cores):

        if type(cores) != int:
            raise TypeError(
                expected_type=int,
                actual_type=type(cores))

        # Call the validate_args() method of the plug-in.
        self._kernel._cores = cores

    #---------------------------------------------------------------------------
    #
    @property
    def upload_input_data(self):
        """Instructs the application to upload one or more files or directories
           from the host the script is running on into the kernel's
           execution directory.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.upload_input_data = ["/location/on/HOST/RUNNING/THE/SCRIPT/data.txt > input.txt"]
        """
        return self._kernel._upload_input_data

    @upload_input_data.setter
    def upload_input_data(self, data_directives):
        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._upload_input_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def download_input_data(self):
        """Instructs the kernel to download one or more files or directories
           from a remote HTTP server into the kernel's execution directory.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.download_input_data = ["http://REMOTE.WEBSERVER/location/data.txt > input.txt"]

           .. note:: Supported URL types are ``http://`` and ``https://``.

        """
        return self._kernel._download_input_data

    @download_input_data.setter
    def download_input_data(self, data_directives):

        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._download_input_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def link_input_data(self):
        """Instructs the kernel to create a link to one or more files or
           directories on the execution host's filesystem in the kernel's
           execution directory.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.link_input_data = ["/location/on/EXECUTION/HOST/data.txt > input.txt"]
        """
        return self._kernel._link_input_data

    @link_input_data.setter
    def link_input_data(self, data_directives):
        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._link_input_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def download_output_data(self):
        """Instructs the application to download one or more files or directories
           from the kernel's execution directory back to the
           host the script is running on.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.download_output_data = ["output.txt > output-run-1.txt"]
        """
        return self._kernel._download_output_data

    @download_output_data.setter
    def download_output_data(self, data_directives):

        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._download_output_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def copy_input_data(self):
        """Instructs the kernel to copy one or more files or directories from
           the execution host's filesystem into the kernel's execution
           directory.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.copy_input_data = ["/location/on/EXECUTION/HOST/data.txt > input.txt"]

        """
        return self._kernel._copy_input_data

    @copy_input_data.setter
    def copy_input_data(self, data_directives):

        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            dd = str(dd)
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._copy_input_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def copy_output_data(self):
        """Instructs the application to copy one or more files or directories
           from the kernel's execution directory to a directory on the
           execution host.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.copy_output_data = ["output.txt > /home/me/results/result1.txt"]
        """
        return self._kernel._copy_output_data

    @download_output_data.setter
    def copy_output_data(self, data_directives):

        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._copy_output_data = data_directives

    #---------------------------------------------------------------------------
    #
    def get_raw_args(self):
        """Returns the arguments  passed to the kernel.
        """
        return self._kernel.get_arg(name)

    #---------------------------------------------------------------------------
    #
    def get_arg(self, name):
        """Returns the value of the kernel argument given by 'arg_name'.
        """
        return self._kernel.get_arg(name)

    #---------------------------------------------------------------------------
    #
    def get_instance_type(self):
        """Returns the instance_type of the kernel. It can be 'single' or
        'multiple'
        """
        return self._kernel.instance_type

    #---------------------------------------------------------------------------
    #
    def _bind_to_resource(self, resource_key, pattern_name=None):
        """Returns the kernel description as a dictionary that can be
           translated into a CU description.
        """
        if (pattern_name == None):
            self._kernel._bind_to_resource(resource_key)
        else:
            self._kernel._bind_to_resource(resource_key, pattern_name)
        return self._kernel
<commit_msg>incorrect setter<commit_after>#!/usr/bin/env python

"""This module defines and implements the Kernel class.
"""

__author__    = "Ole Weider <ole.weidner@rutgers.edu>"
__copyright__ = "Copyright 2014, http://radical.rutgers.edu"
__license__   = "MIT"

from radical.ensemblemd.engine import Engine
from radical.ensemblemd.exceptions import TypeError

# ------------------------------------------------------------------------------
#
class Kernel(object):

    #---------------------------------------------------------------------------
    #
    def __init__(self, name, args=None,instance_type=None):
        """Create a new Kernel object.
        """
        if type(name) != str:
            raise TypeError(
                expected_type=str,
                actual_type=type(name))

        self._engine = Engine()
        self._kernel = self._engine.get_kernel_plugin(name)

        if args is not None:
            self.set_args(args)

        if (instance_type == 'single'):
            self._kernel.instance_type = 'single'
        else:
            self._kernel.instance_type = 'multiple'

    #---------------------------------------------------------------------------
    #
    """
    ANTONS: COMMENTED OUT FOR NOW, HAVE NO IDEA WHAT THIS IS SUPPOSED TO DO!
    @property
    def _cu_def_pre_exec(self):

        pre_exec = list()

        # Translate upload directives into cURL command(s)
        if self._kernel._download_input_data is not None:
            for download in self._kernel._download_input_data:

                # see if a rename is requested
                dl = download.split(">")
                if len(dl) == 1:
                    # no rename
                     cmd = "curl --insecure -O {0}".format(dl[0].strip())
                elif len(dl) == 2:
                     cmd = "curl --insecure -L {0} -o {1}".format(dl[0].strip(), dl[1].strip())
                else:
                    # error
                    raise Exception("Invalid transfer directive %s" % download)

                pre_exec.append(cmd)

        # Removed other directives since you are using RP directives in the execution plugin

        # Add existing pre-exec.
        if self._kernel._pre_exec is not None:
            pre_exec.extend(self._kernel._pre_exec)

        return pre_exec
    """

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_pre_exec(self):
        return self._kernel._pre_exec

    #---------------------------------------------------------------------------
    #
    @property
    def pre_exec(self):
        return self._kernel._pre_exec

    @pre_exec.setter
    def pre_exec(self, commands):
        self._kernel._pre_exec = commands

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_post_exec(self):
        return self._kernel._post_exec

    #---------------------------------------------------------------------------
    #
    @property
    def post_exec(self):
        return self._kernel._post_exec

    @post_exec.setter
    def post_exec(self, commands):
        self._kernel._post_exec = commands

    #---------------------------------------------------------------------------
    #
    @property
    def subname(self):
        return self._kernel._subname

    @subname.setter
    def subname(self, name):
        self._kernel._subname = name

    #---------------------------------------------------------------------------
    #
    @property
    def name(self):
        return self._kernel.get_name()

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_output_data(self):
        return self._kernel._download_output_data

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_input_data(self):
        return self._kernel._upload_input_data

    #---------------------------------------------------------------------------
    #
    @property
    def _cu_def_executable(self):
        return self._kernel._executable

    #---------------------------------------------------------------------------
    #
    @property
    def environment(self):
        return self._kernel._environment

    #---------------------------------------------------------------------------
    #
    @property
    def uses_mpi(self):
        return self._kernel._uses_mpi

    @uses_mpi.setter
    def uses_mpi(self, uses_mpi):

        if type(uses_mpi) != bool:
            raise TypeError(
                expected_type=bool,
                actual_type=type(uses_mpi))

        # Call the validate_args() method of the plug-in.
        self._kernel._uses_mpi = uses_mpi

    #---------------------------------------------------------------------------
    #
    @property
    def arguments(self):
        return self._kernel._arguments

    @arguments.setter
    def arguments(self, args):
        """Sets the arguments for the kernel.
        """
        if type(args) != list:
            raise TypeError(
                expected_type=list,
                actual_type=type(args))

        # Call the validate_args() method of the plug-in.
        self._kernel.validate_args(args)

    #---------------------------------------------------------------------------
    #
    @property
    def cores(self):
        """The number of cores the kernel is using.
        """
        return self._kernel._cores

    @cores.setter
    def cores(self, cores):

        if type(cores) != int:
            raise TypeError(
                expected_type=int,
                actual_type=type(cores))

        # Call the validate_args() method of the plug-in.
        self._kernel._cores = cores

    #---------------------------------------------------------------------------
    #
    @property
    def upload_input_data(self):
        """Instructs the application to upload one or more files or directories
           from the host the script is running on into the kernel's
           execution directory.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.upload_input_data = ["/location/on/HOST/RUNNING/THE/SCRIPT/data.txt > input.txt"]
        """
        return self._kernel._upload_input_data

    @upload_input_data.setter
    def upload_input_data(self, data_directives):
        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._upload_input_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def download_input_data(self):
        """Instructs the kernel to download one or more files or directories
           from a remote HTTP server into the kernel's execution directory.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.download_input_data = ["http://REMOTE.WEBSERVER/location/data.txt > input.txt"]

           .. note:: Supported URL types are ``http://`` and ``https://``.

        """
        return self._kernel._download_input_data

    @download_input_data.setter
    def download_input_data(self, data_directives):

        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._download_input_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def link_input_data(self):
        """Instructs the kernel to create a link to one or more files or
           directories on the execution host's filesystem in the kernel's
           execution directory.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.link_input_data = ["/location/on/EXECUTION/HOST/data.txt > input.txt"]
        """
        return self._kernel._link_input_data

    @link_input_data.setter
    def link_input_data(self, data_directives):
        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._link_input_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def download_output_data(self):
        """Instructs the application to download one or more files or directories
           from the kernel's execution directory back to the
           host the script is running on.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.download_output_data = ["output.txt > output-run-1.txt"]
        """
        return self._kernel._download_output_data

    @download_output_data.setter
    def download_output_data(self, data_directives):

        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._download_output_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def copy_input_data(self):
        """Instructs the kernel to copy one or more files or directories from
           the execution host's filesystem into the kernel's execution
           directory.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.copy_input_data = ["/location/on/EXECUTION/HOST/data.txt > input.txt"]

        """
        return self._kernel._copy_input_data

    @copy_input_data.setter
    def copy_input_data(self, data_directives):

        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            dd = str(dd)
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._copy_input_data = data_directives

    #---------------------------------------------------------------------------
    #
    @property
    def copy_output_data(self):
        """Instructs the application to copy one or more files or directories
           from the kernel's execution directory to a directory on the
           execution host.

           Example::

                k = Kernel(name="misc.ccount")
                k.arguments = ["--inputfile=input.txt", "--outputfile=output.txt"]
                k.copy_output_data = ["output.txt > /home/me/results/result1.txt"]
        """
        return self._kernel._copy_output_data

    @copy_output_data.setter
    def copy_output_data(self, data_directives):

        if type(data_directives) != list:
            data_directives = [data_directives]

        for dd in data_directives:
            if type(dd) != str:
                raise TypeError(
                    expected_type=str,
                    actual_type=type(dd))

        self._kernel._copy_output_data = data_directives

    #---------------------------------------------------------------------------
    #
    def get_raw_args(self):
        """Returns the arguments  passed to the kernel.
        """
        return self._kernel.get_arg(name)

    #---------------------------------------------------------------------------
    #
    def get_arg(self, name):
        """Returns the value of the kernel argument given by 'arg_name'.
        """
        return self._kernel.get_arg(name)

    #---------------------------------------------------------------------------
    #
    def get_instance_type(self):
        """Returns the instance_type of the kernel. It can be 'single' or
        'multiple'
        """
        return self._kernel.instance_type

    #---------------------------------------------------------------------------
    #
    def _bind_to_resource(self, resource_key, pattern_name=None):
        """Returns the kernel description as a dictionary that can be
           translated into a CU description.
        """
        if (pattern_name == None):
            self._kernel._bind_to_resource(resource_key)
        else:
            self._kernel._bind_to_resource(resource_key, pattern_name)
        return self._kernel
<|endoftext|>


<commit_before>from __future__ import absolute_import
from casexml.apps.case.mock import CaseBlock
from casexml.apps.case.xml import V2
import uuid
from xml.etree import ElementTree
from corehq.apps.hqcase.utils import submit_case_blocks, get_case_by_domain_hq_user_id
from couchdbkit.exceptions import MultipleResultsFound
from corehq.elastic import es_query
from ctable.models import SqlExtractMapping, ColumnDef, KeyMatcher, NOT_EQUAL

MAPPING_NAME_CASES = 'cc_case_updates'
MAPPING_NAME_CASE_OWNERSHIP = 'cc_case_ownership'

def sync_user_cases(commcare_user):
    domain = commcare_user.project
    if not (domain and domain.call_center_config.enabled):
        return

    # language or phone_number can be null and will break
    # case submission
    fields = {
        'name': commcare_user.name or commcare_user.raw_username,
        'email': commcare_user.email,
        'language': commcare_user.language or '',
        'phone_number': commcare_user.phone_number or ''
    }
    # fields comes second to prevent custom user data overriding
    fields = dict(commcare_user.user_data, **fields)

    try:
        case = get_case_by_domain_hq_user_id(domain.name, commcare_user._id, include_docs=True)
        found = bool(case)
    except MultipleResultsFound:
        return

    close = commcare_user.to_be_deleted() or not commcare_user.is_active

    owner_id = domain.call_center_config.case_owner_id
    if found:
        caseblock = CaseBlock(
            create=False,
            case_id=case._id,
            version=V2,
            owner_id=owner_id,
            case_type=domain.call_center_config.case_type,
            close=close,
            update=fields
        )
    else:
        fields['hq_user_id'] = commcare_user._id
        caseblock = CaseBlock(
            create=True,
            case_id=uuid.uuid4().hex,
            owner_id=owner_id,
            user_id=owner_id,
            version=V2,
            case_type=domain.call_center_config.case_type,
            update=fields
        )

    casexml = ElementTree.tostring(caseblock.as_xml())
    submit_case_blocks(casexml, domain.name)

def bootstrap_callcenter(domain):
    if not (domain and domain.name and domain.call_center_config.enabled):
        return

    get_case_mapping(domain.name).save()
    get_case_ownership_mapping(domain.name).save()

def get_case_mapping(domain):
    mapping = get_or_create_mapping(domain, MAPPING_NAME_CASES)

    mapping.couch_view = 'callcenter/case_actions_by_user'
    mapping.couch_key_prefix = [domain]
    mapping.columns = [
        ColumnDef(name="date", data_type="date", value_source="key", value_index=1,
                  date_format="%Y-%m-%dT%H:%M:%SZ"),
        ColumnDef(name="user_id", data_type="string", value_source="key", value_index=2),
        ColumnDef(name="case_type", data_type="string", value_source="key", value_index=3),
        ColumnDef(name="action_type", data_type="string", value_source="key", value_index=4),
        ColumnDef(name="case_id", data_type="string", value_source="key", value_index=5),
        ColumnDef(name="action_count", data_type="integer", value_source="value"),
    ]
    return mapping

def get_case_ownership_mapping(domain):
    mapping = get_or_create_mapping(domain, MAPPING_NAME_CASE_OWNERSHIP, date_range=None)

    mapping.couch_view = 'case/by_date_modified_owner'
    mapping.couch_key_prefix = [domain]
    mapping.couch_group_level = 4
    mapping.columns = [
        ColumnDef(name="user_id", data_type="string", value_source="key", value_index=3),
        ColumnDef(name="case_type", data_type="string", value_source="key", value_index=2),
        ColumnDef(name="open_cases", data_type="integer", value_source="value",
                  match_keys=[KeyMatcher(index=1, value='open'),
                              KeyMatcher(index=2, value='{}', operator=NOT_EQUAL)]),
        ColumnDef(name="closed_cases", data_type="integer", value_source="value",
                  match_keys=[KeyMatcher(index=1, value='closed'),
                              KeyMatcher(index=2, value='{}', operator=NOT_EQUAL)]),
    ]
    return mapping

def get_or_create_mapping(domain, mapping_name, date_range=2):
    mapping = SqlExtractMapping.by_name(domain, mapping_name)
    if not mapping:
        mapping = SqlExtractMapping()

    mapping.auto_generated = True
    mapping.domains = [domain]
    mapping.name = mapping_name
    mapping.active = True
    mapping.couch_date_range = date_range
    mapping.couch_view_params = {}
    mapping.schedule_type = 'hourly'
    mapping.schedule_hour = -1
    mapping.schedule_day = -1

    return mapping

def get_call_center_domains():
    q = {'fields': ['name']}
    result = es_query(params={
        'internal.using_call_center': True,
        'is_active': True,
        'is_snapshot': False
    }, q=q)
    hits = result.get('hits', {}).get('hits', {})
    return [hit['fields']['name'] for hit in hits]
<commit_msg>only update user case if there are changes<commit_after>from __future__ import absolute_import
from casexml.apps.case.mock import CaseBlock
from casexml.apps.case.xml import V2
import uuid
from xml.etree import ElementTree
from corehq.apps.hqcase.utils import submit_case_blocks, get_case_by_domain_hq_user_id
from couchdbkit.exceptions import MultipleResultsFound
from corehq.elastic import es_query
from ctable.models import SqlExtractMapping, ColumnDef, KeyMatcher, NOT_EQUAL

MAPPING_NAME_CASES = 'cc_case_updates'
MAPPING_NAME_CASE_OWNERSHIP = 'cc_case_ownership'

def sync_user_cases(commcare_user):
    domain = commcare_user.project
    if not (domain and domain.call_center_config.enabled):
        return

    # language or phone_number can be null and will break
    # case submission
    fields = {
        'name': commcare_user.name or commcare_user.raw_username,
        'email': commcare_user.email,
        'language': commcare_user.language or '',
        'phone_number': commcare_user.phone_number or ''
    }
    # fields comes second to prevent custom user data overriding
    fields = dict(commcare_user.user_data, **fields)

    try:
        case = get_case_by_domain_hq_user_id(domain.name, commcare_user._id, include_docs=True)
        found = bool(case)
    except MultipleResultsFound:
        return

    close = commcare_user.to_be_deleted() or not commcare_user.is_active

    owner_id = domain.call_center_config.case_owner_id
    if found:
        props = dict(case.dynamic_case_properties())

        changed = close != case.closed
        changed = changed or case.owner_id != owner_id
        changed = changed or case.type != domain.call_center_config.case_type
        changed = changed or case.name != fields['name']

        if not changed:
            for field, value in fields.items():
                if field != 'name' and props.get(field) != value:
                    changed = True
                    break

        if changed:
            caseblock = CaseBlock(
                create=False,
                case_id=case._id,
                version=V2,
                owner_id=owner_id,
                case_type=domain.call_center_config.case_type,
                close=close,
                update=fields
            )
    else:
        fields['hq_user_id'] = commcare_user._id
        caseblock = CaseBlock(
            create=True,
            case_id=uuid.uuid4().hex,
            owner_id=owner_id,
            user_id=owner_id,
            version=V2,
            case_type=domain.call_center_config.case_type,
            update=fields
        )

    if caseblock:
        casexml = ElementTree.tostring(caseblock.as_xml())
        submit_case_blocks(casexml, domain.name)

def bootstrap_callcenter(domain):
    if not (domain and domain.name and domain.call_center_config.enabled):
        return

    get_case_mapping(domain.name).save()
    get_case_ownership_mapping(domain.name).save()

def get_case_mapping(domain):
    mapping = get_or_create_mapping(domain, MAPPING_NAME_CASES)

    mapping.couch_view = 'callcenter/case_actions_by_user'
    mapping.couch_key_prefix = [domain]
    mapping.columns = [
        ColumnDef(name="date", data_type="date", value_source="key", value_index=1,
                  date_format="%Y-%m-%dT%H:%M:%SZ"),
        ColumnDef(name="user_id", data_type="string", value_source="key", value_index=2),
        ColumnDef(name="case_type", data_type="string", value_source="key", value_index=3),
        ColumnDef(name="action_type", data_type="string", value_source="key", value_index=4),
        ColumnDef(name="case_id", data_type="string", value_source="key", value_index=5),
        ColumnDef(name="action_count", data_type="integer", value_source="value"),
    ]
    return mapping

def get_case_ownership_mapping(domain):
    mapping = get_or_create_mapping(domain, MAPPING_NAME_CASE_OWNERSHIP, date_range=None)

    mapping.couch_view = 'case/by_date_modified_owner'
    mapping.couch_key_prefix = [domain]
    mapping.couch_group_level = 4
    mapping.columns = [
        ColumnDef(name="user_id", data_type="string", value_source="key", value_index=3),
        ColumnDef(name="case_type", data_type="string", value_source="key", value_index=2),
        ColumnDef(name="open_cases", data_type="integer", value_source="value",
                  match_keys=[KeyMatcher(index=1, value='open'),
                              KeyMatcher(index=2, value='{}', operator=NOT_EQUAL)]),
        ColumnDef(name="closed_cases", data_type="integer", value_source="value",
                  match_keys=[KeyMatcher(index=1, value='closed'),
                              KeyMatcher(index=2, value='{}', operator=NOT_EQUAL)]),
    ]
    return mapping

def get_or_create_mapping(domain, mapping_name, date_range=2):
    mapping = SqlExtractMapping.by_name(domain, mapping_name)
    if not mapping:
        mapping = SqlExtractMapping()

    mapping.auto_generated = True
    mapping.domains = [domain]
    mapping.name = mapping_name
    mapping.active = True
    mapping.couch_date_range = date_range
    mapping.couch_view_params = {}
    mapping.schedule_type = 'hourly'
    mapping.schedule_hour = -1
    mapping.schedule_day = -1

    return mapping

def get_call_center_domains():
    q = {'fields': ['name']}
    result = es_query(params={
        'internal.using_call_center': True,
        'is_active': True,
        'is_snapshot': False
    }, q=q)
    hits = result.get('hits', {}).get('hits', {})
    return [hit['fields']['name'] for hit in hits]
<|endoftext|>


<commit_before>"""
Metrics collection
******************

.. contents::
   :local:

This package exposes functions and utilities to record metrics in CommCare. These metrics
are exported / exposed to the configured metrics providers. Supported providers are:

    * Datadog
    * Prometheus

Providers are enabled using the `METRICS_PROVIDER` setting. Multiple providers can be
enabled concurrently:

::

    METRICS_PROVIDERS = [
        'corehq.util.metrics.prometheus.PrometheusMetrics',
        'corehq.util.metrics.datadog.DatadogMetrics',
    ]

If no metrics providers are configured CommCare will log all metrics to the `commcare.metrics` logger
at the DEBUG level.

Metric tagging
==============
Metrics may be tagged by passing a dictionary of tag names and values. Tags should be used
to add dimensions to a metric e.g. request type, response status.

Tags should not originate from unbounded sources or sources with high dimensionality such as
timestamps, user IDs, request IDs etc. Ideally a tag should not have more than 10 possible values.

Read more about tagging:

* https://prometheus.io/docs/practices/naming/#labels
* https://docs.datadoghq.com/tagging/

Metric Types
============

Counter metric
''''''''''''''

A counter is a cumulative metric that represents a single monotonically increasing counter
whose value can only increase or be reset to zero on restart. For example, you can use a
counter to represent the number of requests served, tasks completed, or errors.

Do not use a counter to expose a value that can decrease. For example, do not use a counter
for the number of currently running processes; instead use a gauge.

::

    metrics_counter('commcare.case_import.count', 1, tags={'domain': domain})

Gauge metric
''''''''''''

A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.

Gauges are typically used for measured values like temperatures or current memory usage,
but also "counts" that can go up and down, like the number of concurrent requests.

::

    metrics_gauge('commcare.case_import.queue_length', queue_length)

For regular reporting of a gauge metric there is the `metrics_gauge_task` function:

.. autofunction:: corehq.util.metrics.metrics_gauge_task

Histogram metric
''''''''''''''''

A histogram samples observations (usually things like request durations or response sizes)
and counts them in configurable buckets.

::

    metrics_histogram(
        'commcare.case_import.duration', timer_duration,
        bucket_tag='size', buckets=[10, 50, 200, 1000], bucket_unit='s',
        tags={'domain': domain}
    )

Histograms are recorded differently in the different providers.

.. automethod:: corehq.util.metrics.datadog.DatadogMetrics._histogram

.. automethod:: corehq.util.metrics.prometheus.PrometheusMetrics._histogram

Utilities
=========

.. autofunction:: corehq.util.metrics.create_metrics_event

.. autofunction:: corehq.util.metrics.metrics_gauge_task

.. autofunction:: corehq.util.metrics.metrics_histogram_timer

.. autoclass:: corehq.util.metrics.metrics_track_errors

Other Notes
===========

* All metrics must use the prefix 'commcare.'
"""
from contextlib import ContextDecorator
from functools import wraps
from typing import Iterable, Callable, Dict

from celery.task import periodic_task

from django.conf import settings
from sentry_sdk import add_breadcrumb

from corehq.util.timer import TimingContext
from dimagi.utils.modules import to_function
from .const import COMMON_TAGS, ALERT_INFO, MPM_ALL
from .metrics import (
    DebugMetrics,
    DelegatedMetrics,
    DEFAULT_BUCKETS,
    _enforce_prefix,
    metrics_logger
)
from .utils import make_buckets_from_timedeltas, DAY_SCALE_TIME_BUCKETS, bucket_value

__all__ = [
    'metrics_counter',
    'metrics_gauge',
    'metrics_histogram',
    'metrics_gauge_task',
    'create_metrics_event',
    'metrics_histogram_timer',
    'make_buckets_from_timedeltas',
    'DAY_SCALE_TIME_BUCKETS',
    'bucket_value',
]

def metrics_counter(name: str, value: float = 1, tags: Dict[str, str] = None, documentation: str = ''):
    provider = _get_metrics_provider()
    provider.counter(name, value, tags=tags, documentation=documentation)

def metrics_gauge(name: str, value: float, tags: Dict[str, str] = None, documentation: str = '',
                  multiprocess_mode: str = MPM_ALL):
    """
    kwargs:
        multiprocess_mode: See PrometheusMetrics._gauge for documentation. This is only passed
            to PrometheusMetrics since it is one of PrometheusMetrics.accepted_gauge_params
    """
    provider = _get_metrics_provider()
    provider.gauge(name, value, tags=tags, documentation=documentation, multiprocess_mode=multiprocess_mode)

def metrics_histogram(
        name: str, value: float,
        bucket_tag: str, buckets: Iterable[int] = DEFAULT_BUCKETS, bucket_unit: str = '',
        tags: Dict[str, str] = None, documentation: str = ''):
    provider = _get_metrics_provider()
    provider.histogram(
        name, value, bucket_tag,
        buckets=buckets, bucket_unit=bucket_unit, tags=tags, documentation=documentation
    )

def metrics_gauge_task(name, fn, run_every, multiprocess_mode=MPM_ALL):
    """
    Helper for easily registering gauges to run periodically

    To update a gauge on a schedule based on the result of a function
    just add to your app's tasks.py:

    ::

        my_calculation = metrics_gauge_task(
            'commcare.my.metric', my_calculation_function, run_every=crontab(minute=0)
        )

    kwargs:
        multiprocess_mode: See PrometheusMetrics._gauge for documentation.
    """
    _enforce_prefix(name, 'commcare')

    @periodic_task(serializer='pickle', queue='background_queue', run_every=run_every,
                   acks_late=True, ignore_result=True)
    @wraps(fn)
    def inner(*args, **kwargs):
        from corehq.util.metrics import metrics_gauge
        metrics_gauge(name, fn(*args, **kwargs), multiprocess_mode=multiprocess_mode)

    return inner

def create_metrics_event(title: str, text: str, alert_type: str = ALERT_INFO,
                         tags: Dict[str, str] = None, aggregation_key: str = None):
    """
    Send an event record to the monitoring provider.

    Currently only implemented by the Datadog provider.

    :param title: Title of the event
    :param text: Event body
    :param alert_type: Event type. One of 'success', 'info', 'warning', 'error'
    :param tags: Event tags
    :param aggregation_key: Key to use to group multiple events
    """
    tags = COMMON_TAGS.update(tags or {})
    try:
        _get_metrics_provider().create_event(title, text, tags, alert_type, aggregation_key)
    except Exception as e:
        metrics_logger.exception('Error creating metrics event', e)

def metrics_histogram_timer(metric: str, timing_buckets: Iterable[int], tags: Dict[str, str] = None,
                            bucket_tag: str = 'duration', callback: Callable = None):
    """
    Create a context manager that times and reports to the metric providers as a histogram

    Example Usage:

    ::

        timer = metrics_histogram_timer('commcare.some.special.metric', tags={
            'type': type,
        ], timing_buckets=(.001, .01, .1, 1, 10, 100))
        with timer:
            some_special_thing()

    This will result it a call to `metrics_histogram` with the timer value.

    Note: Histograms are implemented differently by each provider. See documentation for details.

    :param metric: Name of the metric (must start with 'commcare.')
    :param tags: metric tags to include
    :param timing_buckets: sequence of numbers representing time thresholds, in seconds
    :param bucket_tag: The name of the bucket tag to use (if used by the underlying provider)
    :param callback: a callable which will be called when exiting the context manager with a single argument
                     of the timer duratio
    :return: A context manager that will perform the specified timing
             and send the specified metric

    """
    timer = TimingContext()
    original_stop = timer.stop

    def new_stop(name=None):
        original_stop(name)
        if callback:
            callback(timer.duration)
        metrics_histogram(
            metric, timer.duration,
            bucket_tag=bucket_tag, buckets=timing_buckets, bucket_unit='s',
            tags=tags
        )
        timer_name = ".".join(metric.split('.')[1:])  # remove the 'commcare.' prefix
        add_breadcrumb(
            category="timing",
            message=f"{timer_name}: {timer.duration:0.3f}",
            level="info",
        )

    timer.stop = new_stop
    return timer

class metrics_track_errors(ContextDecorator):
    """Record when something succeeds or errors in the configured metrics provider

    Eg: This code will log to `commcare.myfunction.succeeded` when it completes
    successfully, and to `commcare.myfunction.failed` when an exception is
    raised.

    ::

        @metrics_track_errors('myfunction')
        def myfunction():
            pass
    """

    def __init__(self, name):
        self.succeeded_name = "commcare.{}.succeeded".format(name)
        self.failed_name = "commcare.{}.failed".format(name)

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not exc_type:
            metrics_counter(self.succeeded_name)
        else:
            metrics_counter(self.failed_name)

def push_metrics():
    provider = _get_metrics_provider()
    provider.push_metrics()

_metrics = []

def _get_metrics_provider():
    if not _metrics:
        _global_setup()
        providers = []
        for provider_path in settings.METRICS_PROVIDERS:
            provider = to_function(provider_path)()
            providers.append(provider)

        if not providers:
            metrics = DebugMetrics()
        elif len(providers) > 1:
            metrics = DelegatedMetrics(providers)
        else:
            metrics = providers[0]
        _metrics.append(metrics)
    return _metrics[-1]

def _global_setup():
    if settings.UNIT_TESTING or settings.DEBUG or 'ddtrace.contrib.django' not in settings.INSTALLED_APPS:
        try:
            from ddtrace import tracer
            tracer.enabled = False
            tracer.log.disabled = True
        except ImportError:
            pass
<commit_msg>safer timer name<commit_after>"""
Metrics collection
******************

.. contents::
   :local:

This package exposes functions and utilities to record metrics in CommCare. These metrics
are exported / exposed to the configured metrics providers. Supported providers are:

    * Datadog
    * Prometheus

Providers are enabled using the `METRICS_PROVIDER` setting. Multiple providers can be
enabled concurrently:

::

    METRICS_PROVIDERS = [
        'corehq.util.metrics.prometheus.PrometheusMetrics',
        'corehq.util.metrics.datadog.DatadogMetrics',
    ]

If no metrics providers are configured CommCare will log all metrics to the `commcare.metrics` logger
at the DEBUG level.

Metric tagging
==============
Metrics may be tagged by passing a dictionary of tag names and values. Tags should be used
to add dimensions to a metric e.g. request type, response status.

Tags should not originate from unbounded sources or sources with high dimensionality such as
timestamps, user IDs, request IDs etc. Ideally a tag should not have more than 10 possible values.

Read more about tagging:

* https://prometheus.io/docs/practices/naming/#labels
* https://docs.datadoghq.com/tagging/

Metric Types
============

Counter metric
''''''''''''''

A counter is a cumulative metric that represents a single monotonically increasing counter
whose value can only increase or be reset to zero on restart. For example, you can use a
counter to represent the number of requests served, tasks completed, or errors.

Do not use a counter to expose a value that can decrease. For example, do not use a counter
for the number of currently running processes; instead use a gauge.

::

    metrics_counter('commcare.case_import.count', 1, tags={'domain': domain})

Gauge metric
''''''''''''

A gauge is a metric that represents a single numerical value that can arbitrarily go up and down.

Gauges are typically used for measured values like temperatures or current memory usage,
but also "counts" that can go up and down, like the number of concurrent requests.

::

    metrics_gauge('commcare.case_import.queue_length', queue_length)

For regular reporting of a gauge metric there is the `metrics_gauge_task` function:

.. autofunction:: corehq.util.metrics.metrics_gauge_task

Histogram metric
''''''''''''''''

A histogram samples observations (usually things like request durations or response sizes)
and counts them in configurable buckets.

::

    metrics_histogram(
        'commcare.case_import.duration', timer_duration,
        bucket_tag='size', buckets=[10, 50, 200, 1000], bucket_unit='s',
        tags={'domain': domain}
    )

Histograms are recorded differently in the different providers.

.. automethod:: corehq.util.metrics.datadog.DatadogMetrics._histogram

.. automethod:: corehq.util.metrics.prometheus.PrometheusMetrics._histogram

Utilities
=========

.. autofunction:: corehq.util.metrics.create_metrics_event

.. autofunction:: corehq.util.metrics.metrics_gauge_task

.. autofunction:: corehq.util.metrics.metrics_histogram_timer

.. autoclass:: corehq.util.metrics.metrics_track_errors

Other Notes
===========

* All metrics must use the prefix 'commcare.'
"""
from contextlib import ContextDecorator
from functools import wraps
from typing import Iterable, Callable, Dict

from celery.task import periodic_task

from django.conf import settings
from sentry_sdk import add_breadcrumb

from corehq.util.timer import TimingContext
from dimagi.utils.modules import to_function
from .const import COMMON_TAGS, ALERT_INFO, MPM_ALL
from .metrics import (
    DebugMetrics,
    DelegatedMetrics,
    DEFAULT_BUCKETS,
    _enforce_prefix,
    metrics_logger
)
from .utils import make_buckets_from_timedeltas, DAY_SCALE_TIME_BUCKETS, bucket_value

__all__ = [
    'metrics_counter',
    'metrics_gauge',
    'metrics_histogram',
    'metrics_gauge_task',
    'create_metrics_event',
    'metrics_histogram_timer',
    'make_buckets_from_timedeltas',
    'DAY_SCALE_TIME_BUCKETS',
    'bucket_value',
]

def metrics_counter(name: str, value: float = 1, tags: Dict[str, str] = None, documentation: str = ''):
    provider = _get_metrics_provider()
    provider.counter(name, value, tags=tags, documentation=documentation)

def metrics_gauge(name: str, value: float, tags: Dict[str, str] = None, documentation: str = '',
                  multiprocess_mode: str = MPM_ALL):
    """
    kwargs:
        multiprocess_mode: See PrometheusMetrics._gauge for documentation. This is only passed
            to PrometheusMetrics since it is one of PrometheusMetrics.accepted_gauge_params
    """
    provider = _get_metrics_provider()
    provider.gauge(name, value, tags=tags, documentation=documentation, multiprocess_mode=multiprocess_mode)

def metrics_histogram(
        name: str, value: float,
        bucket_tag: str, buckets: Iterable[int] = DEFAULT_BUCKETS, bucket_unit: str = '',
        tags: Dict[str, str] = None, documentation: str = ''):
    provider = _get_metrics_provider()
    provider.histogram(
        name, value, bucket_tag,
        buckets=buckets, bucket_unit=bucket_unit, tags=tags, documentation=documentation
    )

def metrics_gauge_task(name, fn, run_every, multiprocess_mode=MPM_ALL):
    """
    Helper for easily registering gauges to run periodically

    To update a gauge on a schedule based on the result of a function
    just add to your app's tasks.py:

    ::

        my_calculation = metrics_gauge_task(
            'commcare.my.metric', my_calculation_function, run_every=crontab(minute=0)
        )

    kwargs:
        multiprocess_mode: See PrometheusMetrics._gauge for documentation.
    """
    _enforce_prefix(name, 'commcare')

    @periodic_task(serializer='pickle', queue='background_queue', run_every=run_every,
                   acks_late=True, ignore_result=True)
    @wraps(fn)
    def inner(*args, **kwargs):
        from corehq.util.metrics import metrics_gauge
        metrics_gauge(name, fn(*args, **kwargs), multiprocess_mode=multiprocess_mode)

    return inner

def create_metrics_event(title: str, text: str, alert_type: str = ALERT_INFO,
                         tags: Dict[str, str] = None, aggregation_key: str = None):
    """
    Send an event record to the monitoring provider.

    Currently only implemented by the Datadog provider.

    :param title: Title of the event
    :param text: Event body
    :param alert_type: Event type. One of 'success', 'info', 'warning', 'error'
    :param tags: Event tags
    :param aggregation_key: Key to use to group multiple events
    """
    tags = COMMON_TAGS.update(tags or {})
    try:
        _get_metrics_provider().create_event(title, text, tags, alert_type, aggregation_key)
    except Exception as e:
        metrics_logger.exception('Error creating metrics event', e)

def metrics_histogram_timer(metric: str, timing_buckets: Iterable[int], tags: Dict[str, str] = None,
                            bucket_tag: str = 'duration', callback: Callable = None):
    """
    Create a context manager that times and reports to the metric providers as a histogram

    Example Usage:

    ::

        timer = metrics_histogram_timer('commcare.some.special.metric', tags={
            'type': type,
        ], timing_buckets=(.001, .01, .1, 1, 10, 100))
        with timer:
            some_special_thing()

    This will result it a call to `metrics_histogram` with the timer value.

    Note: Histograms are implemented differently by each provider. See documentation for details.

    :param metric: Name of the metric (must start with 'commcare.')
    :param tags: metric tags to include
    :param timing_buckets: sequence of numbers representing time thresholds, in seconds
    :param bucket_tag: The name of the bucket tag to use (if used by the underlying provider)
    :param callback: a callable which will be called when exiting the context manager with a single argument
                     of the timer duratio
    :return: A context manager that will perform the specified timing
             and send the specified metric

    """
    timer = TimingContext()
    original_stop = timer.stop

    def new_stop(name=None):
        original_stop(name)
        if callback:
            callback(timer.duration)
        metrics_histogram(
            metric, timer.duration,
            bucket_tag=bucket_tag, buckets=timing_buckets, bucket_unit='s',
            tags=tags
        )
        timer_name = metric
        if metric.startswith('commcare.'):
            timer_name = ".".join(metric.split('.')[1:])  # remove the 'commcare.' prefix
        add_breadcrumb(
            category="timing",
            message=f"{timer_name}: {timer.duration:0.3f}",
            level="info",
        )

    timer.stop = new_stop
    return timer

class metrics_track_errors(ContextDecorator):
    """Record when something succeeds or errors in the configured metrics provider

    Eg: This code will log to `commcare.myfunction.succeeded` when it completes
    successfully, and to `commcare.myfunction.failed` when an exception is
    raised.

    ::

        @metrics_track_errors('myfunction')
        def myfunction():
            pass
    """

    def __init__(self, name):
        self.succeeded_name = "commcare.{}.succeeded".format(name)
        self.failed_name = "commcare.{}.failed".format(name)

    def __enter__(self):
        pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        if not exc_type:
            metrics_counter(self.succeeded_name)
        else:
            metrics_counter(self.failed_name)

def push_metrics():
    provider = _get_metrics_provider()
    provider.push_metrics()

_metrics = []

def _get_metrics_provider():
    if not _metrics:
        _global_setup()
        providers = []
        for provider_path in settings.METRICS_PROVIDERS:
            provider = to_function(provider_path)()
            providers.append(provider)

        if not providers:
            metrics = DebugMetrics()
        elif len(providers) > 1:
            metrics = DelegatedMetrics(providers)
        else:
            metrics = providers[0]
        _metrics.append(metrics)
    return _metrics[-1]

def _global_setup():
    if settings.UNIT_TESTING or settings.DEBUG or 'ddtrace.contrib.django' not in settings.INSTALLED_APPS:
        try:
            from ddtrace import tracer
            tracer.enabled = False
            tracer.log.disabled = True
        except ImportError:
            pass
<|endoftext|>