コード例 #1
0
class VumiUserApi(object):

    conversation_wrapper = ConversationWrapper

    def __init__(self, api, user_account_key):
        # We could get either bytes or unicode here. Decode if necessary.
        if not isinstance(user_account_key, unicode):
            user_account_key = user_account_key.decode('utf8')
        self.api = api
        self.manager = self.api.manager
        self.user_account_key = user_account_key
        self.conversation_store = ConversationStore(self.api.manager,
                                                    self.user_account_key)
        self.contact_store = ContactStore(self.api.manager,
                                          self.user_account_key)
        self.router_store = RouterStore(self.api.manager,
                                        self.user_account_key)
        self.channel_store = ChannelStore(self.api.manager,
                                          self.user_account_key)
        self.optout_store = OptOutStore(self.api.manager,
                                        self.user_account_key)

    def exists(self):
        return self.api.user_exists(self.user_account_key)

    @classmethod
    def from_config_sync(cls, user_account_key, config):
        return cls(VumiApi.from_config_sync(config), user_account_key)

    @classmethod
    def from_config_async(cls, user_account_key, config):
        d = VumiApi.from_config_async(config)
        return d.addCallback(cls, user_account_key)

    def get_user_account(self):
        return self.api.get_user_account(self.user_account_key)

    def wrap_conversation(self, conversation):
        """Wrap a conversation with a ConversationWrapper.

        What it says on the tin, really.

        :param Conversation conversation:
            Conversation object to wrap.
        :rtype:
            ConversationWrapper.
        """
        return self.conversation_wrapper(conversation, self)

    @Manager.calls_manager
    def get_wrapped_conversation(self, conversation_key):
        conversation = yield self.conversation_store.get_conversation_by_key(
            conversation_key)
        if conversation:
            returnValue(self.wrap_conversation(conversation))

    def get_conversation(self, conversation_key):
        return self.conversation_store.get_conversation_by_key(
            conversation_key)

    def get_router(self, router_key):
        return self.router_store.get_router_by_key(router_key)

    @Manager.calls_manager
    def get_channel(self, tag):
        tagpool_meta = yield self.api.tpm.get_metadata(tag[0])
        tag_info = yield self.api.mdb.get_tag_info(tag)
        channel = yield self.channel_store.get_channel_by_tag(
            tag, tagpool_meta, tag_info.current_batch.key)
        returnValue(channel)

    @Manager.calls_manager
    def finished_conversations(self):
        conv_store = self.conversation_store
        keys = yield conv_store.list_conversations()
        conversations = []
        for bunch in conv_store.conversations.load_all_bunches(keys):
            conversations.extend((yield bunch))
        returnValue([c for c in conversations if c.ended()])

    @Manager.calls_manager
    def active_conversations(self):
        keys = yield self.conversation_store.list_active_conversations()
        # NOTE: This assumes that we don't have very large numbers of active
        #       conversations.
        convs = []
        for convs_bunch in self.conversation_store.load_all_bunches(keys):
            convs.extend((yield convs_bunch))
        returnValue(convs)

    @Manager.calls_manager
    def running_conversations(self):
        keys = yield self.conversation_store.list_running_conversations()
        # NOTE: This assumes that we don't have very large numbers of active
        #       conversations.
        convs = []
        for convs_bunch in self.conversation_store.load_all_bunches(keys):
            convs.extend((yield convs_bunch))
        returnValue(convs)

    @Manager.calls_manager
    def draft_conversations(self):
        # TODO: Get rid of this once the old UI finally goes away.
        conversations = yield self.active_conversations()
        returnValue([c for c in conversations if c.is_draft()])

    @Manager.calls_manager
    def active_routers(self):
        keys = yield self.router_store.list_active_routers()
        # NOTE: This assumes that we don't have very large numbers of active
        #       routers.
        routers = []
        for routers_bunch in self.router_store.load_all_bunches(keys):
            routers.extend((yield routers_bunch))
        returnValue(routers)

    @Manager.calls_manager
    def active_channels(self):
        channels = []
        user_account = yield self.get_user_account()
        for tag in user_account.tags:
            channel = yield self.get_channel(tuple(tag))
            channels.append(channel)
        returnValue(channels)

    @Manager.calls_manager
    def tagpools(self):
        user_account = yield self.get_user_account()

        tp_usage = defaultdict(int)
        for tag in user_account.tags:
            tp_usage[tag[0]] += 1

        all_pools = yield self.api.tpm.list_pools()
        allowed_pools = set()
        for tp_bunch in user_account.tagpools.load_all_bunches():
            for tp in (yield tp_bunch):
                if (tp.max_keys is None or tp.max_keys > tp_usage[tp.tagpool]):
                    allowed_pools.add(tp.tagpool)

        available_pools = []
        for pool in all_pools:
            if pool not in allowed_pools:
                continue
            free_tags = yield self.api.tpm.free_tags(pool)
            if free_tags:
                available_pools.append(pool)

        pool_data = dict([(pool, (yield self.api.tpm.get_metadata(pool)))
                          for pool in available_pools])
        returnValue(TagpoolSet(pool_data))

    @Manager.calls_manager
    def applications(self):
        user_account = yield self.get_user_account()
        # NOTE: This assumes that we don't have very large numbers of
        #       applications.
        app_permissions = []
        for permissions in user_account.applications.load_all_bunches():
            app_permissions.extend((yield permissions))
        applications = [
            permission.application for permission in app_permissions
        ]
        app_settings = configured_conversations()
        returnValue(
            SortedDict([(application, app_settings[application])
                        for application in sorted(applications)
                        if application in app_settings]))

    @Manager.calls_manager
    def router_types(self):
        # TODO: Permissions.
        yield None
        router_settings = configured_routers()
        returnValue(
            SortedDict([(router_type, router_settings[router_type])
                        for router_type in sorted(router_settings)]))

    def list_groups(self):
        return self.contact_store.list_groups()

    @Manager.calls_manager
    def new_conversation(self,
                         conversation_type,
                         name,
                         description,
                         config,
                         batch_id=None,
                         **fields):
        if not batch_id:
            batch_id = yield self.api.mdb.batch_start(
                tags=[], user_account=self.user_account_key)
        conv = yield self.conversation_store.new_conversation(
            conversation_type, name, description, config, batch_id, **fields)
        returnValue(conv)

    @Manager.calls_manager
    def new_router(self,
                   router_type,
                   name,
                   description,
                   config,
                   batch_id=None,
                   **fields):
        if not batch_id:
            batch_id = yield self.api.mdb.batch_start(
                tags=[], user_account=self.user_account_key)
        router = yield self.router_store.new_router(router_type, name,
                                                    description, config,
                                                    batch_id, **fields)
        returnValue(router)

    @Manager.calls_manager
    def get_routing_table(self, user_account=None):
        if user_account is None:
            user_account = yield self.get_user_account()
        if user_account.routing_table is None:
            raise VumiError("Routing table missing for account: %s" %
                            (user_account.key, ))
        returnValue(user_account.routing_table)

    @Manager.calls_manager
    def validate_routing_table(self, user_account=None):
        """Check that the routing table on this account is valid.

        Currently we just check account ownership of tags and conversations.

        TODO: Cycle detection, if that's even possible. Maybe other stuff.
        TODO: Determine if this is necessary and move it elsewhere if it is.
        """
        if user_account is None:
            user_account = yield self.get_user_account()
        routing_table = yield self.get_routing_table(user_account)
        # We don't care about endpoints here, only connectors.
        routing_connectors = set()
        for src_conn, _src_ep, dst_conn, _dst_ep in routing_table.entries():
            routing_connectors.add(src_conn)
            routing_connectors.add(dst_conn)

        # Checking tags is cheap and easy, so do that first.
        channels = yield self.active_channels()
        for channel in channels:
            channel_conn = channel.get_connector()
            if channel_conn in routing_connectors:
                routing_connectors.remove(channel_conn)

        # Now we run through active conversations to check those.
        convs = yield self.active_conversations()
        for conv in convs:
            conv_conn = conv.get_connector()
            if conv_conn in routing_connectors:
                routing_connectors.remove(conv_conn)

        if routing_connectors:
            raise VumiError(
                "Routing table contains illegal connector names: %s" %
                (routing_connectors, ))

    @Manager.calls_manager
    def _update_tag_data_for_acquire(self, user_account, tag):
        # The batch we create here gets added to the tag_info and we can fish
        # it out later. When we replace this with proper channel objects we can
        # stash it there like we do with conversations and routers.
        yield self.api.mdb.batch_start([tag], user_account=user_account.key)
        user_account.tags.append(tag)
        tag_info = yield self.api.mdb.get_tag_info(tag)
        tag_info.metadata['user_account'] = user_account.key.decode('utf-8')
        yield tag_info.save()
        yield user_account.save()

    @Manager.calls_manager
    def acquire_tag(self, pool):
        """Acquire a tag from a given tag pool.

        Tags should be held for the duration of a conversation.

        :type pool: str
        :param pool:
            name of the pool to retrieve tags from.
        :rtype:
            The tag acquired or None if no tag was available.
        """
        user_account = yield self.get_user_account()
        if not (yield user_account.has_tagpool_permission(pool)):
            log.warning("Account '%s' trying to access forbidden pool '%s'" %
                        (user_account.key, pool))
            returnValue(None)
        tag = yield self.api.tpm.acquire_tag(pool)
        if tag is not None:
            yield self._update_tag_data_for_acquire(user_account, tag)
        returnValue(tag)

    @Manager.calls_manager
    def acquire_specific_tag(self, tag):
        """Acquire a specific tag.

        Tags should be held for the duration of a conversation.

        :type tag: tag tuple
        :param tag:
            The tag to acquire.
        :rtype:
            The tag acquired or None if the tag was not available.
        """
        user_account = yield self.get_user_account()
        if not (yield user_account.has_tagpool_permission(tag[0])):
            log.warning("Account '%s' trying to access forbidden pool '%s'" %
                        (user_account.key, tag[0]))
            returnValue(None)
        tag = yield self.api.tpm.acquire_specific_tag(tag)
        if tag is not None:
            yield self._update_tag_data_for_acquire(user_account, tag)
        returnValue(tag)

    @Manager.calls_manager
    def release_tag(self, tag):
        """Release a tag back to the pool it came from.

        Tags should be released only once a conversation is finished.

        :type pool: str
        :param pool:
            name of the pool to return the tag too (must be the same as
            the name of the pool the tag came from).
        :rtype:
            None.
        """
        user_account = yield self.get_user_account()
        try:
            user_account.tags.remove(list(tag))
        except ValueError, e:
            log.error("Tag not allocated to account: %s" % (tag, ), e)
        else:
コード例 #2
0
ファイル: api.py プロジェクト: TouK/vumi-go
class VumiUserApi(object):

    conversation_wrapper = ConversationWrapper

    def __init__(self, api, user_account_key):
        # We could get either bytes or unicode here. Decode if necessary.
        if not isinstance(user_account_key, unicode):
            user_account_key = user_account_key.decode('utf8')
        self.api = api
        self.manager = self.api.manager
        self.user_account_key = user_account_key
        self.conversation_store = ConversationStore(self.api.manager,
                                                    self.user_account_key)
        self.contact_store = ContactStore(self.api.manager,
                                          self.user_account_key)
        self.router_store = RouterStore(self.api.manager,
                                        self.user_account_key)
        self.channel_store = ChannelStore(self.api.manager,
                                          self.user_account_key)
        self.optout_store = OptOutStore(self.api.manager,
                                        self.user_account_key)

    def exists(self):
        return self.api.user_exists(self.user_account_key)

    @classmethod
    def from_config_sync(cls, user_account_key, config):
        return cls(VumiApi.from_config_sync(config), user_account_key)

    @classmethod
    def from_config_async(cls, user_account_key, config):
        d = VumiApi.from_config_async(config)
        return d.addCallback(cls, user_account_key)

    def get_user_account(self):
        return self.api.get_user_account(self.user_account_key)

    def wrap_conversation(self, conversation):
        """Wrap a conversation with a ConversationWrapper.

        What it says on the tin, really.

        :param Conversation conversation:
            Conversation object to wrap.
        :rtype:
            ConversationWrapper.
        """
        return self.conversation_wrapper(conversation, self)

    @Manager.calls_manager
    def get_wrapped_conversation(self, conversation_key):
        conversation = yield self.conversation_store.get_conversation_by_key(
            conversation_key)
        if conversation:
            returnValue(self.wrap_conversation(conversation))

    def get_conversation(self, conversation_key):
        return self.conversation_store.get_conversation_by_key(
            conversation_key)

    def get_router(self, router_key):
        return self.router_store.get_router_by_key(router_key)

    @Manager.calls_manager
    def get_channel(self, tag):
        tagpool_meta = yield self.api.tpm.get_metadata(tag[0])
        tag_info = yield self.api.mdb.get_tag_info(tag)
        channel = yield self.channel_store.get_channel_by_tag(
            tag, tagpool_meta, tag_info.current_batch.key)
        returnValue(channel)

    @Manager.calls_manager
    def finished_conversations(self):
        conv_store = self.conversation_store
        keys = yield conv_store.list_conversations()
        conversations = []
        for bunch in conv_store.conversations.load_all_bunches(keys):
            conversations.extend((yield bunch))
        returnValue([c for c in conversations if c.ended()])

    @Manager.calls_manager
    def active_conversations(self):
        keys = yield self.conversation_store.list_active_conversations()
        # NOTE: This assumes that we don't have very large numbers of active
        #       conversations.
        convs = []
        for convs_bunch in self.conversation_store.load_all_bunches(keys):
            convs.extend((yield convs_bunch))
        returnValue(convs)

    @Manager.calls_manager
    def running_conversations(self):
        keys = yield self.conversation_store.list_running_conversations()
        # NOTE: This assumes that we don't have very large numbers of active
        #       conversations.
        convs = []
        for convs_bunch in self.conversation_store.load_all_bunches(keys):
            convs.extend((yield convs_bunch))
        returnValue(convs)

    @Manager.calls_manager
    def draft_conversations(self):
        # TODO: Get rid of this once the old UI finally goes away.
        conversations = yield self.active_conversations()
        returnValue([c for c in conversations if c.is_draft()])

    @Manager.calls_manager
    def active_routers(self):
        keys = yield self.router_store.list_active_routers()
        # NOTE: This assumes that we don't have very large numbers of active
        #       routers.
        routers = []
        for routers_bunch in self.router_store.load_all_bunches(keys):
            routers.extend((yield routers_bunch))
        returnValue(routers)

    @Manager.calls_manager
    def active_channels(self):
        channels = []
        user_account = yield self.get_user_account()
        for tag in user_account.tags:
            channel = yield self.get_channel(tuple(tag))
            channels.append(channel)
        returnValue(channels)

    @Manager.calls_manager
    def tagpools(self):
        user_account = yield self.get_user_account()

        tp_usage = defaultdict(int)
        for tag in user_account.tags:
            tp_usage[tag[0]] += 1

        all_pools = yield self.api.tpm.list_pools()
        allowed_pools = set()
        for tp_bunch in user_account.tagpools.load_all_bunches():
            for tp in (yield tp_bunch):
                if (tp.max_keys is None
                        or tp.max_keys > tp_usage[tp.tagpool]):
                    allowed_pools.add(tp.tagpool)

        available_pools = []
        for pool in all_pools:
            if pool not in allowed_pools:
                continue
            free_tags = yield self.api.tpm.free_tags(pool)
            if free_tags:
                available_pools.append(pool)

        pool_data = dict([(pool, (yield self.api.tpm.get_metadata(pool)))
                          for pool in available_pools])
        returnValue(TagpoolSet(pool_data))

    @Manager.calls_manager
    def applications(self):
        user_account = yield self.get_user_account()
        # NOTE: This assumes that we don't have very large numbers of
        #       applications.
        app_permissions = []
        for permissions in user_account.applications.load_all_bunches():
            app_permissions.extend((yield permissions))
        applications = [permission.application for permission
                            in app_permissions]
        app_settings = configured_conversations()
        returnValue(SortedDict([(application,
                        app_settings[application])
                        for application in sorted(applications)
                        if application in app_settings]))

    @Manager.calls_manager
    def router_types(self):
        # TODO: Permissions.
        yield None
        router_settings = configured_routers()
        returnValue(SortedDict([(router_type, router_settings[router_type])
                                for router_type in sorted(router_settings)]))

    def list_groups(self):
        return self.contact_store.list_groups()

    @Manager.calls_manager
    def new_conversation(self, conversation_type, name, description, config,
                         batch_id=None, **fields):
        if not batch_id:
            batch_id = yield self.api.mdb.batch_start(
                tags=[], user_account=self.user_account_key)
        conv = yield self.conversation_store.new_conversation(
            conversation_type, name, description, config, batch_id, **fields)
        returnValue(conv)

    @Manager.calls_manager
    def new_router(self, router_type, name, description, config,
                   batch_id=None, **fields):
        if not batch_id:
            batch_id = yield self.api.mdb.batch_start(
                tags=[], user_account=self.user_account_key)
        router = yield self.router_store.new_router(
            router_type, name, description, config, batch_id, **fields)
        returnValue(router)

    @Manager.calls_manager
    def get_routing_table(self, user_account=None):
        if user_account is None:
            user_account = yield self.get_user_account()
        if user_account.routing_table is None:
            raise VumiError(
                "Routing table missing for account: %s" % (user_account.key,))
        returnValue(user_account.routing_table)

    @Manager.calls_manager
    def validate_routing_table(self, user_account=None):
        """Check that the routing table on this account is valid.

        Currently we just check account ownership of tags and conversations.

        TODO: Cycle detection, if that's even possible. Maybe other stuff.
        TODO: Determine if this is necessary and move it elsewhere if it is.
        """
        if user_account is None:
            user_account = yield self.get_user_account()
        routing_table = yield self.get_routing_table(user_account)
        # We don't care about endpoints here, only connectors.
        routing_connectors = set()
        for src_conn, _src_ep, dst_conn, _dst_ep in routing_table.entries():
            routing_connectors.add(src_conn)
            routing_connectors.add(dst_conn)

        # Checking tags is cheap and easy, so do that first.
        channels = yield self.active_channels()
        for channel in channels:
            channel_conn = channel.get_connector()
            if channel_conn in routing_connectors:
                routing_connectors.remove(channel_conn)

        # Now we run through active conversations to check those.
        convs = yield self.active_conversations()
        for conv in convs:
            conv_conn = conv.get_connector()
            if conv_conn in routing_connectors:
                routing_connectors.remove(conv_conn)

        if routing_connectors:
            raise VumiError(
                "Routing table contains illegal connector names: %s" % (
                    routing_connectors,))

    @Manager.calls_manager
    def _update_tag_data_for_acquire(self, user_account, tag):
        # The batch we create here gets added to the tag_info and we can fish
        # it out later. When we replace this with proper channel objects we can
        # stash it there like we do with conversations and routers.
        yield self.api.mdb.batch_start([tag], user_account=user_account.key)
        user_account.tags.append(tag)
        tag_info = yield self.api.mdb.get_tag_info(tag)
        tag_info.metadata['user_account'] = user_account.key.decode('utf-8')
        yield tag_info.save()
        yield user_account.save()

    @Manager.calls_manager
    def acquire_tag(self, pool):
        """Acquire a tag from a given tag pool.

        Tags should be held for the duration of a conversation.

        :type pool: str
        :param pool:
            name of the pool to retrieve tags from.
        :rtype:
            The tag acquired or None if no tag was available.
        """
        user_account = yield self.get_user_account()
        if not (yield user_account.has_tagpool_permission(pool)):
            log.warning("Account '%s' trying to access forbidden pool '%s'" % (
                user_account.key, pool))
            returnValue(None)
        tag = yield self.api.tpm.acquire_tag(pool)
        if tag is not None:
            yield self._update_tag_data_for_acquire(user_account, tag)
        returnValue(tag)

    @Manager.calls_manager
    def acquire_specific_tag(self, tag):
        """Acquire a specific tag.

        Tags should be held for the duration of a conversation.

        :type tag: tag tuple
        :param tag:
            The tag to acquire.
        :rtype:
            The tag acquired or None if the tag was not available.
        """
        user_account = yield self.get_user_account()
        if not (yield user_account.has_tagpool_permission(tag[0])):
            log.warning("Account '%s' trying to access forbidden pool '%s'" % (
                user_account.key, tag[0]))
            returnValue(None)
        tag = yield self.api.tpm.acquire_specific_tag(tag)
        if tag is not None:
            yield self._update_tag_data_for_acquire(user_account, tag)
        returnValue(tag)

    @Manager.calls_manager
    def release_tag(self, tag):
        """Release a tag back to the pool it came from.

        Tags should be released only once a conversation is finished.

        :type pool: str
        :param pool:
            name of the pool to return the tag too (must be the same as
            the name of the pool the tag came from).
        :rtype:
            None.
        """
        user_account = yield self.get_user_account()
        try:
            user_account.tags.remove(list(tag))
        except ValueError, e:
            log.error("Tag not allocated to account: %s" % (tag,), e)
        else: