<commit_before>"""Helpers object, Buffet template plug-in, RequestLocal object, and Paste Template config

The util module provides the main Helper object used by Pylons, in addition
to the Buffet object which enables usage of template engines supporting the
template plug-in scheme TurboGears utilizes.

The RequestLocal thread-local is utilized by Pylons as the ``c`` object that
is available via ``pylons.c`` and is cleared every request by Pylons.

PylonsTemplate is a Paste Template sub-class that configures the source directory and default
plug-ins for a new Pylons project.
"""
import sys
import os.path, gettext
import pkg_resources

from paste.script.templates import Template

import pylons

from routes import threadinglocal
from paste.deploy.config import CONFIG

available_engines = {}

for entry_point in pkg_resources.iter_entry_points('python.templating.engines'):
    Engine = entry_point.load()
    available_engines[entry_point.name] = Engine

def get_prefix(environ):
    prefix = environ['paste.config']['app'].get('prefix')
    if not prefix:
        if environ.get('SCRIPT_NAME', '') != '':
            prefix = environ['SCRIPT_NAME']
    if not prefix:
        prefix = ''
    return prefix

def run_wsgi(app, m, request, environ=None):
    if environ == None:
        environ = request.environ

    def pylons_response(status, headers, exc_info=None):
        request.status = status
        if exc_info != None:
            raise exc_info
        request.headers_out.clear()
        for h,v in headers:
            if h.lower() == 'content-type':
                request.content_type=v
            else:
                request.headers_out[h] = v

    for data in app(environ, pylons_response):
        m.write(data)

class RequestLocal(object):
    """This object emulates a dict and supports the full set of dict functions and operations.

    Internally, the dict is attached to a threading local object and
    all access is passed through to the thread-safe object.

    This difference means that the object must be initialized per-thread
    with a _clear() call before the object can be used, and it should be
    _clear()'ed every request call.

    The RequestLocal object also support attribute assignment, which is
    then internally stored as if they used item assignment. Attribute
    get is also supported, and is used to 'get' the name requested. Unlike
    normal attribute access, this will return an empty string if the
    attribute does not exist.
    """
    def __init__(self):
        self.__dict__['_local'] = threadinglocal.local()

    def __getattr__(self, name):
        if name.startswith('_'):
            return object.__getattribute__(self, name)
        else:
            try:
                result = getattr(self._local.request, name)
            except AttributeError:
                result = self._local.request.get(name, '')
            return result

    def __setattr__(self, key, value):
        if key.startswith('_'):
            raise AttributeError("You cannot set attributes begining with '_' on the 'temp' object use temp['%s'] instead"%key)
            #object.__setattr__(self, key, value)
        else:
            self.__setitem__(key, value)

    def __len__(self):
        return self._local.request.__len__()

    def __getitem__(self, key):
        return self._local.request.__getitem__(key)

    def __setitem__(self, key, value):
        self._local.request.__setitem__(key, value)

    def __delitem__(self, key):
        self._local.request.__delitem__(key)

    def __iter__(self):
        return self._local.request.__iter__()

    def __contains__(self, item):
        return self._local.request.__contains__(item)

    def _clear(self):
        self._local.request = {}

    def __repr__(self):
        return self._local.request.__repr__()

class LanguageError(Exception):
    """Exception raised when a problem occurs with changing languages"""
    pass

class _Translator(object):
    """An empty gettext translator which just returns the original string"""
    def gettext(self, value):
        return value

class Helpers(object):
    def __init__(self, **_pylons):
        self.__dict__['_local'] = RequestLocal()
        self.__dict__['_pylons'] = _pylons

    def __call__(self):
        """Initialize Helpers object for request with helpers module/object

        When called, the Helpers object will return itself, after initializing
        itself for the current thread/request. It is intended to be run at the
        begginning of every request to clear the thread local it uses and setup
        the helpers space that will be used for fetching helper names as well
        as translation.
        """
        self.__dict__['_local']._clear()
        project_name = CONFIG['app']['package']
        try:
            helpers = getattr(getattr(__import__(project_name+'.config.helpers'), 'config'), 'helpers')
        except:
            helpers = getattr(getattr(__import__(project_name+'.lib.helpers'), 'lib'), 'helpers')
        self.__dict__['_local'].helpers = helpers
        self.__dict__['_local'].translator = _Translator()
        self.__dict__['_local'].config = CONFIG
        if CONFIG['app'].has_key('lang'):
            self.set_lang(CONFIG['app']['lang'])
        else:
            self.__dict__['_local'].lang = None
        return self

    def __getattr__(self, name):
        if hasattr(self.__dict__['_local'].helpers, name):
            return getattr(self.__dict__['_local'].helpers, name)
        elif name in self.__dict__['_local'].keys() and name != '_local' and len(str(name))>0 and str(name)[0] != '_':
            return getattr(self.__dict__['_local'],name)
        else:
            raise AttributeError('No such helper %s'%repr(name))

    def __setattr__(self, name, value):
        if name not in ['lang']:# or not self.__dict__['_local'].has_key(name):
            raise AttributeError("Helper attributes cannot be set. You should use the context object 'c' to store conext information.")
        else:
            self.set_lang(value)

    def log(self, msg):
        """Log a message to the output log."""
        self.__dict__['_pylons']['request'].environ['wsgi.errors'].write('=> %s\n'%str(msg))

    def translate(self, value):
        """Deprecated, use _()"""
        raise NotImplementedError('Use h._() instead')

    def _(self, value):
        """Mark a string for translation

        Mark a string to be internationalised as follows:

            h._('This should be in lots of langauges')
        """
        return self.__dict__['_local'].translator.gettext(value)

    def set_lang(self, lang):
        """Set the language used"""
        project_name = CONFIG['app']['package']
        self.__dict__['_local'].lang = lang
        if lang == None:
            self.__dict__['_local'].translator = _Translator()
        else:
            from pkg_resources import resource_string, resource_stream, resource_exists, resource_filename
            from pylons.i18n.translation import egg_translation
            if not resource_exists(project_name, 'i18n/%s/LC_MESSAGES'%(lang)):
                raise LanguageError(
                    'Langauge catalog %s not found'%repr(
                        '%s/i18n/%s/LC_MESSAGES'%(
                            project_name,
                            lang
                        )
                    )
                )
            self.__dict__['_local'].translator = egg_translation(
                project_name,
                lang = 'i18n/%s/LC_MESSAGES'%(
                    lang
                )
            )

    def get_lang(self):
        return self.__dict__['_local'].lang

class BuffetError(Exception):
    pass

class Buffet(object):
    """Buffet style plug-in template rendering

    Buffet implements template language plug-in support modeled highly on the
    `Buffet Project <http://projects.dowski.com/projects/buffet>`_ from which
    this class inherits its name.
    """
    def __init__(self):
        self._local = RequestLocal()

    def _clear(self):
        self._local._clear()

    def prepare(self, engine_name, template_root=None):
        """Prepare a template engine for use

        This method must be run every request before the `render <#render>`_ method
        is called so that the ``template_root`` can be set.
        """
        Engine = available_engines.get(engine_name, None)
        if not Engine:
            raise TemplateEngineMissing('Please install a plugin for "%s" to use its functionality' % engine_name)
        setattr(self._local, engine_name, dict(engine=Engine(), root=template_root))

    def render(self, engine_name, template_name, as_string=False, include_pylons_variables=True, namespace=None, **options):
        """Render a template using a template engine plug-in

        To use templates it is expected that you will attach data to be used in
        the template to the ``c`` variable which is available in the controller
        and the template.

        When porting code from other projects it is sometimes easier to use an
        exisitng dictionary which can be specified with ``namespace``.

        ``engine_name``
            The name of the template engine to use, which must be
            'prepared' first.
        ``template_name``
            Name of the template to render
        ``as_string``
            Whether or not to directly render the output to the browser, or
            return the rendered template as a string.
        ``include_pylons_variables``
            If a custom namespace is specified this determines whether Pylons
            variables are included in the namespace or not. Defaults to ``True``.
        ``namespace``
            A custom dictionary of names and values to be substituted in the template.
            If ``include_pylons_variables`` is ``True`` and any keys in ``namespace``
            conflict with names of Pylons variables, an error is raised.

        All other keyword options are passed directly to the template engine
        used.
        """
        for char in ['/','\\']:
            if char in template_name:
                raise BuffetError('Templates should be specified as module paths relative to the '
                'template root and therefore cannot contain %s characters'%repr(char))

        def update_namespace(namespace):
            d = {}
            for k,v in namespace.items():
                d[k] = v
            d.update(
                dict(
                    c=pylons.c,
                    h=pylons.h,
                    m=pylons.m,
                    request=pylons.request,
                    g=pylons.request.environ['pylons.g'],
                    session=pylons.session,
                )
            )
            return d

        if namespace==None:
            if include_pylons_variables == False:
                raise BuffetError('You must specify ``namespace`` if ``include_pylons_variables`` is False')
            else:
                namespace = update_namespace({})
        elif isinstance(namespace, dict):
            if include_pylons_variables == True:
                keys = namespace.keys()
                for k in ['c','m','h','g','request','session']:
                    if k in keys:
                        raise Exception('The variable %s specified in namespace conflicts '
                            'with the Pylons variable of the same name. Set ``include_pylons_variables`` '
                            'to ``False`` if you do not want to use Pylons variables in your template'%k)
                namespace = update_namespace(namespace)
        else:
            namespace = update_namespace(namespace)
        engine_config = getattr(self._local, engine_name)
        base_path = engine_config['root'].split('/')
        tmpl_path = template_name.split('/')
        full_path = os.path.join(*(base_path + tmpl_path))
        dotted_path = full_path.replace(os.path.sep, '.').lstrip('.')
        page_data = engine_config['engine'].render(namespace, template=dotted_path, **options)
        if as_string:
            return page_data
        return pylons.m.write(page_data)

class TemplateEngineMissing(Exception):
    pass

class PylonsTemplate(Template):
    _template_dir = 'templates/paster_template'
    summary = 'Pylons application template'
    egg_plugins = ['Pylons', 'WebHelpers']

__all__ = ['RequestLocal', 'Helpers', 'Buffet']
__pudge_all__ = ['RequestLocal', 'Helpers', 'Buffet', 'PylonsTemplate']
<commit_msg>[svn] Patched for helpers under stranger package names, thanks Matthew Scott!<commit_after>"""Helpers object, Buffet template plug-in, RequestLocal object, and Paste Template config

The util module provides the main Helper object used by Pylons, in addition
to the Buffet object which enables usage of template engines supporting the
template plug-in scheme TurboGears utilizes.

The RequestLocal thread-local is utilized by Pylons as the ``c`` object that
is available via ``pylons.c`` and is cleared every request by Pylons.

PylonsTemplate is a Paste Template sub-class that configures the source directory and default
plug-ins for a new Pylons project.
"""
import sys
import os.path, gettext
import pkg_resources

from paste.script.templates import Template

import pylons

from routes import threadinglocal
from paste.deploy.config import CONFIG

available_engines = {}

for entry_point in pkg_resources.iter_entry_points('python.templating.engines'):
    Engine = entry_point.load()
    available_engines[entry_point.name] = Engine

def get_prefix(environ):
    prefix = environ['paste.config']['app'].get('prefix')
    if not prefix:
        if environ.get('SCRIPT_NAME', '') != '':
            prefix = environ['SCRIPT_NAME']
    if not prefix:
        prefix = ''
    return prefix

def run_wsgi(app, m, request, environ=None):
    if environ == None:
        environ = request.environ

    def pylons_response(status, headers, exc_info=None):
        request.status = status
        if exc_info != None:
            raise exc_info
        request.headers_out.clear()
        for h,v in headers:
            if h.lower() == 'content-type':
                request.content_type=v
            else:
                request.headers_out[h] = v

    for data in app(environ, pylons_response):
        m.write(data)

class RequestLocal(object):
    """This object emulates a dict and supports the full set of dict functions and operations.

    Internally, the dict is attached to a threading local object and
    all access is passed through to the thread-safe object.

    This difference means that the object must be initialized per-thread
    with a _clear() call before the object can be used, and it should be
    _clear()'ed every request call.

    The RequestLocal object also support attribute assignment, which is
    then internally stored as if they used item assignment. Attribute
    get is also supported, and is used to 'get' the name requested. Unlike
    normal attribute access, this will return an empty string if the
    attribute does not exist.
    """
    def __init__(self):
        self.__dict__['_local'] = threadinglocal.local()

    def __getattr__(self, name):
        if name.startswith('_'):
            return object.__getattribute__(self, name)
        else:
            try:
                result = getattr(self._local.request, name)
            except AttributeError:
                result = self._local.request.get(name, '')
            return result

    def __setattr__(self, key, value):
        if key.startswith('_'):
            raise AttributeError("You cannot set attributes begining with '_' on the 'temp' object use temp['%s'] instead"%key)
            #object.__setattr__(self, key, value)
        else:
            self.__setitem__(key, value)

    def __len__(self):
        return self._local.request.__len__()

    def __getitem__(self, key):
        return self._local.request.__getitem__(key)

    def __setitem__(self, key, value):
        self._local.request.__setitem__(key, value)

    def __delitem__(self, key):
        self._local.request.__delitem__(key)

    def __iter__(self):
        return self._local.request.__iter__()

    def __contains__(self, item):
        return self._local.request.__contains__(item)

    def _clear(self):
        self._local.request = {}

    def __repr__(self):
        return self._local.request.__repr__()

class LanguageError(Exception):
    """Exception raised when a problem occurs with changing languages"""
    pass

class _Translator(object):
    """An empty gettext translator which just returns the original string"""
    def gettext(self, value):
        return value

class Helpers(object):
    def __init__(self, **_pylons):
        self.__dict__['_local'] = RequestLocal()
        self.__dict__['_pylons'] = _pylons

    def __call__(self):
        """Initialize Helpers object for request with helpers module/object

        When called, the Helpers object will return itself, after initializing
        itself for the current thread/request. It is intended to be run at the
        begginning of every request to clear the thread local it uses and setup
        the helpers space that will be used for fetching helper names as well
        as translation.
        """
        self.__dict__['_local']._clear()
        project_name = CONFIG['app']['package']
        try:
            helpers_name = project_name + '.config.helpers'
            __import__(helpers_name)
        except:
            helpers_name = project_name + '.lib.helpers'
            __import__(helpers_name)
        helpers = sys.modules[helpers_name]
        self.__dict__['_local'].helpers = helpers
        self.__dict__['_local'].translator = _Translator()
        self.__dict__['_local'].config = CONFIG
        if CONFIG['app'].has_key('lang'):
            self.set_lang(CONFIG['app']['lang'])
        else:
            self.__dict__['_local'].lang = None
        return self

    def __getattr__(self, name):
        if hasattr(self.__dict__['_local'].helpers, name):
            return getattr(self.__dict__['_local'].helpers, name)
        elif name in self.__dict__['_local'].keys() and name != '_local' and len(str(name))>0 and str(name)[0] != '_':
            return getattr(self.__dict__['_local'],name)
        else:
            raise AttributeError('No such helper %s'%repr(name))

    def __setattr__(self, name, value):
        if name not in ['lang']:# or not self.__dict__['_local'].has_key(name):
            raise AttributeError("Helper attributes cannot be set. You should use the context object 'c' to store conext information.")
        else:
            self.set_lang(value)

    def log(self, msg):
        """Log a message to the output log."""
        self.__dict__['_pylons']['request'].environ['wsgi.errors'].write('=> %s\n'%str(msg))

    def translate(self, value):
        """Deprecated, use _()"""
        raise NotImplementedError('Use h._() instead')

    def _(self, value):
        """Mark a string for translation

        Mark a string to be internationalised as follows:

            h._('This should be in lots of langauges')
        """
        return self.__dict__['_local'].translator.gettext(value)

    def set_lang(self, lang):
        """Set the language used"""
        project_name = CONFIG['app']['package']
        self.__dict__['_local'].lang = lang
        if lang == None:
            self.__dict__['_local'].translator = _Translator()
        else:
            from pkg_resources import resource_string, resource_stream, resource_exists, resource_filename
            from pylons.i18n.translation import egg_translation
            if not resource_exists(project_name, 'i18n/%s/LC_MESSAGES'%(lang)):
                raise LanguageError(
                    'Langauge catalog %s not found'%repr(
                        '%s/i18n/%s/LC_MESSAGES'%(
                            project_name,
                            lang
                        )
                    )
                )
            self.__dict__['_local'].translator = egg_translation(
                project_name,
                lang = 'i18n/%s/LC_MESSAGES'%(
                    lang
                )
            )

    def get_lang(self):
        return self.__dict__['_local'].lang

class BuffetError(Exception):
    pass

class Buffet(object):
    """Buffet style plug-in template rendering

    Buffet implements template language plug-in support modeled highly on the
    `Buffet Project <http://projects.dowski.com/projects/buffet>`_ from which
    this class inherits its name.
    """
    def __init__(self):
        self._local = RequestLocal()

    def _clear(self):
        self._local._clear()

    def prepare(self, engine_name, template_root=None):
        """Prepare a template engine for use

        This method must be run every request before the `render <#render>`_ method
        is called so that the ``template_root`` can be set.
        """
        Engine = available_engines.get(engine_name, None)
        if not Engine:
            raise TemplateEngineMissing('Please install a plugin for "%s" to use its functionality' % engine_name)
        setattr(self._local, engine_name, dict(engine=Engine(), root=template_root))

    def render(self, engine_name, template_name, as_string=False, include_pylons_variables=True, namespace=None, **options):
        """Render a template using a template engine plug-in

        To use templates it is expected that you will attach data to be used in
        the template to the ``c`` variable which is available in the controller
        and the template.

        When porting code from other projects it is sometimes easier to use an
        exisitng dictionary which can be specified with ``namespace``.

        ``engine_name``
            The name of the template engine to use, which must be
            'prepared' first.
        ``template_name``
            Name of the template to render
        ``as_string``
            Whether or not to directly render the output to the browser, or
            return the rendered template as a string.
        ``include_pylons_variables``
            If a custom namespace is specified this determines whether Pylons
            variables are included in the namespace or not. Defaults to ``True``.
        ``namespace``
            A custom dictionary of names and values to be substituted in the template.
            If ``include_pylons_variables`` is ``True`` and any keys in ``namespace``
            conflict with names of Pylons variables, an error is raised.

        All other keyword options are passed directly to the template engine
        used.
        """
        for char in ['/','\\']:
            if char in template_name:
                raise BuffetError('Templates should be specified as module paths relative to the '
                'template root and therefore cannot contain %s characters'%repr(char))

        def update_namespace(namespace):
            d = {}
            for k,v in namespace.items():
                d[k] = v
            d.update(
                dict(
                    c=pylons.c,
                    h=pylons.h,
                    m=pylons.m,
                    request=pylons.request,
                    g=pylons.request.environ['pylons.g'],
                    session=pylons.session,
                )
            )
            return d

        if namespace==None:
            if include_pylons_variables == False:
                raise BuffetError('You must specify ``namespace`` if ``include_pylons_variables`` is False')
            else:
                namespace = update_namespace({})
        elif isinstance(namespace, dict):
            if include_pylons_variables == True:
                keys = namespace.keys()
                for k in ['c','m','h','g','request','session']:
                    if k in keys:
                        raise Exception('The variable %s specified in namespace conflicts '
                            'with the Pylons variable of the same name. Set ``include_pylons_variables`` '
                            'to ``False`` if you do not want to use Pylons variables in your template'%k)
                namespace = update_namespace(namespace)
        else:
            namespace = update_namespace(namespace)
        engine_config = getattr(self._local, engine_name)
        base_path = engine_config['root'].split('/')
        tmpl_path = template_name.split('/')
        full_path = os.path.join(*(base_path + tmpl_path))
        dotted_path = full_path.replace(os.path.sep, '.').lstrip('.')
        page_data = engine_config['engine'].render(namespace, template=dotted_path, **options)
        if as_string:
            return page_data
        return pylons.m.write(page_data)

class TemplateEngineMissing(Exception):
    pass

class PylonsTemplate(Template):
    _template_dir = 'templates/paster_template'
    summary = 'Pylons application template'
    egg_plugins = ['Pylons', 'WebHelpers']

__all__ = ['RequestLocal', 'Helpers', 'Buffet']
__pudge_all__ = ['RequestLocal', 'Helpers', 'Buffet', 'PylonsTemplate']
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
"""
    PyLTI decorator implementation for flask framework
"""
from __future__ import absolute_import
from functools import wraps
import logging
import json

from flask import session, current_app, Flask
from flask import request as flask_request

from .common import (
    LTI_SESSION_KEY,
    LTI_PROPERTY_LIST,
    LTI_ROLES,
    verify_request_common,
    post_message,
    post_message2,
    generate_request_xml,
    LTIException,
    LTIRoleException,
    LTINotInSessionException,
    LTIPostMessageException
)

log = logging.getLogger('pylti.flask')  # pylint: disable=invalid-name

def default_error(exception=None):
    """Render simple error page.  This should be overidden in applications."""
    # pylint: disable=unused-argument
    return "There was an LTI communication error", 500

class LTI(object):
    """
    LTI Object represents abstraction of current LTI session. It provides
    callback methods and methods that allow developer to inspect
    LTI basic-launch-request.

    This object is instantiated by @lti wrapper.
    """

    def __init__(self, lti_args, lti_kwargs):
        self.lti_args = lti_args
        self.lti_kwargs = lti_kwargs
        self.nickname = self.name

        # Set app to current_app if not specified
        if not self.lti_kwargs['app']:
            self.lti_kwargs['app'] = current_app

    @property
    def name(self):  # pylint: disable=no-self-use
        """
        Name returns user's name or user's email or user_id
        :return: best guess of name to use to greet user
        """
        if 'lis_person_sourcedid' in session:
            return session['lis_person_sourcedid']
        elif 'lis_person_contact_email_primary' in session:
            return session['lis_person_contact_email_primary']
        elif 'user_id' in session:
            return session['user_id']
        else:
            return ''

    @property
    def user_id(self):  # pylint: disable=no-self-use
        """
        Returns user_id as provided by LTI

        :return: user_id
        """
        return session['user_id']

    def verify(self):
        """
        Verify if LTI request is valid, validation
        depends on @lti wrapper arguments

        :raises: LTIException
        """
        log.debug('verify request=%s', self.lti_kwargs.get('request'))
        if self.lti_kwargs.get('request') == 'session':
            self._verify_session()
        elif self.lti_kwargs.get('request') == 'initial':
            self.verify_request()
        elif self.lti_kwargs.get('request') == 'any':
            self._verify_any()
        else:
            raise LTIException("Unknown request type")
        return True

    def _verify_any(self):
        """
        Verify that request is in session or initial request

        :raises: LTIException
        """
        log.debug('verify_any enter')
        try:
            self._verify_session()
        except LTINotInSessionException:
            self.verify_request()

    @staticmethod
    def _verify_session():
        """
        Verify that session was already created

        :raises: LTIException
        """
        if not session.get(LTI_SESSION_KEY, False):
            log.debug('verify_session failed')
            raise LTINotInSessionException('Session expired or unavailable')

    def _consumers(self):
        """
        Gets consumer's map from app config
        :return: consumers map
        """
        app_config = self.lti_kwargs['app'].config
        config = app_config.get('PYLTI_CONFIG', dict())
        consumers = config.get('consumers', dict())
        return consumers

    @property
    def key(self):  # pylint: disable=no-self-use
        """
        OAuth Consumer Key
        :return: key
        """
        return session['oauth_consumer_key']

    @staticmethod
    def message_identifier_id():
        """
        Message identifier to use for XML callback

        :return: non-empty string
        """
        return "edX_fix"

    @property
    def lis_result_sourcedid(self):  # pylint: disable=no-self-use
        """
        lis_result_sourcedid to use for XML callback

        :return: LTI lis_result_sourcedid
        """
        return session['lis_result_sourcedid']

    @property
    def role(self):  # pylint: disable=no-self-use
        """
        LTI roles

        :return: roles
        """
        return session.get('roles')

    @staticmethod
    def is_role(role):
        """
        Verify if user is in role

        :param: role: role to verify against
        :return: if user is in role
        :exception: LTIException if role is unknown
        """
        log.debug("is_role %s", role)
        roles = session['roles'].split(',')
        if role in LTI_ROLES:
            role_list = LTI_ROLES[role]
            # find the intersection of the roles
            roles = set(role_list) & set(roles)
            is_user_role_there = len(roles) >= 1
            log.debug(
                "is_role roles_list=%s role=%s in list=%s", role_list,
                roles, is_user_role_there
            )
            return is_user_role_there
        else:
            raise LTIException("Unknown role {}.".format(role))

    def _check_role(self):
        """
        Check that user is in role specified as wrapper attribute

        :exception: LTIException if user is not in roles
        """
        role = u'any'
        if 'role' in self.lti_kwargs:
            role = self.lti_kwargs['role']
        log.debug(
            "check_role lti_role=%s decorator_role=%s", self.role, role
        )
        if not (role == u'any' or self.is_role(role)):
            raise LTIRoleException('Not authorized.')

    @property
    def response_url(self):
        """
        Returns remapped lis_outcome_service_url
        uses PYLTI_URL_FIX map to support edX dev-stack

        :return: remapped lis_outcome_service_url
        """
        url = session['lis_outcome_service_url']
        app_config = self.lti_kwargs['app'].config
        urls = app_config.get('PYLTI_URL_FIX', dict())
        # url remapping is useful for using devstack
        # devstack reports httpS://localhost:8000/ and listens on HTTP
        for prefix, mapping in urls.iteritems():
            if url.startswith(prefix):
                for _from, _to in mapping.iteritems():
                    url = url.replace(_from, _to)
        return url

    def verify_request(self):
        """
        Verify LTI request

        :raises: LTIException is request validation failed
        """
        if flask_request.method == 'POST':
            params = flask_request.form.to_dict()
        else:
            params = flask_request.args.to_dict()
        log.debug(params)

        log.debug('verify_request?')
        try:
            verify_request_common(self._consumers(), flask_request.url,
                                  flask_request.method, flask_request.headers,
                                  params)
            log.debug('verify_request success')

            # All good to go, store all of the LTI params into a
            # session dict for use in views
            for prop in LTI_PROPERTY_LIST:
                if params.get(prop, None):
                    log.debug("params %s=%s", prop, params.get(prop, None))
                    session[prop] = params[prop]

            # Set logged in session key
            session[LTI_SESSION_KEY] = True
            return True
        except LTIException:
            log.debug('verify_request failed')
            for prop in LTI_PROPERTY_LIST:
                if session.get(prop, None):
                    del session[prop]

            session[LTI_SESSION_KEY] = False
            raise

    def post_grade(self, grade):
        """
        Post grade to LTI consumer using XML

        :param: grade: 0 <= grade <= 1
        :return: True is post successful and grade valid
        :exception: LTIPostMessageException if call failed
        """
        message_identifier_id = self.message_identifier_id()
        operation = 'replaceResult'
        lis_result_sourcedid = self.lis_result_sourcedid
        # # edX devbox fix
        score = float(grade)
        if 0 <= score <= 1.0:
            xml = generate_request_xml(
                message_identifier_id, operation, lis_result_sourcedid,
                score)
            ret = post_message(self._consumers(), self.key,
                               self.response_url, xml)
            if not ret:
                raise LTIPostMessageException("Post Message Failed")
            return True

        return False

    def post_grade2(self, grade, user=None, comment=''):
        """
        Post grade to LTI consumer using REST/JSON
        URL munging will is related to:
        https://openedx.atlassian.net/browse/PLAT-281

        :param: grade: 0 <= grade <= 1
        :return: True is post successful and grade valid
        :exception: LTIPostMessageException if call failed
        """
        content_type = 'application/vnd.ims.lis.v2.result+json'
        if user is None:
            user = self.user_id
        lti2_url = self.response_url.replace(
            "/grade_handler",
            "/lti_2_0_result_rest_handler/user/{}".format(user))
        score = float(grade)
        if 0 <= score <= 1.0:
            body = json.dumps({
                "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
                "@type": "Result",
                "resultScore": score,
                "comment": comment
            })
            ret = post_message2(self._consumers(), self.key, lti2_url, body,
                                method='PUT',
                                content_type=content_type)
            if not ret:
                raise LTIPostMessageException("Post Message Failed")
            return True

        return False

    @staticmethod
    def close_session():
        """
        Invalidates session
        """
        for prop in LTI_PROPERTY_LIST:
            if session.get(prop, None):
                del session[prop]
        session[LTI_SESSION_KEY] = False

def lti(app=None, request='any', error=default_error, role='any',
        *lti_args, **lti_kwargs):
    """
    LTI decorator

    :param: app - Flask App object (optional).
        :py:attr:`flask.current_app` is used if no object is passed in.
    :param: error - Callback if LTI throws exception (optional).
        :py:attr:`pylti.flask.default_error` is the default.
    :param: request - Request type from
        :py:attr:`pylti.common.LTI_REQUEST_TYPE`. (default: any)
    :param: roles - LTI Role (default: any)
    :return: wrapper
    """

    def _lti(function):
        """
        Inner LTI decorator

        :param: function:
        :return:
        """

        @wraps(function)
        def wrapper(*args, **kwargs):
            """
            Pass LTI reference to function or return error.
            """
            try:
                the_lti = LTI(lti_args, lti_kwargs)
                the_lti.verify()
                the_lti._check_role()  # pylint: disable=protected-access
                kwargs['lti'] = the_lti
                return function(*args, **kwargs)
            except LTIException as lti_exception:
                error = lti_kwargs.get('error')
                exception = dict()
                exception['exception'] = lti_exception
                exception['kwargs'] = kwargs
                exception['args'] = args
                return error(exception=exception)

        return wrapper

    lti_kwargs['request'] = request
    lti_kwargs['error'] = error
    lti_kwargs['role'] = role

    if (not app) or isinstance(app, Flask):
        lti_kwargs['app'] = app
        return _lti
    else:
        # We are wrapping without arguments
        lti_kwargs['app'] = None
        return _lti(app)
<commit_msg>document correct exception thrown by flask _check_role()<commit_after># -*- coding: utf-8 -*-
"""
    PyLTI decorator implementation for flask framework
"""
from __future__ import absolute_import
from functools import wraps
import logging
import json

from flask import session, current_app, Flask
from flask import request as flask_request

from .common import (
    LTI_SESSION_KEY,
    LTI_PROPERTY_LIST,
    LTI_ROLES,
    verify_request_common,
    post_message,
    post_message2,
    generate_request_xml,
    LTIException,
    LTIRoleException,
    LTINotInSessionException,
    LTIPostMessageException
)

log = logging.getLogger('pylti.flask')  # pylint: disable=invalid-name

def default_error(exception=None):
    """Render simple error page.  This should be overidden in applications."""
    # pylint: disable=unused-argument
    return "There was an LTI communication error", 500

class LTI(object):
    """
    LTI Object represents abstraction of current LTI session. It provides
    callback methods and methods that allow developer to inspect
    LTI basic-launch-request.

    This object is instantiated by @lti wrapper.
    """

    def __init__(self, lti_args, lti_kwargs):
        self.lti_args = lti_args
        self.lti_kwargs = lti_kwargs
        self.nickname = self.name

        # Set app to current_app if not specified
        if not self.lti_kwargs['app']:
            self.lti_kwargs['app'] = current_app

    @property
    def name(self):  # pylint: disable=no-self-use
        """
        Name returns user's name or user's email or user_id
        :return: best guess of name to use to greet user
        """
        if 'lis_person_sourcedid' in session:
            return session['lis_person_sourcedid']
        elif 'lis_person_contact_email_primary' in session:
            return session['lis_person_contact_email_primary']
        elif 'user_id' in session:
            return session['user_id']
        else:
            return ''

    @property
    def user_id(self):  # pylint: disable=no-self-use
        """
        Returns user_id as provided by LTI

        :return: user_id
        """
        return session['user_id']

    def verify(self):
        """
        Verify if LTI request is valid, validation
        depends on @lti wrapper arguments

        :raises: LTIException
        """
        log.debug('verify request=%s', self.lti_kwargs.get('request'))
        if self.lti_kwargs.get('request') == 'session':
            self._verify_session()
        elif self.lti_kwargs.get('request') == 'initial':
            self.verify_request()
        elif self.lti_kwargs.get('request') == 'any':
            self._verify_any()
        else:
            raise LTIException("Unknown request type")
        return True

    def _verify_any(self):
        """
        Verify that request is in session or initial request

        :raises: LTIException
        """
        log.debug('verify_any enter')
        try:
            self._verify_session()
        except LTINotInSessionException:
            self.verify_request()

    @staticmethod
    def _verify_session():
        """
        Verify that session was already created

        :raises: LTIException
        """
        if not session.get(LTI_SESSION_KEY, False):
            log.debug('verify_session failed')
            raise LTINotInSessionException('Session expired or unavailable')

    def _consumers(self):
        """
        Gets consumer's map from app config
        :return: consumers map
        """
        app_config = self.lti_kwargs['app'].config
        config = app_config.get('PYLTI_CONFIG', dict())
        consumers = config.get('consumers', dict())
        return consumers

    @property
    def key(self):  # pylint: disable=no-self-use
        """
        OAuth Consumer Key
        :return: key
        """
        return session['oauth_consumer_key']

    @staticmethod
    def message_identifier_id():
        """
        Message identifier to use for XML callback

        :return: non-empty string
        """
        return "edX_fix"

    @property
    def lis_result_sourcedid(self):  # pylint: disable=no-self-use
        """
        lis_result_sourcedid to use for XML callback

        :return: LTI lis_result_sourcedid
        """
        return session['lis_result_sourcedid']

    @property
    def role(self):  # pylint: disable=no-self-use
        """
        LTI roles

        :return: roles
        """
        return session.get('roles')

    @staticmethod
    def is_role(role):
        """
        Verify if user is in role

        :param: role: role to verify against
        :return: if user is in role
        :exception: LTIException if role is unknown
        """
        log.debug("is_role %s", role)
        roles = session['roles'].split(',')
        if role in LTI_ROLES:
            role_list = LTI_ROLES[role]
            # find the intersection of the roles
            roles = set(role_list) & set(roles)
            is_user_role_there = len(roles) >= 1
            log.debug(
                "is_role roles_list=%s role=%s in list=%s", role_list,
                roles, is_user_role_there
            )
            return is_user_role_there
        else:
            raise LTIException("Unknown role {}.".format(role))

    def _check_role(self):
        """
        Check that user is in role specified as wrapper attribute

        :exception: LTIRoleException if user is not in roles
        """
        role = u'any'
        if 'role' in self.lti_kwargs:
            role = self.lti_kwargs['role']
        log.debug(
            "check_role lti_role=%s decorator_role=%s", self.role, role
        )
        if not (role == u'any' or self.is_role(role)):
            raise LTIRoleException('Not authorized.')

    @property
    def response_url(self):
        """
        Returns remapped lis_outcome_service_url
        uses PYLTI_URL_FIX map to support edX dev-stack

        :return: remapped lis_outcome_service_url
        """
        url = session['lis_outcome_service_url']
        app_config = self.lti_kwargs['app'].config
        urls = app_config.get('PYLTI_URL_FIX', dict())
        # url remapping is useful for using devstack
        # devstack reports httpS://localhost:8000/ and listens on HTTP
        for prefix, mapping in urls.iteritems():
            if url.startswith(prefix):
                for _from, _to in mapping.iteritems():
                    url = url.replace(_from, _to)
        return url

    def verify_request(self):
        """
        Verify LTI request

        :raises: LTIException is request validation failed
        """
        if flask_request.method == 'POST':
            params = flask_request.form.to_dict()
        else:
            params = flask_request.args.to_dict()
        log.debug(params)

        log.debug('verify_request?')
        try:
            verify_request_common(self._consumers(), flask_request.url,
                                  flask_request.method, flask_request.headers,
                                  params)
            log.debug('verify_request success')

            # All good to go, store all of the LTI params into a
            # session dict for use in views
            for prop in LTI_PROPERTY_LIST:
                if params.get(prop, None):
                    log.debug("params %s=%s", prop, params.get(prop, None))
                    session[prop] = params[prop]

            # Set logged in session key
            session[LTI_SESSION_KEY] = True
            return True
        except LTIException:
            log.debug('verify_request failed')
            for prop in LTI_PROPERTY_LIST:
                if session.get(prop, None):
                    del session[prop]

            session[LTI_SESSION_KEY] = False
            raise

    def post_grade(self, grade):
        """
        Post grade to LTI consumer using XML

        :param: grade: 0 <= grade <= 1
        :return: True is post successful and grade valid
        :exception: LTIPostMessageException if call failed
        """
        message_identifier_id = self.message_identifier_id()
        operation = 'replaceResult'
        lis_result_sourcedid = self.lis_result_sourcedid
        # # edX devbox fix
        score = float(grade)
        if 0 <= score <= 1.0:
            xml = generate_request_xml(
                message_identifier_id, operation, lis_result_sourcedid,
                score)
            ret = post_message(self._consumers(), self.key,
                               self.response_url, xml)
            if not ret:
                raise LTIPostMessageException("Post Message Failed")
            return True

        return False

    def post_grade2(self, grade, user=None, comment=''):
        """
        Post grade to LTI consumer using REST/JSON
        URL munging will is related to:
        https://openedx.atlassian.net/browse/PLAT-281

        :param: grade: 0 <= grade <= 1
        :return: True is post successful and grade valid
        :exception: LTIPostMessageException if call failed
        """
        content_type = 'application/vnd.ims.lis.v2.result+json'
        if user is None:
            user = self.user_id
        lti2_url = self.response_url.replace(
            "/grade_handler",
            "/lti_2_0_result_rest_handler/user/{}".format(user))
        score = float(grade)
        if 0 <= score <= 1.0:
            body = json.dumps({
                "@context": "http://purl.imsglobal.org/ctx/lis/v2/Result",
                "@type": "Result",
                "resultScore": score,
                "comment": comment
            })
            ret = post_message2(self._consumers(), self.key, lti2_url, body,
                                method='PUT',
                                content_type=content_type)
            if not ret:
                raise LTIPostMessageException("Post Message Failed")
            return True

        return False

    @staticmethod
    def close_session():
        """
        Invalidates session
        """
        for prop in LTI_PROPERTY_LIST:
            if session.get(prop, None):
                del session[prop]
        session[LTI_SESSION_KEY] = False

def lti(app=None, request='any', error=default_error, role='any',
        *lti_args, **lti_kwargs):
    """
    LTI decorator

    :param: app - Flask App object (optional).
        :py:attr:`flask.current_app` is used if no object is passed in.
    :param: error - Callback if LTI throws exception (optional).
        :py:attr:`pylti.flask.default_error` is the default.
    :param: request - Request type from
        :py:attr:`pylti.common.LTI_REQUEST_TYPE`. (default: any)
    :param: roles - LTI Role (default: any)
    :return: wrapper
    """

    def _lti(function):
        """
        Inner LTI decorator

        :param: function:
        :return:
        """

        @wraps(function)
        def wrapper(*args, **kwargs):
            """
            Pass LTI reference to function or return error.
            """
            try:
                the_lti = LTI(lti_args, lti_kwargs)
                the_lti.verify()
                the_lti._check_role()  # pylint: disable=protected-access
                kwargs['lti'] = the_lti
                return function(*args, **kwargs)
            except LTIException as lti_exception:
                error = lti_kwargs.get('error')
                exception = dict()
                exception['exception'] = lti_exception
                exception['kwargs'] = kwargs
                exception['args'] = args
                return error(exception=exception)

        return wrapper

    lti_kwargs['request'] = request
    lti_kwargs['error'] = error
    lti_kwargs['role'] = role

    if (not app) or isinstance(app, Flask):
        lti_kwargs['app'] = app
        return _lti
    else:
        # We are wrapping without arguments
        lti_kwargs['app'] = None
        return _lti(app)
<|endoftext|>


<commit_before>from ctypes import byref, sizeof, addressof, create_string_buffer, cast, POINTER, c_ubyte, c_void_p
from typing import Optional, Callable
import warnings
try:
    import numpy as np
except ImportError:
    warnings.warn('could not import numpy, some Frame methods may not work.')

from .vimba_exception import VimbaException
from . import vimba_c

# Map pixel formats to bytes per pixel
PIXEL_FORMATS = {
    "Mono8": 1,
    "Mono12": 2,
    # untested
    "Mono12Packed": 1.5,
    "Mono14": 2,
    "Mono16": 2,
    "RGB8": 3,
    "RGB8Packed": 3,
    "BGR8Packed": 3,
    "RGBA8Packed": 4,
    "BGRA8Packed": 4,
    # untested
    "YUV411Packed": 4 / 3.0,
    "YUV422Packed": 2,
    "YUV444Packed": 3,
    "BayerRG8": 1,
    "BayerRG12": 2,
    "BayerGR8": 1,
    "BayerGR12": 2,
    # untested
    "BayerGR12Packed": 1.5,
}

class MemoryBlock:
    """
    A memory block object for dealing neatly with C memory allocations.
    """

    @property
    def block(self):
        return c_void_p(addressof(self._block))

    def __init__(self, block_size):
        self._block = create_string_buffer(block_size)

        # this seems to be None if too much memory is requested
        if self._block is None:
            raise VimbaException(VimbaException.ERR_FRAME_BUFFER_MEMORY)

    def __del__(self):
        del self._block

class Frame:
    """
    A Vimba frame.
    """

    def __init__(self, camera: 'Camera'):
        self._camera = camera
        self._handle = camera.handle

        # get frame sizes
        self.payload_size = self._camera.PayloadSize
        self.width = self._camera.Width
        self.height = self._camera.Height
        self.pixel_bytes = PIXEL_FORMATS[self._camera.PixelFormat]

        # frame structure
        self._frame = vimba_c.VmbFrame()

        self._c_mem = None

    def announce(self):
        """
        Announce frames to the API that may be queued for frame capturing later. Should be called after the frame is
        created. Call startCapture after this method.
        """
        # keep this reference to keep block alive for life of frame
        self._c_mem = MemoryBlock(self.payload_size)
        # set buffer to have length of expected payload size
        self._frame.buffer = self._c_mem.block

        # set buffer size to expected payload size
        self._frame.bufferSize = self.payload_size

        error = vimba_c.vmb_frame_announce(self._handle,
                                           byref(self._frame),
                                           sizeof(self._frame))
        if error:
            raise VimbaException(error)

    def revoke(self):
        """
        Revoke a frame from the API.
        """
        error = vimba_c.vmb_frame_revoke(self._handle,
                                         byref(self._frame))
        if error:
            raise VimbaException(error)

    def queue_capture(self, frame_callback: Optional[Callable] = None) -> None:
        """
        Queue frames that may be filled during frame capturing. Call after announceFrame and startCapture. Callback
        must accept argument of type frame. Remember to requeue the frame by calling frame.queue_capture() at the end
        of your callback function.
        """
        self._frame_callback = frame_callback

        # define a callback wrapper here so it doesn't bind self
        def frame_callback_wrapper(camera_handle, frame_ptr):
            # call the user's callback with the self bound outside the wrapper
            # ignore the frame pointer since we already know the callback refers to this frame
            self._frame_callback(self)

        if self._frame_callback is None:
            self._frame_callback_wrapper_c = None
        else:
            # keep a reference to prevent gc issues
            self._frame_callback_wrapper_c = vimba_c.vmb_frame_callback_func(frame_callback_wrapper)

        error = vimba_c.vmb_capture_frame_queue(self._handle,
                                                byref(self._frame),
                                                self._frame_callback_wrapper_c)
        if error:
            raise VimbaException(error)

    def wait_capture(self, timeout_ms: Optional[int] = 2000) -> int:
        """
        Wait for a queued frame to be filled (or dequeued). Call after an acquisition command.
        :param timeout_ms: time out in milliseconds.
        """
        error = vimba_c.vmb_capture_frame_wait(self._handle,
                                               byref(self._frame),
                                               timeout_ms)

        # error to be processed by the end user for this function.
        # Prevents system for breaking for example on a hardware trigger timeout

        # todo raise error instead?

        return error

    def get_buffer_data(self):
        """
        Retrieve buffer data in a useful format.
        """

        # todo simplify?

        # cast frame buffer memory contents to a usable type
        data = cast(self._frame.buffer, POINTER(c_ubyte * self.payload_size))

        # make array of c_ubytes from buffer
        image_bytes = int(self.height * self.width * self.pixel_bytes)
        return (c_ubyte * image_bytes).from_address(addressof(data.contents))

    def get_image(self):
        """
        Returns the frame's image data as a NumPy array.
        """
        data = cast(self._frame.buffer, POINTER(c_ubyte * self._frame.imageSize))
        return np.ndarray(buffer=data.contents, dtype=np.uint8, shape=(self._frame.height, self._frame.width))

    @property
    def timestamp(self) -> int:
        return self._frame.timestamp

    @property
    def receive_status(self) -> int:
        return self._frame.receiveStatus
<commit_msg>cleanup frame class<commit_after>from ctypes import byref, sizeof, addressof, create_string_buffer, cast, POINTER, c_ubyte, c_void_p
from typing import Optional, Callable
import numpy as np

from . import camera as _camera
from .vimba_exception import VimbaException
from . import vimba_c

class MemoryBlock:
    """
    A memory block object for dealing neatly with C memory allocations.
    """

    @property
    def block(self):
        return c_void_p(addressof(self._block))

    def __init__(self, block_size):
        self._block = create_string_buffer(block_size)

        # this seems to be None if too much memory is requested
        if self._block is None:
            raise VimbaException(VimbaException.ERR_FRAME_BUFFER_MEMORY)

class Frame:
    """
    A Vimba frame.
    """

    def __init__(self, camera: '_camera.Camera'):
        self._camera = camera

        self._vmb_frame = vimba_c.VmbFrame()

        self._c_mem = None
        self._frame_callback = None
        self._frame_callback_wrapper_c = None

    def announce(self) -> None:
        """
        Announce frames to the API that may be queued for frame capturing later. Should be called after the frame is
        created. Call startCapture after this method.
        """
        # keep this reference to keep block alive for life of frame
        self._c_mem = MemoryBlock(self._camera.PayloadSize)

        # set buffer to have length of expected payload size
        self._vmb_frame.buffer = self._c_mem.block

        # set buffer size to expected payload size
        self._vmb_frame.bufferSize = self._camera.PayloadSize

        error = vimba_c.vmb_frame_announce(self._camera.handle,
                                           byref(self._vmb_frame),
                                           sizeof(self._vmb_frame))
        if error:
            raise VimbaException(error)

    def revoke(self) -> None:
        """
        Revoke a frame from the API.
        """
        error = vimba_c.vmb_frame_revoke(self._camera.handle,
                                         byref(self._vmb_frame))
        if error:
            raise VimbaException(error)

    def queue_for_capture(self, frame_callback: Optional[Callable] = None) -> None:
        """
        Queue frames that may be filled during frame capturing. Call after announceFrame and startCapture. Callback
        must accept argument of type frame. Remember to requeue the frame by calling frame.queue_capture() at the end
        of your callback function.
        """
        self._frame_callback = frame_callback

        # define a callback wrapper here so it doesn't bind self
        def frame_callback_wrapper(camera_handle, frame_ptr):
            # call the user's callback with the self bound outside the wrapper
            # ignore the frame pointer since we already know the callback refers to this frame
            self._frame_callback(self)

        if self._frame_callback is None:
            self._frame_callback_wrapper_c = None
        else:
            # keep a reference to prevent gc issues
            self._frame_callback_wrapper_c = vimba_c.vmb_frame_callback_func(frame_callback_wrapper)

        error = vimba_c.vmb_capture_frame_queue(self._camera.handle,
                                                byref(self._vmb_frame),
                                                self._frame_callback_wrapper_c)
        if error:
            raise VimbaException(error)

    def wait_for_capture(self, timeout_ms: Optional[int] = 2000) -> None:
        """
        Wait for a queued frame to be filled (or dequeued). Call after an acquisition command.
        :param timeout_ms: time out in milliseconds.
        """
        error = vimba_c.vmb_capture_frame_wait(self._camera.handle,
                                               byref(self._vmb_frame),
                                               timeout_ms)
        if error:
            raise VimbaException(error)

    def image_pointer(self):
        """
        Get a pointer to the frame's image data as a ctypes c_ubyte array pointer.
        """
        return cast(self._vmb_frame.buffer, POINTER(c_ubyte * self._vmb_frame.bufferSize))

    def image_buffer(self):
        """
        Get a copy of the frame's image data as a ctypes c_ubyte array.
        """
        return self.image_pointer().contents

    def image_numpy_array(self) -> np.ndarray:
        """
        Get the frame's image data as a NumPy array, which can be used with OpenCV.
        """
        # todo pixel formats larger than 8-bit

        return np.ndarray(buffer=self.image_buffer(),
                          dtype=np.uint8,
                          shape=(self._vmb_frame.height, self._vmb_frame.width))
<|endoftext|>


<commit_before>from .vartypes import *

from theano import theano, tensor as t, function
from theano.tensor.var import TensorVariable

import numpy as np
from functools import wraps
from .theanof import *
from inspect import getargspec

from .memoize import memoize

__all__ = ['Model', 'Factor', 'compilef', 'fn', 'fastfn', 'modelcontext', 'Point', 'Deterministic', 'Potential']

class Context(object):
    """Functionality for objects that put themselves in a context using the `with` statement."""
    def __enter__(self):
        type(self).get_contexts().append(self)
        return self

    def __exit__(self, typ, value, traceback):
        type(self).get_contexts().pop()

    @classmethod
    def get_contexts(cls):
        if not hasattr(cls, "contexts"):
            cls.contexts = []

        return cls.contexts

    @classmethod
    def get_context(cls):
        """Return the deepest context on the stack."""
        try:
            return cls.get_contexts()[-1]
        except IndexError:
            raise TypeError("No context on context stack")

def modelcontext(model):
    """return the given model or try to find it in the context if there was none supplied."""
    if model is None:
        return Model.get_context()
    return model

class Factor(object):
    """Common functionality for objects with a log probability density associated with them."""
    @property
    def logp(self):
        """Compiled log probability density function"""
        return self.model.fn(self.logpt)

    @property
    def logp_elemwise(self):
        return self.model.fn(self.logp_elemwiset)

    def dlogp(self, vars=None):
        """Compiled log probability density gradient function"""
        return self.model.fn(gradient(self.logpt, vars))

    def d2logp(self, vars=None):
        """Compiled log probability density hessian function"""
        return self.model.fn(hessian(self.logpt, vars))

    @property
    def fastlogp(self):
        """Compiled log probability density function"""
        return self.model.fastfn(self.logpt)

    def fastdlogp(self, vars=None):
        """Compiled log probability density gradient function"""
        return self.model.fastfn(gradient(self.logpt, vars))

    def fastd2logp(self, vars=None):
        """Compiled log probability density hessian function"""
        return self.model.fastfn(hessian(self.logpt, vars))

    @property
    def logpt(self):
        """Theano scalar of log-probability of the model"""
        return t.sum(self.logp_elemwiset)

class Model(Context, Factor):
    """Encapsulates the variables and likelihood factors of a model."""

    def __init__(self):
        self.named_vars = {}
        self.free_RVs = []
        self.observed_RVs = []
        self.deterministics = []
        self.potentials = []
        self.missing_values = []
        self.model = self

    @property
    @memoize
    def logpt(self):
        """Theano scalar of log-probability of the model"""
        factors = [var.logpt for var in self.basic_RVs] + self.potentials
        return t.add(*map(t.sum, factors))

    @property
    def vars(self):
        """List of unobserved random variables used as inputs to the model (which excludes deterministics)."""
        return self.free_RVs

    @property
    def basic_RVs(self):
        """List of random variables the model is defined in terms of (which excludes deterministics)."""
        return (self.free_RVs + self.observed_RVs)

    @property
    def unobserved_RVs(self):
        """List of all random variable, including deterministic ones."""
        return self.vars + self.deterministics

    @property
    def test_point(self):
        """Test point used to check that the model doesn't generate errors"""
        return Point(((var, var.tag.test_value) for var in self.vars),
                     model=self)

    @property
    def cont_vars(self):
        """All the continuous variables in the model"""
        return list(typefilter(self.vars, continuous_types))

    def Var(self, name, dist, data=None):
        """Create and add (un)observed random variable to the model with an appropriate prior distribution.

        Parameters
        ----------
            name : str
            dist : distribution for the random variable
            data : arraylike (optional)
               if data is provided, the variable is observed. If None, the variable is unobserved.
        Returns
        -------
            FreeRV or ObservedRV
        """
        if data is None:
            if getattr(dist, "transform", None) is None:
                var = FreeRV(name=name, distribution=dist, model=self)
                self.free_RVs.append(var)
            else:
                var = TransformedRV(name=name, distribution=dist, model=self, transform=dist.transform)
                self.deterministics.append(var)
                return var
        elif isinstance(data, dict):
            var = MultiObservedRV(name=name, data=data, distribution=dist, model=self)
            self.observed_RVs.append(var)
            if var.missing_values:
                self.free_RVs += var.missing_values
                self.missing_values += var.missing_values
        else:
            var = ObservedRV(name=name, data=data, distribution=dist, model=self)
            self.observed_RVs.append(var)
            if var.missing_values:
                self.free_RVs.append(var.missing_values)
                self.missing_values.append(var.missing_values)

        self.add_random_variable(var)
        return var

    def add_random_variable(self, var):
        """Add a random variable to the named variables of the model."""
        self.named_vars[var.name] = var
        if not hasattr(self, var.name):
            setattr(self, var.name, var)

    def __getitem__(self, key):
        return self.named_vars[key]

    @memoize
    def makefn(self, outs, mode=None, *args, **kwargs):
        """Compiles a Theano function which returns `outs` and takes the variable
        ancestors of `outs` as inputs.

        Parameters
        ----------
        outs : Theano variable or iterable of Theano variables
        mode : Theano compilation mode

        Returns
        -------
        Compiled Theano function"""
        return function(self.vars, outs,
                     allow_input_downcast=True,
                     on_unused_input='ignore',
                     accept_inplace=True,
                     mode=mode, *args, **kwargs)

    def fn(self, outs, mode=None, *args, **kwargs):
        """Compiles a Theano function which returns the values of `outs` and takes values of model
        vars as arguments.

        Parameters
        ----------
        outs : Theano variable or iterable of Theano variables
        mode : Theano compilation mode

        Returns
        -------
        Compiled Theano function"""
        return LoosePointFunc(self.makefn(outs, mode, *args, **kwargs), self)

    def fastfn(self, outs, mode=None, *args, **kwargs):
        """Compiles a Theano function which returns `outs` and takes values of model
        vars as a dict as an argument.

        Parameters
        ----------
        outs : Theano variable or iterable of Theano variables
        mode : Theano compilation mode

        Returns
        -------
        Compiled Theano function as point function."""
        return FastPointFunc(self.makefn(outs, mode, *args, **kwargs))

    def profile(self, outs, n=1000, point=None, profile=True, *args, **kwargs):
        """Compiles and profiles a Theano function which returns `outs` and takes values of model
        vars as a dict as an argument.

        Parameters
        ----------
        outs : Theano variable or iterable of Theano variables
        n : int, default 1000
            Number of iterations to run
        point : point
            Point to pass to the function
        profile : True or ProfileStats
        *args, **kwargs
            Compilation args

        Returns
        -------
        ProfileStats
            Use .summary() to print stats."""
        f = self.makefn(outs, profile=profile, *args, **kwargs)
        if point is None:
            point = self.test_point

        for i in range(n):
            f(**point)

        return f.profile

def fn(outs, mode=None, model=None, *args, **kwargs):
    """Compiles a Theano function which returns the values of `outs` and takes values of model
    vars as arguments.

    Parameters
    ----------
    outs : Theano variable or iterable of Theano variables
    mode : Theano compilation mode

    Returns
    -------
    Compiled Theano function"""
    model = modelcontext(model)
    return model.fn(outs,mode, *args, **kwargs)

def fastfn(outs, mode=None, model=None):
    """Compiles a Theano function which returns `outs` and takes values of model
    vars as a dict as an argument.

    Parameters
    ----------
    outs : Theano variable or iterable of Theano variables
    mode : Theano compilation mode

    Returns
    -------
    Compiled Theano function as point function."""
    model = modelcontext(model)
    return model.fastfn(outs,mode)

def Point(*args, **kwargs):
    """Build a point. Uses same args as dict() does.
    Filters out variables not in the model. All keys are strings.

    Parameters
    ----------
        *args, **kwargs
            arguments to build a dict"""
    model = modelcontext(kwargs.pop('model', None))

    args = [a for a in args]
    try:
        d = dict(*args, **kwargs)
    except Exception as e:
        raise TypeError(
            "can't turn " + str(args) + " and " + str(kwargs) +
            " into a dict. " + str(e))

    varnames = list(map(str, model.vars))
    return dict((str(k), np.array(v))
                for (k, v) in d.items()
                if str(k) in varnames)

class FastPointFunc(object):
    """Wraps so a function so it takes a dict of arguments instead of arguments."""
    def __init__(self, f):
        self.f = f

    def __call__(self, state):
        return self.f(**state)

class LoosePointFunc(object):
    """Wraps so a function so it takes a dict of arguments instead of arguments
    but can still take arguments."""
    def __init__(self, f, model):
        self.f = f
        self.model = model

    def __call__(self, *args, **kwargs):
        point = Point(model=self.model, *args, **kwargs)
        return self.f(**point)

compilef = fastfn

class FreeRV(Factor, TensorVariable):
    """Unobserved random variable that a model is specified in terms of."""
    def __init__(self, type=None, owner=None, index=None, name=None, distribution=None, model=None):
        """
        Parameters
        ----------

        type : theano type (optional)
        owner : theano owner (optional)

        name : str
        distribution : Distribution
        model : Model"""
        if type is None:
            type = distribution.type
        super(FreeRV, self).__init__(type, owner, index, name)

        if distribution is not None:
            self.dshape = tuple(distribution.shape)
            self.dsize = int(np.prod(distribution.shape))
            self.distribution = distribution
            self.tag.test_value = np.ones(
                distribution.shape, distribution.dtype) * distribution.default()
            self.logp_elemwiset = distribution.logp(self)
            self.model = model

def pandas_to_array(data):
    if hasattr(data, 'values'): #pandas
        if data.isnull().any().any(): #missing values
            return np.ma.MaskedArray(data.values, data.isnull().values)
        else:
            return data.values
    elif hasattr(data, 'mask'):
        return data
    else:
        return np.asarray(data)

def as_tensor(data, name):
    data = pandas_to_array(data)

    if hasattr(data, 'mask'):
        from .distributions import NoDistribution
        fakedist = NoDistribution.dist(shape=data.mask.sum(), dtype=data.dtype, testval=data.mask.mean().astype(data.dtype))
        missing_values = FreeRV(name=name + '_missing', distribution=fakedist)

        constant = t.as_tensor_variable(data.filled())

        dataTensor = theano.tensor.set_subtensor(constant[data.mask.nonzero()], missing_values)
        dataTensor.missing_values = missing_values
        return dataTensor
    else:
        data = t.as_tensor_variable(data, name=name)
        data.missing_values = None
        return data

class ObservedRV(Factor, TensorVariable):
    """Observed random variable that a model is specified in terms of.
    Potentially partially observed.
    """
    def __init__(self, type=None, owner=None, index=None, name=None, data=None, distribution=None, model=None):
        """
        Parameters
        ----------

        type : theano type (optional)
        owner : theano owner (optional)

        name : str
        distribution : Distribution
        model : Model
        """
        from .distributions import TensorType
        if type is None:
            data = pandas_to_array(data)
            type = TensorType(data.dtype, data.shape)

        super(TensorVariable, self).__init__(type, None, None, name)

        if distribution is not None:
            data = as_tensor(data, name)
            self.missing_values = data.missing_values

            self.logp_elemwiset = distribution.logp(data)
            self.model = model
            self.distribution = distribution

            #make this RV a view on the combined missing/nonmissing array
            theano.gof.Apply(theano.compile.view_op, inputs=[data], outputs=[self])

            self.tag.test_value = theano.compile.view_op(data).tag.test_value

class MultiObservedRV(Factor):
    """Observed random variable that a model is specified in terms of.
    Potentially partially observed.
    """
    def __init__(self, name, data, distribution, model):
        """
        Parameters
        ----------

        type : theano type (optional)
        owner : theano owner (optional)

        name : str
        distribution : Distribution
        model : Model
        """
        self.name = name

        self.data = { name : as_tensor(data, name) for name, data in data.items()}

        self.missing_values = [ data.missing_values for data in self.data.values() if data.missing_values is not None]
        self.logp_elemwiset = distribution.logp(**self.data)
        self.model = model
        self.distribution = distribution

def Deterministic(name, var, model=None):
    """Create a named deterministic variable

    Parameters
    ----------
        name : str
        var : theano variables
    Returns
    -------
        n : var but with name name"""
    var.name = name
    modelcontext(model).deterministics.append(var)
    modelcontext(model).add_random_variable(var)
    return var

def Potential(name, var, model=None):
    """Add an arbitrary factor potential to the model likelihood

    Parameters
    ----------
        name : str
        var : theano variables
    Returns
    -------
        var : var, with name attribute
    """

    var.name = name
    modelcontext(model).potentials.append(var)
    return var

class TransformedRV(TensorVariable):
    def __init__(self, type=None, owner=None, index=None, name=None, distribution=None, model=None, transform=None):
        """
        Parameters
        ----------

        type : theano type (optional)
        owner : theano owner (optional)

        name : str
        distribution : Distribution
        model : Model"""
        if type is None:
            type = distribution.type
        super(TransformedRV, self).__init__(type, owner, index, name)

        if distribution is not None:
            self.model = model

            self.transformed = model.Var(name + "_" + transform.name, transform.apply(distribution))

            normalRV = transform.backward(self.transformed)

            theano.Apply(theano.compile.view_op, inputs=[normalRV], outputs=[self])
            self.tag.test_value = normalRV.tag.test_value

def as_iterargs(data):
    if isinstance(data, tuple):
        return data
    else:
        return [data]

# theano stuff
theano.config.warn.sum_div_dimshuffle_bug = False
theano.config.compute_test_value = 'raise'
<commit_msg>give missing variables the .model parameter<commit_after>from .vartypes import *

from theano import theano, tensor as t, function
from theano.tensor.var import TensorVariable

import numpy as np
from functools import wraps
from .theanof import *
from inspect import getargspec

from .memoize import memoize

__all__ = ['Model', 'Factor', 'compilef', 'fn', 'fastfn', 'modelcontext', 'Point', 'Deterministic', 'Potential']

class Context(object):
    """Functionality for objects that put themselves in a context using the `with` statement."""
    def __enter__(self):
        type(self).get_contexts().append(self)
        return self

    def __exit__(self, typ, value, traceback):
        type(self).get_contexts().pop()

    @classmethod
    def get_contexts(cls):
        if not hasattr(cls, "contexts"):
            cls.contexts = []

        return cls.contexts

    @classmethod
    def get_context(cls):
        """Return the deepest context on the stack."""
        try:
            return cls.get_contexts()[-1]
        except IndexError:
            raise TypeError("No context on context stack")

def modelcontext(model):
    """return the given model or try to find it in the context if there was none supplied."""
    if model is None:
        return Model.get_context()
    return model

class Factor(object):
    """Common functionality for objects with a log probability density associated with them."""
    @property
    def logp(self):
        """Compiled log probability density function"""
        return self.model.fn(self.logpt)

    @property
    def logp_elemwise(self):
        return self.model.fn(self.logp_elemwiset)

    def dlogp(self, vars=None):
        """Compiled log probability density gradient function"""
        return self.model.fn(gradient(self.logpt, vars))

    def d2logp(self, vars=None):
        """Compiled log probability density hessian function"""
        return self.model.fn(hessian(self.logpt, vars))

    @property
    def fastlogp(self):
        """Compiled log probability density function"""
        return self.model.fastfn(self.logpt)

    def fastdlogp(self, vars=None):
        """Compiled log probability density gradient function"""
        return self.model.fastfn(gradient(self.logpt, vars))

    def fastd2logp(self, vars=None):
        """Compiled log probability density hessian function"""
        return self.model.fastfn(hessian(self.logpt, vars))

    @property
    def logpt(self):
        """Theano scalar of log-probability of the model"""
        return t.sum(self.logp_elemwiset)

class Model(Context, Factor):
    """Encapsulates the variables and likelihood factors of a model."""

    def __init__(self):
        self.named_vars = {}
        self.free_RVs = []
        self.observed_RVs = []
        self.deterministics = []
        self.potentials = []
        self.missing_values = []
        self.model = self

    @property
    @memoize
    def logpt(self):
        """Theano scalar of log-probability of the model"""
        factors = [var.logpt for var in self.basic_RVs] + self.potentials
        return t.add(*map(t.sum, factors))

    @property
    def vars(self):
        """List of unobserved random variables used as inputs to the model (which excludes deterministics)."""
        return self.free_RVs

    @property
    def basic_RVs(self):
        """List of random variables the model is defined in terms of (which excludes deterministics)."""
        return (self.free_RVs + self.observed_RVs)

    @property
    def unobserved_RVs(self):
        """List of all random variable, including deterministic ones."""
        return self.vars + self.deterministics

    @property
    def test_point(self):
        """Test point used to check that the model doesn't generate errors"""
        return Point(((var, var.tag.test_value) for var in self.vars),
                     model=self)

    @property
    def cont_vars(self):
        """All the continuous variables in the model"""
        return list(typefilter(self.vars, continuous_types))

    def Var(self, name, dist, data=None):
        """Create and add (un)observed random variable to the model with an appropriate prior distribution.

        Parameters
        ----------
            name : str
            dist : distribution for the random variable
            data : arraylike (optional)
               if data is provided, the variable is observed. If None, the variable is unobserved.
        Returns
        -------
            FreeRV or ObservedRV
        """
        if data is None:
            if getattr(dist, "transform", None) is None:
                var = FreeRV(name=name, distribution=dist, model=self)
                self.free_RVs.append(var)
            else:
                var = TransformedRV(name=name, distribution=dist, model=self, transform=dist.transform)
                self.deterministics.append(var)
                return var
        elif isinstance(data, dict):
            var = MultiObservedRV(name=name, data=data, distribution=dist, model=self)
            self.observed_RVs.append(var)
            if var.missing_values:
                self.free_RVs += var.missing_values
                self.missing_values += var.missing_values
        else:
            var = ObservedRV(name=name, data=data, distribution=dist, model=self)
            self.observed_RVs.append(var)
            if var.missing_values:
                self.free_RVs.append(var.missing_values)
                self.missing_values.append(var.missing_values)

        self.add_random_variable(var)
        return var

    def add_random_variable(self, var):
        """Add a random variable to the named variables of the model."""
        self.named_vars[var.name] = var
        if not hasattr(self, var.name):
            setattr(self, var.name, var)

    def __getitem__(self, key):
        return self.named_vars[key]

    @memoize
    def makefn(self, outs, mode=None, *args, **kwargs):
        """Compiles a Theano function which returns `outs` and takes the variable
        ancestors of `outs` as inputs.

        Parameters
        ----------
        outs : Theano variable or iterable of Theano variables
        mode : Theano compilation mode

        Returns
        -------
        Compiled Theano function"""
        return function(self.vars, outs,
                     allow_input_downcast=True,
                     on_unused_input='ignore',
                     accept_inplace=True,
                     mode=mode, *args, **kwargs)

    def fn(self, outs, mode=None, *args, **kwargs):
        """Compiles a Theano function which returns the values of `outs` and takes values of model
        vars as arguments.

        Parameters
        ----------
        outs : Theano variable or iterable of Theano variables
        mode : Theano compilation mode

        Returns
        -------
        Compiled Theano function"""
        return LoosePointFunc(self.makefn(outs, mode, *args, **kwargs), self)

    def fastfn(self, outs, mode=None, *args, **kwargs):
        """Compiles a Theano function which returns `outs` and takes values of model
        vars as a dict as an argument.

        Parameters
        ----------
        outs : Theano variable or iterable of Theano variables
        mode : Theano compilation mode

        Returns
        -------
        Compiled Theano function as point function."""
        return FastPointFunc(self.makefn(outs, mode, *args, **kwargs))

    def profile(self, outs, n=1000, point=None, profile=True, *args, **kwargs):
        """Compiles and profiles a Theano function which returns `outs` and takes values of model
        vars as a dict as an argument.

        Parameters
        ----------
        outs : Theano variable or iterable of Theano variables
        n : int, default 1000
            Number of iterations to run
        point : point
            Point to pass to the function
        profile : True or ProfileStats
        *args, **kwargs
            Compilation args

        Returns
        -------
        ProfileStats
            Use .summary() to print stats."""
        f = self.makefn(outs, profile=profile, *args, **kwargs)
        if point is None:
            point = self.test_point

        for i in range(n):
            f(**point)

        return f.profile

def fn(outs, mode=None, model=None, *args, **kwargs):
    """Compiles a Theano function which returns the values of `outs` and takes values of model
    vars as arguments.

    Parameters
    ----------
    outs : Theano variable or iterable of Theano variables
    mode : Theano compilation mode

    Returns
    -------
    Compiled Theano function"""
    model = modelcontext(model)
    return model.fn(outs,mode, *args, **kwargs)

def fastfn(outs, mode=None, model=None):
    """Compiles a Theano function which returns `outs` and takes values of model
    vars as a dict as an argument.

    Parameters
    ----------
    outs : Theano variable or iterable of Theano variables
    mode : Theano compilation mode

    Returns
    -------
    Compiled Theano function as point function."""
    model = modelcontext(model)
    return model.fastfn(outs,mode)

def Point(*args, **kwargs):
    """Build a point. Uses same args as dict() does.
    Filters out variables not in the model. All keys are strings.

    Parameters
    ----------
        *args, **kwargs
            arguments to build a dict"""
    model = modelcontext(kwargs.pop('model', None))

    args = [a for a in args]
    try:
        d = dict(*args, **kwargs)
    except Exception as e:
        raise TypeError(
            "can't turn " + str(args) + " and " + str(kwargs) +
            " into a dict. " + str(e))

    varnames = list(map(str, model.vars))
    return dict((str(k), np.array(v))
                for (k, v) in d.items()
                if str(k) in varnames)

class FastPointFunc(object):
    """Wraps so a function so it takes a dict of arguments instead of arguments."""
    def __init__(self, f):
        self.f = f

    def __call__(self, state):
        return self.f(**state)

class LoosePointFunc(object):
    """Wraps so a function so it takes a dict of arguments instead of arguments
    but can still take arguments."""
    def __init__(self, f, model):
        self.f = f
        self.model = model

    def __call__(self, *args, **kwargs):
        point = Point(model=self.model, *args, **kwargs)
        return self.f(**point)

compilef = fastfn

class FreeRV(Factor, TensorVariable):
    """Unobserved random variable that a model is specified in terms of."""
    def __init__(self, type=None, owner=None, index=None, name=None, distribution=None, model=None):
        """
        Parameters
        ----------

        type : theano type (optional)
        owner : theano owner (optional)

        name : str
        distribution : Distribution
        model : Model"""
        if type is None:
            type = distribution.type
        super(FreeRV, self).__init__(type, owner, index, name)

        if distribution is not None:
            self.dshape = tuple(distribution.shape)
            self.dsize = int(np.prod(distribution.shape))
            self.distribution = distribution
            self.tag.test_value = np.ones(
                distribution.shape, distribution.dtype) * distribution.default()
            self.logp_elemwiset = distribution.logp(self)
            self.model = model

def pandas_to_array(data):
    if hasattr(data, 'values'): #pandas
        if data.isnull().any().any(): #missing values
            return np.ma.MaskedArray(data.values, data.isnull().values)
        else:
            return data.values
    elif hasattr(data, 'mask'):
        return data
    else:
        return np.asarray(data)

def as_tensor(data, name,model):
    data = pandas_to_array(data)

    if hasattr(data, 'mask'):
        from .distributions import NoDistribution
        fakedist = NoDistribution.dist(shape=data.mask.sum(), dtype=data.dtype, testval=data.mask.mean().astype(data.dtype))
        missing_values = FreeRV(name=name + '_missing', distribution=fakedist, model=model)

        constant = t.as_tensor_variable(data.filled())

        dataTensor = theano.tensor.set_subtensor(constant[data.mask.nonzero()], missing_values)
        dataTensor.missing_values = missing_values
        return dataTensor
    else:
        data = t.as_tensor_variable(data, name=name)
        data.missing_values = None
        return data

class ObservedRV(Factor, TensorVariable):
    """Observed random variable that a model is specified in terms of.
    Potentially partially observed.
    """
    def __init__(self, type=None, owner=None, index=None, name=None, data=None, distribution=None, model=None):
        """
        Parameters
        ----------

        type : theano type (optional)
        owner : theano owner (optional)

        name : str
        distribution : Distribution
        model : Model
        """
        from .distributions import TensorType
        if type is None:
            data = pandas_to_array(data)
            type = TensorType(data.dtype, data.shape)

        super(TensorVariable, self).__init__(type, None, None, name)

        if distribution is not None:
            data = as_tensor(data, name,model)
            self.missing_values = data.missing_values

            self.logp_elemwiset = distribution.logp(data)
            self.model = model
            self.distribution = distribution

            #make this RV a view on the combined missing/nonmissing array
            theano.gof.Apply(theano.compile.view_op, inputs=[data], outputs=[self])

            self.tag.test_value = theano.compile.view_op(data).tag.test_value

class MultiObservedRV(Factor):
    """Observed random variable that a model is specified in terms of.
    Potentially partially observed.
    """
    def __init__(self, name, data, distribution, model):
        """
        Parameters
        ----------

        type : theano type (optional)
        owner : theano owner (optional)

        name : str
        distribution : Distribution
        model : Model
        """
        self.name = name

        self.data = { name : as_tensor(data, name,model) for name, data in data.items()}

        self.missing_values = [ data.missing_values for data in self.data.values() if data.missing_values is not None]
        self.logp_elemwiset = distribution.logp(**self.data)
        self.model = model
        self.distribution = distribution

def Deterministic(name, var, model=None):
    """Create a named deterministic variable

    Parameters
    ----------
        name : str
        var : theano variables
    Returns
    -------
        n : var but with name name"""
    var.name = name
    modelcontext(model).deterministics.append(var)
    modelcontext(model).add_random_variable(var)
    return var

def Potential(name, var, model=None):
    """Add an arbitrary factor potential to the model likelihood

    Parameters
    ----------
        name : str
        var : theano variables
    Returns
    -------
        var : var, with name attribute
    """

    var.name = name
    modelcontext(model).potentials.append(var)
    return var

class TransformedRV(TensorVariable):
    def __init__(self, type=None, owner=None, index=None, name=None, distribution=None, model=None, transform=None):
        """
        Parameters
        ----------

        type : theano type (optional)
        owner : theano owner (optional)

        name : str
        distribution : Distribution
        model : Model"""
        if type is None:
            type = distribution.type
        super(TransformedRV, self).__init__(type, owner, index, name)

        if distribution is not None:
            self.model = model

            self.transformed = model.Var(name + "_" + transform.name, transform.apply(distribution))

            normalRV = transform.backward(self.transformed)

            theano.Apply(theano.compile.view_op, inputs=[normalRV], outputs=[self])
            self.tag.test_value = normalRV.tag.test_value

def as_iterargs(data):
    if isinstance(data, tuple):
        return data
    else:
        return [data]

# theano stuff
theano.config.warn.sum_div_dimshuffle_bug = False
theano.config.compute_test_value = 'raise'
<|endoftext|>