<commit_before>from django.conf.urls import url
from django.db.models import Max, Q
from django.http import HttpRequest
from django.utils.datetime_safe import datetime
from django.views.generic import View
from yunity.api.ids import chat_id_uri_pattern, user_id_uri_pattern

from yunity.api.utils import ApiBase, model_to_json, body_as_json
from yunity.models.concrete import Chat as ChatModel
from yunity.models.concrete import Message as MessageModel
from yunity.models.concrete import User as UserModel

def user_to_json(user):
    return model_to_json(user, 'id')

def message_to_json(message):
    return model_to_json(message, 'sender', 'created_at', 'type', 'content')

def chat_to_json(chat):
    return model_to_json(chat, 'id')

def chat_from(userids):
    participants = UserModel.objects \
        .filter(id__in=userids) \
        .all()
    chat = ChatModel.objects.create()
    chat.participants = participants
    return chat

def user_has_rights_to_chat(chatid, userid):
    return ChatModel.objects \
        .filter(id=chatid) \
        .filter(Q(participants=userid) | Q(administrated_by=userid)) \
        .exists()

class Chats(ApiBase, View):
    def get(self, request):
        """List all chats in which the currently logged in user is involved.
        The chats are in descending order of the most recent message time; this means that the first element of the
        chats list is the chat with the most recent activity. The most recent message is included.
        ---
        tags:
            - Chat
        responses:
            200:
                description: A list of all the chats the logged in user is involved.
                schema:
                    type: object
                    properties:
                        chats:
                            type: array
                            items:
                                $ref: '#/definitions/chat'
        ...

        :type request: HttpRequest

        """
        chats = ChatModel.objects \
            .filter(participants=request.user.id) \
            .annotate(most_recent_message=Max('messages__created_at')) \
            .order_by('-most_recent_message') \
            .values('id')

        return self.success({'chats': [chat_to_json(_) for _ in chats]})

    @body_as_json(expected_keys=['participants', 'message'])
    def post(self, request):
        """Create a new chat involving participants and add the first message.
        ---
        tags:
            - Chat
        parameters:
            - in: body
              name: body
              schema:
                  id: create_chat
                  required:
                    - message
                    - participants
                  properties:
                      message:
                          $ref: '#/definitions/send_message'
                      participants:
                          type: array
                          example: [1, 742, 23]
                          description: List of participants that will be added to the chat
                          items:
                              type: integer
        responses:
            201:
                description: Chat created
                schema:
                    id: chat
                    type: object
                    properties:
                      id:
                        type: number
                        description: Identifier of the chat
                        example: 3
                      name:
                        type: string
                        example: Planning of next party
                        description: Custom name for the chat
                      participants:
                        type: array
                        items:
                            type: integer
                        example: [1, 87, 633, 234]
                        description: The list of userids participating in the chat
                      message:
                          $ref: '#/definitions/message'

            403:
                description: The logged in user is not part of the conversation
        ...

        :type request: HttpRequest
        :rtype JsonResponse

        """
        participant_ids = request.body['participants']
        if request.user.id not in participant_ids:
            return self.forbidden("User can only create chat including self")
        chat = chat_from(participant_ids)
        return self.created({'id': chat.id})

class Chat(ApiBase, View):
    @body_as_json(expected_keys=['name'])
    def put(self, request, chatid):
        """Update details about chat chatid

        :type request: HttpRequest
        :type chatid: int

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden({"reason": 'user does not have rights to chat'})
        chat_name = request.body['name']
        chat = ChatModel.objects.get(id=chatid)
        chat.name = chat_name

        return self.success({'name': chat.name})

class ChatMessages(ApiBase, View):
    @body_as_json(expected_keys=['type', 'content'])
    def post(self, request, chatid):
        """ Send a new message in given chat
        ---
        tags:
            - Chat
        parameters:
            - in: path
              name: chatid
              description: ID of chat
              type: integer
            - in: body
              name: body
              schema:
                  id: send_message
                  required:
                    - type
                    - content
                  properties:
                      type:
                          type: string
                          enum: [TEXT, IMAGE]
                          description: Type of this message
                      content:
                          type: string
                          example: Hi Peter, how are you?
                          description: Content, e.g. utf8-formatted plain text or image id received by the upload endpoint
        responses:
            201:
                description: Chat message added
                schema:
                    id: message
                    type: object
                    properties:
                        typ:
                            type: string
                            enum: [TEXT, IMAGE]
                            description: Type of this message
                        content:
                            type: string
                            example: Hi Peter, how are you?
                            description: Content, e.g. utf8-formatted plain text or image id received by the upload endpoint
                        sender:
                            type: integer
                            description: Userid of sender of message
                            example: 82
                        created_at:
                            type: string
                            description: ISO 8601 formatted timestring in UTC timezone (YYYY-MM-DDTHH:MM:SS)
                            example: 2007-12-24T18:21:00.003423
                        id:
                            type: integer
                            description: Unique identifier of this message
                            example: 124624

            403:
                description: The logged in user is not part of the conversation
        ...

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden({'reason': 'user does not have rights to chat'})
        type_str = request.body['type']
        if type_str not in ['TEXT', 'IMAGE']:
            return self.error({'reason': 'invalid type'})
        content = request.body['content']
        sender = UserModel.objects.get(id=request.user.id)
        created_at = datetime.datetime.now()

        chat = ChatModel.objects.get(id=chatid)
        message = MessageModel.objects.create(
                sent_by=sender,
                in_conversation=chat,
                created_at=created_at,
                type=type_str,
                content=content)

        return self.success(message_to_json(message))

    def get(self, request, chatid):
        """Retrieve all the messages in the given chat, sorted descending by time (most recent first).
        ---
        tags:
            - Chat
        parameters:
            - in: path
              name: chatid
              description: ID of chat
              type: integer
            - in: query
              name: take
              description: Number of messages to retrieve
              type: integer
            - in: query
              name: before_id
              description: The newest retrieved message is the first earlier one than this ID
              type: integer
        responses:
            201:
                description: Result message set
                schema:
                    id: result_messages
                    type: object
                    properties:
                        messages:
                            type: array
                            items:
                                $ref: '#/definitions/message'

            403:
                description: The logged in user is not part of the conversation
        ...

        :type request: HttpRequest
        :type chatid: int

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden('user does not have rights to chat')

        messages = MessageModel.objects \
            .filter(in_conversation=chatid) \
            .reverse() \
            .all()

        return self.success({'messages': [message_to_json(_) for _ in messages]})

class ChatParticipants(ApiBase, View):
    @body_as_json(expected_keys=['users'])
    def post(self, request, chatid):
        """Add a list of users to the chat.
        ---
        tags:
            - Chat
        parameters:
            - in: path
              name: chatid
              description: ID of chat
              type: integer
            - in: body
              name: body
              schema:
                  id: chatusers
                  required:
                      - users
                  properties:
                      users:
                          type: array
                          description: List of users to add to this specific chat
                          example: [1, 5, 8]
                          items:
                              type: number
        responses:
            201:
                description: All users added
            400:
                description: At least one user does not exist, none added
        ...

        request_json:
            users:
                type: list
                required: true
                description: a list of ids of users to remove

        :type request: HttpRequest
        :type chatid: int

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden('user does not have rights to chat')

        users_to_add = UserModel.objects \
            .filter(id=request.body['users']) \
            .all()

        ChatModel.objects \
            .get(id=chatid) \
            .participants \
            .add(users_to_add)

        return self.success()

class ChatParticipant(ApiBase, View):
    def delete(self, request, chatid, userid):
        """Remove a user from the chat.

        url_parameter:
            userid:
                type: integer
                required: true
                description: the id of the user to remove

        :type request: HttpRequest
        :type chatid: int
        :type userid: int

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden('user does not have rights to chat')

        ChatModel.objects \
            .get(id=chatid) \
            .filter(participants=userid) \
            .delete()

        return self.success()

"""
* DELETE /chat/{chatId}/participants/{userId} -- delete a user from the chat
* POST /chat/{chatId}/participants -- add a new user to the chat

* POST /chat/{chatid}/messages -- add a new message to chat

* PUT /chat/{chatid} -- change chat details
* POST /chat/ -- create a new chat with some users
* GET /chat/{chatId}/messages -- return list of messages in chat
* GET /chat/
"""

urlpatterns = [
    url(r'^$', Chats.as_view()), # GET, POST
    url(r'^{chatid}/?$'.format(chatid=chat_id_uri_pattern), Chat.as_view()), # PUT
    url(r'^{chatid}/messages/?$'.format(chatid=chat_id_uri_pattern), ChatMessages.as_view()), # GET, POST
    url(r'^{chatid}/participants/?$'.format(chatid=chat_id_uri_pattern), ChatParticipants.as_view()), #POST
    url(r'^{chatid}/participants/{userid}/?$'.format(chatid=chat_id_uri_pattern, userid=user_id_uri_pattern),
        ChatParticipant.as_view()), # DELETE
]
<commit_msg>Fix bad indentation<commit_after>from django.conf.urls import url
from django.db.models import Max, Q
from django.http import HttpRequest
from django.utils.datetime_safe import datetime
from django.views.generic import View
from yunity.api.ids import chat_id_uri_pattern, user_id_uri_pattern

from yunity.api.utils import ApiBase, model_to_json, body_as_json
from yunity.models.concrete import Chat as ChatModel
from yunity.models.concrete import Message as MessageModel
from yunity.models.concrete import User as UserModel

def user_to_json(user):
    return model_to_json(user, 'id')

def message_to_json(message):
    return model_to_json(message, 'sender', 'created_at', 'type', 'content')

def chat_to_json(chat):
    return model_to_json(chat, 'id')

def chat_from(userids):
    participants = UserModel.objects \
        .filter(id__in=userids) \
        .all()
    chat = ChatModel.objects.create()
    chat.participants = participants
    return chat

def user_has_rights_to_chat(chatid, userid):
    return ChatModel.objects \
        .filter(id=chatid) \
        .filter(Q(participants=userid) | Q(administrated_by=userid)) \
        .exists()

class Chats(ApiBase, View):
    def get(self, request):
        """List all chats in which the currently logged in user is involved.
        The chats are in descending order of the most recent message time; this means that the first element of the
        chats list is the chat with the most recent activity. The most recent message is included.
        ---
        tags:
            - Chat
        responses:
            200:
                description: A list of all the chats the logged in user is involved.
                schema:
                    type: object
                    properties:
                        chats:
                            type: array
                            items:
                                $ref: '#/definitions/chat'
        ...

        :type request: HttpRequest

        """
        chats = ChatModel.objects \
            .filter(participants=request.user.id) \
            .annotate(most_recent_message=Max('messages__created_at')) \
            .order_by('-most_recent_message') \
            .values('id')

        return self.success({'chats': [chat_to_json(_) for _ in chats]})

    @body_as_json(expected_keys=['participants', 'message'])
    def post(self, request):
        """Create a new chat involving participants and add the first message.
        ---
        tags:
            - Chat
        parameters:
            - in: body
              name: body
              schema:
                  id: create_chat
                  required:
                    - message
                    - participants
                  properties:
                      message:
                          $ref: '#/definitions/send_message'
                      participants:
                          type: array
                          example: [1, 742, 23]
                          description: List of participants that will be added to the chat
                          items:
                              type: integer
        responses:
            201:
                description: Chat created
                schema:
                    id: chat
                    type: object
                    properties:
                      id:
                        type: number
                        description: Identifier of the chat
                        example: 3
                      name:
                        type: string
                        example: Planning of next party
                        description: Custom name for the chat
                      participants:
                        type: array
                        items:
                            type: integer
                        example: [1, 87, 633, 234]
                        description: The list of userids participating in the chat
                      message:
                          $ref: '#/definitions/message'

            403:
                description: The logged in user is not part of the conversation
        ...

        :type request: HttpRequest
        :rtype JsonResponse

        """
        participant_ids = request.body['participants']
        if request.user.id not in participant_ids:
            return self.forbidden("User can only create chat including self")
        chat = chat_from(participant_ids)
        return self.created({'id': chat.id})

class Chat(ApiBase, View):
    @body_as_json(expected_keys=['name'])
    def put(self, request, chatid):
        """Update details about chat chatid

        :type request: HttpRequest
        :type chatid: int

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden({"reason": 'user does not have rights to chat'})
        chat_name = request.body['name']
        chat = ChatModel.objects.get(id=chatid)
        chat.name = chat_name

        return self.success({'name': chat.name})

class ChatMessages(ApiBase, View):
    @body_as_json(expected_keys=['type', 'content'])
    def post(self, request, chatid):
        """ Send a new message in given chat
        ---
        tags:
            - Chat
        parameters:
            - in: path
              name: chatid
              description: ID of chat
              type: integer
            - in: body
              name: body
              schema:
                  id: send_message
                  required:
                    - type
                    - content
                  properties:
                      type:
                          type: string
                          enum: [TEXT, IMAGE]
                          description: Type of this message
                      content:
                          type: string
                          example: Hi Peter, how are you?
                          description: Content, e.g. utf8-formatted plain text or image id received by the upload endpoint
        responses:
            201:
                description: Chat message added
                schema:
                    id: message
                    type: object
                    properties:
                        typ:
                            type: string
                            enum: [TEXT, IMAGE]
                            description: Type of this message
                        content:
                            type: string
                            example: Hi Peter, how are you?
                            description: Content, e.g. utf8-formatted plain text or image id received by the upload endpoint
                        sender:
                            type: integer
                            description: Userid of sender of message
                            example: 82
                        created_at:
                            type: string
                            description: ISO 8601 formatted timestring in UTC timezone (YYYY-MM-DDTHH:MM:SS)
                            example: 2007-12-24T18:21:00.003423
                        id:
                            type: integer
                            description: Unique identifier of this message
                            example: 124624

            403:
                description: The logged in user is not part of the conversation
        ...

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden({'reason': 'user does not have rights to chat'})
        type_str = request.body['type']
        if type_str not in ['TEXT', 'IMAGE']:
            return self.error({'reason': 'invalid type'})
        content = request.body['content']
        sender = UserModel.objects.get(id=request.user.id)
        created_at = datetime.datetime.now()

        chat = ChatModel.objects.get(id=chatid)
        message = MessageModel.objects.create(
            sent_by=sender,
            in_conversation=chat,
            created_at=created_at,
            type=type_str,
            content=content,
        )

        return self.success(message_to_json(message))

    def get(self, request, chatid):
        """Retrieve all the messages in the given chat, sorted descending by time (most recent first).
        ---
        tags:
            - Chat
        parameters:
            - in: path
              name: chatid
              description: ID of chat
              type: integer
            - in: query
              name: take
              description: Number of messages to retrieve
              type: integer
            - in: query
              name: before_id
              description: The newest retrieved message is the first earlier one than this ID
              type: integer
        responses:
            201:
                description: Result message set
                schema:
                    id: result_messages
                    type: object
                    properties:
                        messages:
                            type: array
                            items:
                                $ref: '#/definitions/message'

            403:
                description: The logged in user is not part of the conversation
        ...

        :type request: HttpRequest
        :type chatid: int

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden('user does not have rights to chat')

        messages = MessageModel.objects \
            .filter(in_conversation=chatid) \
            .reverse() \
            .all()

        return self.success({'messages': [message_to_json(_) for _ in messages]})

class ChatParticipants(ApiBase, View):
    @body_as_json(expected_keys=['users'])
    def post(self, request, chatid):
        """Add a list of users to the chat.
        ---
        tags:
            - Chat
        parameters:
            - in: path
              name: chatid
              description: ID of chat
              type: integer
            - in: body
              name: body
              schema:
                  id: chatusers
                  required:
                      - users
                  properties:
                      users:
                          type: array
                          description: List of users to add to this specific chat
                          example: [1, 5, 8]
                          items:
                              type: number
        responses:
            201:
                description: All users added
            400:
                description: At least one user does not exist, none added
        ...

        request_json:
            users:
                type: list
                required: true
                description: a list of ids of users to remove

        :type request: HttpRequest
        :type chatid: int

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden('user does not have rights to chat')

        users_to_add = UserModel.objects \
            .filter(id=request.body['users']) \
            .all()

        ChatModel.objects \
            .get(id=chatid) \
            .participants \
            .add(users_to_add)

        return self.success()

class ChatParticipant(ApiBase, View):
    def delete(self, request, chatid, userid):
        """Remove a user from the chat.

        url_parameter:
            userid:
                type: integer
                required: true
                description: the id of the user to remove

        :type request: HttpRequest
        :type chatid: int
        :type userid: int

        """
        if not user_has_rights_to_chat(chatid, request.user.id):
            return self.forbidden('user does not have rights to chat')

        ChatModel.objects \
            .get(id=chatid) \
            .filter(participants=userid) \
            .delete()

        return self.success()

"""
* DELETE /chat/{chatId}/participants/{userId} -- delete a user from the chat
* POST /chat/{chatId}/participants -- add a new user to the chat

* POST /chat/{chatid}/messages -- add a new message to chat

* PUT /chat/{chatid} -- change chat details
* POST /chat/ -- create a new chat with some users
* GET /chat/{chatId}/messages -- return list of messages in chat
* GET /chat/
"""

urlpatterns = [
    url(r'^$', Chats.as_view()), # GET, POST
    url(r'^{chatid}/?$'.format(chatid=chat_id_uri_pattern), Chat.as_view()), # PUT
    url(r'^{chatid}/messages/?$'.format(chatid=chat_id_uri_pattern), ChatMessages.as_view()), # GET, POST
    url(r'^{chatid}/participants/?$'.format(chatid=chat_id_uri_pattern), ChatParticipants.as_view()), #POST
    url(r'^{chatid}/participants/{userid}/?$'.format(chatid=chat_id_uri_pattern, userid=user_id_uri_pattern),
        ChatParticipant.as_view()), # DELETE
]
<|endoftext|>


<commit_before># System documented in https://zulip.readthedocs.io/en/latest/subsystems/logging.html

import logging
import os

from collections import defaultdict
from django.conf import settings
from django.core.mail import mail_admins
from django.http import HttpResponse
from django.utils.translation import ugettext as _
from typing import Any, Dict, Text

from zerver.models import get_system_bot
from zerver.lib.actions import internal_send_message
from zerver.lib.response import json_success, json_error
from version import ZULIP_VERSION

def format_subject(subject):
    # type: (str) -> str
    """
    Escape CR and LF characters.
    """
    return subject.replace('\n', '\\n').replace('\r', '\\r')

def user_info_str(report):
    # type: (Dict[str, Any]) -> str
    if report['user_full_name'] and report['user_email']:
        user_info = "%(user_full_name)s (%(user_email)s)" % (report)
    else:
        user_info = "Anonymous user (not logged in)"

    user_info += " on %s deployment"  % (report['deployment'],)
    return user_info

def deployment_repr() -> str:
    deployment = 'Deployed code:\n'
    deployment += '- ZULIP_VERSION: %s\n' % (ZULIP_VERSION,)
    version_path = os.path.join(os.path.dirname(__file__), '../../version')
    if os.path.exists(version_path):
        deployment += '- version: %s\n' % (open(version_path).read().strip(),)
    return deployment

def notify_browser_error(report):
    # type: (Dict[str, Any]) -> None
    report = defaultdict(lambda: None, report)
    if settings.ERROR_BOT:
        zulip_browser_error(report)
    email_browser_error(report)

def email_browser_error(report):
    # type: (Dict[str, Any]) -> None
    subject = "Browser error for %s" % (user_info_str(report))

    body = ("User: %(user_full_name)s <%(user_email)s> on %(deployment)s\n\n"
            "Message:\n%(message)s\n\nStacktrace:\n%(stacktrace)s\n\n"
            "IP address: %(ip_address)s\n"
            "User agent: %(user_agent)s\n"
            "href: %(href)s\n"
            "Server path: %(server_path)s\n"
            "Deployed version: %(version)s\n"
            % (report))

    more_info = report['more_info']
    if more_info is not None:
        body += "\nAdditional information:"
        for (key, value) in more_info.items():
            body += "\n  %s: %s" % (key, value)

    body += "\n\nLog:\n%s" % (report['log'],)

    mail_admins(subject, body)

def zulip_browser_error(report):
    # type: (Dict[str, Any]) -> None
    subject = "JS error: %s" % (report['user_email'],)

    user_info = user_info_str(report)

    body = "User: %s\n" % (user_info,)
    body += ("Message: %(message)s\n"
             % (report))

    realm = get_system_bot(settings.ERROR_BOT).realm
    internal_send_message(realm, settings.ERROR_BOT,
                          "stream", "errors", format_subject(subject), body)

def notify_server_error(report):
    # type: (Dict[str, Any]) -> None
    report = defaultdict(lambda: None, report)
    email_server_error(report)
    if settings.ERROR_BOT:
        zulip_server_error(report)

def zulip_server_error(report):
    # type: (Dict[str, Any]) -> None
    subject = '%(node)s: %(message)s' % (report)
    stack_trace = report['stack_trace'] or "No stack trace available"

    user_info = user_info_str(report)
    deployment = deployment_repr()

    request_repr = (
        "Request info:\n~~~~\n"
        "- path: %(path)s\n"
        "- %(method)s: %(data)s\n") % (report)

    for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
        request_repr += "- %s: \"%s\"\n" % (field, report.get(field.lower()))
    request_repr += "~~~~"

    realm = get_system_bot(settings.ERROR_BOT).realm
    internal_send_message(realm, settings.ERROR_BOT,
                          "stream", "errors", format_subject(subject),
                          "Error generated by %s\n\n~~~~ pytb\n%s\n\n~~~~\n%s\n%s"
                          % (user_info, stack_trace, deployment, request_repr))

def email_server_error(report):
    # type: (Dict[str, Any]) -> None
    subject = '%(node)s: %(message)s' % (report)

    user_info = user_info_str(report)
    deployment = deployment_repr()

    request_repr = (
        "Request info:\n"
        "- path: %(path)s\n"
        "- %(method)s: %(data)s\n") % (report)

    for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
        request_repr += "- %s: \"%s\"\n" % (field, report.get(field.lower()))

    message = ("Error generated by %s\n\n%s\n\n%s\n\n%s"
               % (user_info, report['stack_trace'], deployment, request_repr))

    mail_admins(format_subject(subject), message, fail_silently=True)

def do_report_error(deployment_name, type, report):
    # type: (Text, Text, Dict[str, Any]) -> HttpResponse
    report['deployment'] = deployment_name
    if type == 'browser':
        notify_browser_error(report)
    elif type == 'server':
        notify_server_error(report)
    else:
        return json_error(_("Invalid type parameter"))
    return json_success()
<commit_msg>error_notify: Try `git describe` too.<commit_after># System documented in https://zulip.readthedocs.io/en/latest/subsystems/logging.html

import logging
import os
import subprocess

from collections import defaultdict
from django.conf import settings
from django.core.mail import mail_admins
from django.http import HttpResponse
from django.utils.translation import ugettext as _
from typing import Any, Dict, Optional, Text

from zerver.models import get_system_bot
from zerver.lib.actions import internal_send_message
from zerver.lib.response import json_success, json_error
from version import ZULIP_VERSION

def format_subject(subject):
    # type: (str) -> str
    """
    Escape CR and LF characters.
    """
    return subject.replace('\n', '\\n').replace('\r', '\\r')

def user_info_str(report):
    # type: (Dict[str, Any]) -> str
    if report['user_full_name'] and report['user_email']:
        user_info = "%(user_full_name)s (%(user_email)s)" % (report)
    else:
        user_info = "Anonymous user (not logged in)"

    user_info += " on %s deployment"  % (report['deployment'],)
    return user_info

def try_git_describe() -> Optional[str]:
    try:
        return subprocess.check_output(
            ['git',
             '--git-dir', os.path.join(os.path.dirname(__file__), '../../.git'),
             'describe', '--tags', '--dirty', '--long'],
            stderr=subprocess.PIPE,
        ).strip().decode('utf-8')
    except Exception:
        return None

def deployment_repr() -> str:
    deployment = 'Deployed code:\n'

    git_described = try_git_describe()
    if git_described is not None:
        deployment += '- git: %s\n' % (git_described,)

    deployment += '- ZULIP_VERSION: %s\n' % (ZULIP_VERSION,)

    version_path = os.path.join(os.path.dirname(__file__), '../../version')
    if os.path.exists(version_path):
        deployment += '- version: %s\n' % (open(version_path).read().strip(),)

    return deployment

def notify_browser_error(report):
    # type: (Dict[str, Any]) -> None
    report = defaultdict(lambda: None, report)
    if settings.ERROR_BOT:
        zulip_browser_error(report)
    email_browser_error(report)

def email_browser_error(report):
    # type: (Dict[str, Any]) -> None
    subject = "Browser error for %s" % (user_info_str(report))

    body = ("User: %(user_full_name)s <%(user_email)s> on %(deployment)s\n\n"
            "Message:\n%(message)s\n\nStacktrace:\n%(stacktrace)s\n\n"
            "IP address: %(ip_address)s\n"
            "User agent: %(user_agent)s\n"
            "href: %(href)s\n"
            "Server path: %(server_path)s\n"
            "Deployed version: %(version)s\n"
            % (report))

    more_info = report['more_info']
    if more_info is not None:
        body += "\nAdditional information:"
        for (key, value) in more_info.items():
            body += "\n  %s: %s" % (key, value)

    body += "\n\nLog:\n%s" % (report['log'],)

    mail_admins(subject, body)

def zulip_browser_error(report):
    # type: (Dict[str, Any]) -> None
    subject = "JS error: %s" % (report['user_email'],)

    user_info = user_info_str(report)

    body = "User: %s\n" % (user_info,)
    body += ("Message: %(message)s\n"
             % (report))

    realm = get_system_bot(settings.ERROR_BOT).realm
    internal_send_message(realm, settings.ERROR_BOT,
                          "stream", "errors", format_subject(subject), body)

def notify_server_error(report):
    # type: (Dict[str, Any]) -> None
    report = defaultdict(lambda: None, report)
    email_server_error(report)
    if settings.ERROR_BOT:
        zulip_server_error(report)

def zulip_server_error(report):
    # type: (Dict[str, Any]) -> None
    subject = '%(node)s: %(message)s' % (report)
    stack_trace = report['stack_trace'] or "No stack trace available"

    user_info = user_info_str(report)
    deployment = deployment_repr()

    request_repr = (
        "Request info:\n~~~~\n"
        "- path: %(path)s\n"
        "- %(method)s: %(data)s\n") % (report)

    for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
        request_repr += "- %s: \"%s\"\n" % (field, report.get(field.lower()))
    request_repr += "~~~~"

    realm = get_system_bot(settings.ERROR_BOT).realm
    internal_send_message(realm, settings.ERROR_BOT,
                          "stream", "errors", format_subject(subject),
                          "Error generated by %s\n\n~~~~ pytb\n%s\n\n~~~~\n%s\n%s"
                          % (user_info, stack_trace, deployment, request_repr))

def email_server_error(report):
    # type: (Dict[str, Any]) -> None
    subject = '%(node)s: %(message)s' % (report)

    user_info = user_info_str(report)
    deployment = deployment_repr()

    request_repr = (
        "Request info:\n"
        "- path: %(path)s\n"
        "- %(method)s: %(data)s\n") % (report)

    for field in ["REMOTE_ADDR", "QUERY_STRING", "SERVER_NAME"]:
        request_repr += "- %s: \"%s\"\n" % (field, report.get(field.lower()))

    message = ("Error generated by %s\n\n%s\n\n%s\n\n%s"
               % (user_info, report['stack_trace'], deployment, request_repr))

    mail_admins(format_subject(subject), message, fail_silently=True)

def do_report_error(deployment_name, type, report):
    # type: (Text, Text, Dict[str, Any]) -> HttpResponse
    report['deployment'] = deployment_name
    if type == 'browser':
        notify_browser_error(report)
    elif type == 'server':
        notify_server_error(report)
    else:
        return json_error(_("Invalid type parameter"))
    return json_success()
<|endoftext|>


<commit_before># This file is a part of zone_normalize: A package to parse, normalize
# and show DNS zone data
#
# Copyright (C) 2016 Max R.D. Parmer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from collections import OrderedDict
from typing import Dict, Iterator, Iterable, Tuple

# semver
__version__ = "0.9.0-alpha"

# should be exhaustive
RECORDCLASSES = ['ch', 'in', 'hs', 'cs']
# non-exhaustive
RECORDTYPES = ['a',
               'aaaa',
               'dnskey',
               'rrsig',
               'mx',
               'ns',
               'nsec',
               'nsec3',
               'nsec3param',
               'rrsig',
               'txt']
# Basic format for a zone and data
ZONE_FMT_STR = "{0[origin]} {0[ttl]} {0[class]} {0[type]} {1}"

def normalize_data(line: str, default_values: Dict[str, str]) -> [str]:
    line_chunks = line.split()

    # replace @ with origin
    if line_chunks[0] == '@':
        line_chunks[0] = default_values['origin']

    # A two field [type, data] entry, needs three fields added
    if line_chunks[0] in RECORDTYPES:
        line_chunks.insert(0, default_values['class'])
        line_chunks.insert(0, default_values['ttl'])
        line_chunks.insert(0, default_values['origin'])
    # probably a three field record begining with TTL
    elif line_chunks[0].isnumeric() and not line_chunks[0].endswith('.'):
        line_chunks.insert(0, default_values['origin'])

    if not line_chunks[0].endswith('.'):
        line_chunks[0] += '.' + default_values['origin']

    # if the first char of the first field isn't numeric and the second is
    # a class, inject a ttl field
    if line_chunks[1] in RECORDCLASSES \
            and not line_chunks[0][0].isnumeric():
        line_chunks.insert(1, default_values['ttl'])
    # if the second field is a known record type inject a ttl and class
    elif line_chunks[1] in RECORDTYPES:
        line_chunks.insert(1, default_values['class'])
        line_chunks.insert(1, default_values['ttl'])

    if line_chunks[1].isnumeric() and line_chunks[2] in RECORDTYPES:
        line_chunks.insert(2, default_values['class'])

    return line_chunks

def split_comments(line: str) -> Tuple[str, str]:
    semicolon = line.index(';')
    return (line[0:semicolon].strip(), line[semicolon+1:].strip())

def set_defaults(line: str, default_values: Dict[str, str]) -> Dict[str, str]:
    var_name, var_value = tuple(line[1:].split())
    var_name = var_name
    default_values[var_name] = var_value
    return default_values

def set_flags(line: str, multiline: bool) -> Tuple[bool, bool, bool]:
    comments = False
    end_of_multiline = False

    for token in line:
        if not multiline and token == '(':
            multiline = True
        if multiline and token == ')':
            end_of_multiline = True
        if token == ';':
            comments = True
    return (comments, multiline, end_of_multiline)

def zone_dict_to_str(record: OrderedDict, fmt_str: str=ZONE_FMT_STR) -> str:
    return fmt_str.format(record, " ".join(record['data']))

def zone_normalize(zone_file: Iterable, def_class="in", ttl="900") -> Iterator:
    default_values = {'class': def_class,
                      'ttl': ttl}
    multiline = False
    multiline_str = ''
    for line in zone_file:
        line = line.strip().lower()
        record = OrderedDict()  # type: OrderedDict[str, str]

        if line.startswith('$'):
            default_values = set_defaults(line, default_values)
            continue

        comments, multiline, end_of_multiline = set_flags(line, multiline)
        if comments:
            line, _ = split_comments(line)

        if not line:
            continue

        if multiline:
            multiline_str += "{} ".format(line)
            if not end_of_multiline:
                continue
            else:
                line = multiline_str
                multiline_str = ''
                multiline = False

        if line:
            line_chunks = normalize_data(line, default_values)
            # try to name the fields
            record['origin'], record['ttl'], record['class'], record['type'] \
                = tuple(line_chunks[:4])
            # the rest of the data goes here and needs a type specific parser
            record['data'] = line_chunks[4:]

            if 'origin' not in default_values:
                default_values['origin'] = record['origin']
            default_values['ttl'] = record['ttl']

        yield record
<commit_msg>Clarifies some flow control grouping.<commit_after># This file is a part of zone_normalize: A package to parse, normalize
# and show DNS zone data
#
# Copyright (C) 2016 Max R.D. Parmer
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from collections import OrderedDict
from typing import Dict, Iterator, Iterable, Tuple

# semver
__version__ = "0.9.0-alpha"

# should be exhaustive
RECORDCLASSES = ['ch', 'in', 'hs', 'cs']
# non-exhaustive
RECORDTYPES = ['a',
               'aaaa',
               'dnskey',
               'rrsig',
               'mx',
               'ns',
               'nsec',
               'nsec3',
               'nsec3param',
               'rrsig',
               'txt']
# Basic format for a zone and data
ZONE_FMT_STR = "{0[origin]} {0[ttl]} {0[class]} {0[type]} {1}"

def normalize_data(line: str, default_values: Dict[str, str]) -> [str]:
    line_chunks = line.split()

    # replace @ with origin
    if line_chunks[0] == '@':
        line_chunks[0] = default_values['origin']

    # A two field [type, data] entry, needs three fields added
    if line_chunks[0] in RECORDTYPES:
        line_chunks.insert(0, default_values['class'])
        line_chunks.insert(0, default_values['ttl'])
        line_chunks.insert(0, default_values['origin'])
    # probably a three field record begining with TTL
    elif line_chunks[0].isnumeric() and not line_chunks[0].endswith('.'):
        line_chunks.insert(0, default_values['origin'])

    if not line_chunks[0].endswith('.'):
        line_chunks[0] += '.' + default_values['origin']

    # if the first char of the first field isn't numeric and the second is
    # a class, inject a ttl field
    if line_chunks[1] in RECORDCLASSES \
            and not line_chunks[0][0].isnumeric():
        line_chunks.insert(1, default_values['ttl'])
    # if the second field is a known record type inject a ttl and class
    elif line_chunks[1] in RECORDTYPES:
        line_chunks.insert(1, default_values['class'])
        line_chunks.insert(1, default_values['ttl'])

    if line_chunks[1].isnumeric() and line_chunks[2] in RECORDTYPES:
        line_chunks.insert(2, default_values['class'])

    return line_chunks

def split_comments(line: str) -> Tuple[str, str]:
    semicolon = line.index(';')
    return (line[0:semicolon].strip(), line[semicolon+1:].strip())

def set_defaults(line: str, default_values: Dict[str, str]) -> Dict[str, str]:
    var_name, var_value = tuple(line[1:].split())
    var_name = var_name
    default_values[var_name] = var_value
    return default_values

def set_flags(line: str, multiline: bool) -> Tuple[bool, bool, bool]:
    comments = False
    end_of_multiline = False

    for token in line:
        if not multiline and token == '(':
            multiline = True
        if multiline and token == ')':
            end_of_multiline = True
        if token == ';':
            comments = True
    return (comments, multiline, end_of_multiline)

def zone_dict_to_str(record: OrderedDict, fmt_str: str=ZONE_FMT_STR) -> str:
    return fmt_str.format(record, " ".join(record['data']))

def zone_normalize(zone_file: Iterable, def_class="in", ttl="900") -> Iterator:
    default_values = {'class': def_class,
                      'ttl': ttl}
    multiline = False
    multiline_str = ''
    for line in zone_file:
        line = line.strip().lower()
        record = OrderedDict()  # type: OrderedDict[str, str]

        if line.startswith('$'):
            default_values = set_defaults(line, default_values)
            continue

        comments, multiline, end_of_multiline = set_flags(line, multiline)
        if comments:
            line, _ = split_comments(line)

        if line:
            if multiline:
                multiline_str += "{} ".format(line)
                if not end_of_multiline:
                    continue
                else:
                    line = multiline_str
                    multiline_str = ''
                    multiline = False

            line_chunks = normalize_data(line, default_values)

            # try to name the fields
            record['origin'], record['ttl'], record['class'], record['type'] \
                = tuple(line_chunks[:4])
            # the rest of the data goes here and needs a type specific parser
            record['data'] = line_chunks[4:]

            if 'origin' not in default_values:
                default_values['origin'] = record['origin']
            default_values['ttl'] = record['ttl']
        else:
            continue

        yield record
<|endoftext|>


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

from django.core.management import call_command
from django.test import TestCase
from io import StringIO
from tests import testapp_with_no_models_file

from unittest.mock import Mock, patch

TEMPLATETAGS_DIR = os.path.join(testapp_with_no_models_file.__path__[0], 'templatetags')

class CreateTemplateTagsTests(TestCase):
    """Tests for create_template_tags command."""

    def tearDown(self):
        """Remove templatetags directory after each test."""
        try:
            shutil.rmtree(TEMPLATETAGS_DIR)
        except OSError:
            pass

    def test_should_create_testapp_with_no_models_file_tags_file(self):
        call_command('create_template_tags', 'testapp_with_no_models_file')
        self.assertTrue(os.path.isfile(os.path.join(TEMPLATETAGS_DIR, 'testapp_with_no_models_file_tags.py')))

    def test_should_create_custom__name_tags_file(self):
        call_command('create_template_tags', 'testapp_with_no_models_file', '--name', 'custom_name_tags')
        self.assertTrue(os.path.isfile(os.path.join(TEMPLATETAGS_DIR, 'custom_name_tags.py')))

    @patch('sys.stderr', new_callable=StringIO)
    def test_should_print_error_notice_on_OSError(self, m_stderr):
        m_shutil = Mock()
        m_shutil.copymode.side_effect = OSError
        with patch.dict('sys.modules', shutil=m_shutil):
            call_command('create_template_tags', 'testapp_with_no_models_file')

        self.assertRegex(
            m_stderr.getvalue(),
            r"Notice: Couldn't set permission bits on \S+ You're probably using an uncommon filesystem setup. No problem.",
        )
<commit_msg>fix mocking shutil.copymode for test<commit_after># -*- coding: utf-8 -*-
import os
import shutil

from django.core.management import call_command
from django.test import TestCase
from io import StringIO
from tests import testapp_with_no_models_file

from unittest.mock import patch

TEMPLATETAGS_DIR = os.path.join(testapp_with_no_models_file.__path__[0], 'templatetags')

class CreateTemplateTagsTests(TestCase):
    """Tests for create_template_tags command."""

    def tearDown(self):
        """Remove templatetags directory after each test."""
        try:
            shutil.rmtree(TEMPLATETAGS_DIR)
        except OSError:
            pass

    def test_should_create_testapp_with_no_models_file_tags_file(self):
        call_command('create_template_tags', 'testapp_with_no_models_file')
        self.assertTrue(os.path.isfile(os.path.join(TEMPLATETAGS_DIR, 'testapp_with_no_models_file_tags.py')))

    def test_should_create_custom__name_tags_file(self):
        call_command('create_template_tags', 'testapp_with_no_models_file', '--name', 'custom_name_tags')
        self.assertTrue(os.path.isfile(os.path.join(TEMPLATETAGS_DIR, 'custom_name_tags.py')))

    @patch('sys.stderr', new_callable=StringIO)
    @patch('shutil.copymode', side_effect=OSError)
    def test_should_print_error_notice_on_OSError(self, m_copymode, m_stderr):
        call_command('create_template_tags', 'testapp_with_no_models_file')

        self.assertRegex(
            m_stderr.getvalue(),
            r"Notice: Couldn't set permission bits on \S+ You're probably using an uncommon filesystem setup. No problem.",
        )
<|endoftext|>


<commit_before>from django.db import models

class Building(models.Model):
    name = models.CharField(max_length=10)

    def __unicode__(self):
        return u"Building: %s" % self.name

class Device(models.Model):
    building = models.ForeignKey('Building')
    name = models.CharField(max_length=10)

    def __unicode__(self):
        return u"device '%s' in building %s" % (self.name, self.building)

class Port(models.Model):
    device = models.ForeignKey('Device')
    number = models.CharField(max_length=10)

    def __unicode__(self):
        return u"%s/%s" % (self.device.name, self.number)

class Connection(models.Model):
    start = models.ForeignKey(Port, related_name='connection_start',
            unique=True)
    end = models.ForeignKey(Port, related_name='connection_end', unique=True)

    def __unicode__(self):
        return u"%s to %s" % (self.start, self.end)

__test__ = {'API_TESTS': """
Regression test for bug #7110. When using select_related(), we must query the
Device and Building tables using two different aliases (each) in order to
differentiate the start and end Connection fields. The net result is that both
the "connections = ..." queries here should give the same results.

>>> b=Building.objects.create(name='101')
>>> dev1=Device.objects.create(name="router", building=b)
>>> dev2=Device.objects.create(name="switch", building=b)
>>> dev3=Device.objects.create(name="server", building=b)
>>> port1=Port.objects.create(number='4',device=dev1)
>>> port2=Port.objects.create(number='7',device=dev2)
>>> port3=Port.objects.create(number='1',device=dev3)
>>> c1=Connection.objects.create(start=port1, end=port2)
>>> c2=Connection.objects.create(start=port2, end=port3)

>>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).order_by('id')
>>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections]
[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]

>>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).select_related().order_by('id')
>>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections]
[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]

# This final query should only join seven tables (port, device and building
# twice each, plus connection once).
>>> connections.query.count_active_tables()
7

"""}
<commit_msg>Renamed a field in a test case that was preventing the tests from  running in Oracle.<commit_after>from django.db import models

class Building(models.Model):
    name = models.CharField(max_length=10)

    def __unicode__(self):
        return u"Building: %s" % self.name

class Device(models.Model):
    building = models.ForeignKey('Building')
    name = models.CharField(max_length=10)

    def __unicode__(self):
        return u"device '%s' in building %s" % (self.name, self.building)

class Port(models.Model):
    device = models.ForeignKey('Device')
    port_number = models.CharField(max_length=10)

    def __unicode__(self):
        return u"%s/%s" % (self.device.name, self.port_number)

class Connection(models.Model):
    start = models.ForeignKey(Port, related_name='connection_start',
            unique=True)
    end = models.ForeignKey(Port, related_name='connection_end', unique=True)

    def __unicode__(self):
        return u"%s to %s" % (self.start, self.end)

__test__ = {'API_TESTS': """
Regression test for bug #7110. When using select_related(), we must query the
Device and Building tables using two different aliases (each) in order to
differentiate the start and end Connection fields. The net result is that both
the "connections = ..." queries here should give the same results.

>>> b=Building.objects.create(name='101')
>>> dev1=Device.objects.create(name="router", building=b)
>>> dev2=Device.objects.create(name="switch", building=b)
>>> dev3=Device.objects.create(name="server", building=b)
>>> port1=Port.objects.create(port_number='4',device=dev1)
>>> port2=Port.objects.create(port_number='7',device=dev2)
>>> port3=Port.objects.create(port_number='1',device=dev3)
>>> c1=Connection.objects.create(start=port1, end=port2)
>>> c2=Connection.objects.create(start=port2, end=port3)

>>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).order_by('id')
>>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections]
[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]

>>> connections=Connection.objects.filter(start__device__building=b, end__device__building=b).select_related().order_by('id')
>>> [(c.id, unicode(c.start), unicode(c.end)) for c in connections]
[(1, u'router/4', u'switch/7'), (2, u'switch/7', u'server/1')]

# This final query should only join seven tables (port, device and building
# twice each, plus connection once).
>>> connections.query.count_active_tables()
7

"""}
<|endoftext|>


<commit_before>#! /usr/bin/env python
# ----------------------------------------------------------------------
# Numenta Platform for Intelligent Computing (NuPIC)
# Copyright (C) 2013, Numenta, Inc.  Unless you have an agreement
# with Numenta, Inc., for a separate license for this software code, the
# following terms and conditions apply:
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with this program.  If not, see http://www.gnu.org/licenses.
#
# http://numenta.org/licenses/
# ----------------------------------------------------------------------

import time
import numpy
import unittest2 as unittest

from nupic.support.unittesthelpers.algorithm_test_helpers \
     import CreateSP
from nupic.bindings.math import GetNTAReal

uintType = "uint32"

# set a single seed for running both implementations
SEED = int((time.time()%10000)*10)

def _computeOverlap(x, y):
  """
  Given two binary arrays, compute their overlap. The overlap is the number
  of bits where x[i] and y[i] are both 1
  """
  return ((x + y) == 2).sum()

def _areAllSDRsUnique(sdrDict):
  """Return True iff all the SDR's in the dict are unique."""
  for k1, v1 in sdrDict.iteritems():
    for k2, v2 in sdrDict.iteritems():
      # Return false if two different keys have identical SDR's
      if (k2 != k1) and ((v1 == v2).sum() == v1.size):
        return False

  return True

class SpatialPoolerBoostTest(unittest.TestCase):
  """
  Test boosting.

  The test is constructed as follows: we construct a set of 5 known inputs. Two
  of the input patterns have 50% overlap while all other combinations have 0%
  overlap. Each input pattern has 20 bits on to ensure reasonable overlap with
  almost all columns.

  SP parameters:  The SP is set to have 600 columns with 10% output sparsity.
  This ensures that the 5 inputs cannot use up all the columns. Yet we still can h
  ave a reasonable number of winning columns at each step in order to test
  overlap properties. maxBoost is set to 10 so that some boosted columns are
  guaranteed to win eventually but not necessarily quickly. potentialPct is set
  to 0.9 to ensure all columns have at least some overlap with at least one
  input bit. Thus, when sufficiently boosted, every column should become a
  winner at some point. We set permanence increment and decrement to 0 so that
  winning columns don't change unless they have been boosted.

  Phase 1: As learning progresses through the first 5 iterations, the first 5
  patterns should get distinct output SDRs. The two overlapping input patterns
  should have reasonably overlapping output SDRs. The other pattern
  combinations should have very little overlap. The boost factor for all
  columns should be at 1. At this point at least half of the columns should have
  never become active and these columns should have duty cycle of 0. Any
  columns which have won, should have duty cycles >= 0.2.

  Phase 2: Over the next 45 iterations, boosting should stay at 1 for all
  columns since minActiveDutyCycle is only calculated after 50 iterations. The
  winning columns should be very similar (identical?) to the columns that won
  before. About half of the columns should never become active. At the end of
  the this phase, most of these columns should have activity level around 0.2.
  It's ok for some columns to have higher activity levels.

  Phase 3: At this point about half or fewer columns have never won. These
  should get boosted to maxBoost and start to win. As each one wins, their
  boost gets lowered to 1. After 2 batches, the number of columns that
  have never won should be 0.  Because of the artificially induced thrashing
  behavior in this test, all the inputs should now have pretty distinct
  patterns. During this process, as soon as a new column wins, the boost value
  for that column should be set back to 1.

  Phase 4: Run for 5 iterations without learning on. Boost values and winners
  should not change.
  """

  def setUp(self):
    """
    Set various constants. Create the input patterns and the spatial pooler
    """
    self.inputSize = 90
    self.columnDimensions = 600

    # Create a set of input vectors, x
    # B,C,D don't overlap at all with other patterns
    self.x = numpy.zeros((5, self.inputSize), dtype=uintType)
    self.x[0, 0:20]  = 1   # Input pattern A
    self.x[1, 10:30] = 1   # Input pattern A' (half the bits overlap with A)
    self.x[2, 30:50] = 1   # Input pattern B  (no overlap with others)
    self.x[3, 50:70] = 1   # Input pattern C  (no overlap with others)
    self.x[4, 70:90] = 1   # Input pattern D  (no overlap with others)

    # For each column, this will contain the last iteration number where that
    # column was a winner
    self.winningIteration = numpy.zeros(self.columnDimensions)

    # For each input vector i, lastSDR[i] contains the most recent SDR output
    # by the SP.
    self.lastSDR = {}

    self.spImplementation = "None"

    self.sp = None

    # Setup the SP creation parameters we will use
    self.params = {
      'inputDimensions':            [self.inputSize],
      'columnDimensions':           [self.columnDimensions],
      'potentialRadius':            self.inputSize,
      'potentialPct':               0.9,
      'globalInhibition':           True,
      'numActiveColumnsPerInhArea': 60,
      'minPctActiveDutyCycle':      0.1,
      'synPermActiveInc':           0.0,
      'synPermInactiveDec':         0.0,
      'dutyCyclePeriod':            10,
      'maxBoost':                   10.0,
      'seed':                       SEED,
    }
    print "SP seed set to:", self.params['seed']

  def debugPrint(self):
    """
    Helpful debug print statements while debugging this test.
    """

    minDutyCycle = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
    self.sp.getMinActiveDutyCycles(minDutyCycle)

    activeDutyCycle = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
    self.sp.getActiveDutyCycles(activeDutyCycle)

    boost = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
    self.sp.getBoostFactors(boost)
    print "\n--------- ITERATION", (
      self.sp.getIterationNum() ),"-----------------------"
    print "SP implementation:", self.spImplementation
    print "Learning iteration:",
    print "minactiveDutyCycle (lower cycles cause boosting to start):",(
      minDutyCycle[0] )
    print "Max/min active duty cycle:", (
      activeDutyCycle.max(), activeDutyCycle.min() )
    print "Average non-zero active duty cycle:", (
      activeDutyCycle[activeDutyCycle>0].mean() )
    print "Active duty cycle", activeDutyCycle
    print
    print "Boost factor for sp:", boost
    print
    print "Last winning iteration for each column"
    print self.winningIteration
    print "Number of columns that have won at some point:", (
      self.columnDimensions - (self.winningIteration==0).sum() )

  def verifySDRProperties(self):
    """
    Verify that all SDRs have the properties desired for this test.

    The bounds for checking overlap are set fairly loosely here since there is
    some variance due to randomness and the artificial parameters used in this
    test.
    """
    # Verify that all SDR's are unique
    self.assertTrue(_areAllSDRsUnique(self.lastSDR), "All SDR's are not unique")

    # Verify that the first two SDR's have some overlap.
    self.assertGreater(_computeOverlap(self.lastSDR[0], self.lastSDR[1]), 9,
                       "First two SDR's don't overlap much")

    # Verify the last three SDR's have low overlap with everyone else.
    for i in [2, 3, 4]:
      for j in range(5):
        if (i!=j):
          self.assertLess(_computeOverlap(self.lastSDR[i], self.lastSDR[j]),
                          18, "One of the last three SDRs has high overlap")

  def boostTestPhase1(self):

    y = numpy.zeros(self.columnDimensions, dtype = uintType)
    self.sp._maxBoost = 1
    # Do one training batch through the input patterns
    for idx, v in enumerate(self.x):
      y.fill(0)
      self.sp.compute(v, True, y)
      self.winningIteration[y.nonzero()[0]] = self.sp.getIterationLearnNum()
      self.lastSDR[idx] = y.copy()

    # The boost factor for all columns should be at 1.
    boost = numpy.zeros(self.columnDimensions, dtype = GetNTAReal())
    self.sp.getBoostFactors(boost)
    self.assertEqual((boost==1).sum(), self.columnDimensions,
      "Boost factors are not all 1")

    # At least half of the columns should have never been active.
    self.assertGreaterEqual((self.winningIteration==0).sum(),
      self.columnDimensions/2, "More than half of the columns have been active")

    # All the never-active columns should have duty cycle of 0
    # All the at-least-once-active columns should have duty cycle >= 0.2
    dutyCycles = numpy.zeros(self.columnDimensions, dtype = GetNTAReal())
    self.sp.getActiveDutyCycles(dutyCycles)
    self.assertEqual(dutyCycles[self.winningIteration == 0].sum(), 0,
                     "Inactive columns have positive duty cycle.")
    self.assertGreaterEqual(dutyCycles[self.winningIteration > 0].min(),
                            0.2,
                            "Active columns have duty cycle that is too low.")

    self.verifySDRProperties()

  def boostTestPhase2(self):

    y = numpy.zeros(self.columnDimensions, dtype = uintType)
    boost = numpy.zeros(self.columnDimensions, dtype = GetNTAReal())

    # Do 9 training batch through the input patterns
    for _ in range(9):
      for idx, v in enumerate(self.x):
        y.fill(0)
        self.sp.compute(v, True, y)
        self.winningIteration[y.nonzero()[0]] = self.sp.getIterationLearnNum()
        self.lastSDR[idx] = y.copy()

        # The boost factor for all columns should be at 1.
        self.sp.getBoostFactors(boost)
        self.assertEqual((boost==1).sum(), self.columnDimensions,
          "Boost factors are not all 1")

    # Roughly half of the columns should have never been active.
    self.assertGreaterEqual((self.winningIteration==0).sum(),
      0.4*self.columnDimensions,
      "More than 60% of the columns have been active")

    # All the never-active columns should have duty cycle of 0
    dutyCycles = numpy.zeros(self.columnDimensions, dtype = GetNTAReal())
    self.sp.getActiveDutyCycles(dutyCycles)
    self.assertEqual(dutyCycles[self.winningIteration == 0].sum(), 0,
                     "Inactive columns have positive duty cycle.")

    # The average at-least-once-active columns should have duty cycle >= 0.15
    # and <= 0.30
    avg = dutyCycles[dutyCycles>0].mean()
    self.assertGreaterEqual(avg, 0.15,
                "Average on-columns duty cycle is too low.")
    self.assertLessEqual(avg, 0.30,
                "Average on-columns duty cycle is too high.")

    self.verifySDRProperties()

  def boostTestPhase3(self):
    self.sp._maxBoost = 10
    # Do two more training batches through the input patterns
    y = numpy.zeros(self.columnDimensions, dtype = uintType)
    for _ in range(2):
      for idx, v in enumerate(self.x):
        y.fill(0)
        self.sp.compute(v, True, y)
        self.winningIteration[y.nonzero()[0]] = self.sp.getIterationLearnNum()
        self.lastSDR[idx] = y.copy()

    # By now, every column should have been sufficiently boosted to win at least
    # once. The number of columns that have never won should now be 0
    numLosersAfter = (self.winningIteration==0).sum()
    self.assertEqual(numLosersAfter, 0)

    # Because of the artificially induced thrashing, even the first two patterns
    # should have low overlap. Verify that the first two SDR's now have little
    # overlap
    self.assertLess(_computeOverlap(self.lastSDR[0], self.lastSDR[1]), 7,
                       "First two SDR's overlap significantly when they "
                       "shouldn't")

  def boostTestPhase4(self):

    # The boost factor for all columns that just won should be at 1.
    boostAtBeg = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
    self.sp.getBoostFactors(boostAtBeg)

    # Do one more iteration through the input patterns with learning OFF
    y = numpy.zeros(self.columnDimensions, dtype=uintType)
    for _, v in enumerate(self.x):
      y.fill(0)
      self.sp.compute(v, False, y)

      # The boost factor for all columns that just won should be at 1.
      boost = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
      self.sp.getBoostFactors(boost)
      self.assertEqual(boost.sum(), boostAtBeg.sum(),
        "Boost factors changed when learning is off")

  def boostTestLoop(self, imp):
    """Main test loop."""
    self.sp = CreateSP(imp, self.params)
    self.spImplementation = imp
    self.winningIteration.fill(0)
    self.lastSDR = {}

    self.boostTestPhase1()
    self.boostTestPhase2()
    self.boostTestPhase3()
    self.boostTestPhase4()

  def testBoostingPY(self):
    self.boostTestLoop("py")

  # def testBoostingCPP(self):
  #   self.boostTestLoop("cpp")

if __name__ == "__main__":
  unittest.main()
<commit_msg>update docs for spatial_pooler_boost_test<commit_after>#! /usr/bin/env python
# ----------------------------------------------------------------------
# Numenta Platform for Intelligent Computing (NuPIC)
# Copyright (C) 2013, Numenta, Inc.  Unless you have an agreement
# with Numenta, Inc., for a separate license for this software code, the
# following terms and conditions apply:
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU Affero Public License for more details.
#
# You should have received a copy of the GNU Affero Public License
# along with this program.  If not, see http://www.gnu.org/licenses.
#
# http://numenta.org/licenses/
# ----------------------------------------------------------------------

import time
import numpy
import unittest2 as unittest

from nupic.support.unittesthelpers.algorithm_test_helpers \
     import CreateSP
from nupic.bindings.math import GetNTAReal

uintType = "uint32"

# set a single seed for running both implementations
SEED = int((time.time()%10000)*10)

def _computeOverlap(x, y):
  """
  Given two binary arrays, compute their overlap. The overlap is the number
  of bits where x[i] and y[i] are both 1
  """
  return ((x + y) == 2).sum()

def _areAllSDRsUnique(sdrDict):
  """Return True iff all the SDR's in the dict are unique."""
  for k1, v1 in sdrDict.iteritems():
    for k2, v2 in sdrDict.iteritems():
      # Return false if two different keys have identical SDR's
      if (k2 != k1) and ((v1 == v2).sum() == v1.size):
        return False

  return True

class SpatialPoolerBoostTest(unittest.TestCase):
  """
  Test boosting.

  The test is constructed as follows: we construct a set of 5 known inputs. Two
  of the input patterns have 50% overlap while all other combinations have 0%
  overlap. Each input pattern has 20 bits on to ensure reasonable overlap with
  almost all columns.

  SP parameters:  The SP is set to have 600 columns with 10% output sparsity.
  This ensures that the 5 inputs cannot use up all the columns. Yet we still can
  have a reasonable number of winning columns at each step in order to test
  overlap properties. maxBoost is set to 10 so that some boosted columns are
  guaranteed to win eventually but not necessarily quickly. potentialPct is set
  to 0.9 to ensure all columns have at least some overlap with at least one
  input bit. Thus, when sufficiently boosted, every column should become a
  winner at some point. We set permanence increment and decrement to 0 so that
  winning columns don't change unless they have been boosted.

  maxBoost is set to 1.0 for Phase 1-2 and 10.0 for Phase 3-4

  Phase 1: As learning progresses through the first 5 iterations, the first 5
  patterns should get distinct output SDRs. The two overlapping input patterns
  should have reasonably overlapping output SDRs. The other pattern
  combinations should have very little overlap. The boost factor for all
  columns should be at 1. At this point at least half of the columns should have
  never become active and these columns should have duty cycle of 0. Any
  columns which have won, should have duty cycles >= 0.2.

  Phase 2: Over the next 45 iterations, boosting should stay at 1 for all
  columns. The winning columns should be very similar (identical?) to the columns
  that won before. About half of the columns should never become active.
  At the end of the this phase, most of these columns should have activity level
  around 0.2. It's ok for some columns to have higher activity levels.

  Phase 3: At this point about half or fewer columns have never won. These
  should get boosted and start to win. After 2 batches, the number of columns
  that have never won should be 0.  Because of the artificially induced thrashing
  behavior in this test, all the inputs should now have pretty distinct
  patterns. During this process, as soon as a new column wins, the boost value
  for that column should be set back to 1.

  Phase 4: Run for 5 iterations without learning on. Boost values and winners
  should not change.
  """

  def setUp(self):
    """
    Set various constants. Create the input patterns and the spatial pooler
    """
    self.inputSize = 90
    self.columnDimensions = 600

    # Create a set of input vectors, x
    # B,C,D don't overlap at all with other patterns
    self.x = numpy.zeros((5, self.inputSize), dtype=uintType)
    self.x[0, 0:20]  = 1   # Input pattern A
    self.x[1, 10:30] = 1   # Input pattern A' (half the bits overlap with A)
    self.x[2, 30:50] = 1   # Input pattern B  (no overlap with others)
    self.x[3, 50:70] = 1   # Input pattern C  (no overlap with others)
    self.x[4, 70:90] = 1   # Input pattern D  (no overlap with others)

    # For each column, this will contain the last iteration number where that
    # column was a winner
    self.winningIteration = numpy.zeros(self.columnDimensions)

    # For each input vector i, lastSDR[i] contains the most recent SDR output
    # by the SP.
    self.lastSDR = {}

    self.spImplementation = "None"

    self.sp = None

    # Setup the SP creation parameters we will use
    self.params = {
      'inputDimensions':            [self.inputSize],
      'columnDimensions':           [self.columnDimensions],
      'potentialRadius':            self.inputSize,
      'potentialPct':               0.9,
      'globalInhibition':           True,
      'numActiveColumnsPerInhArea': 60,
      'minPctActiveDutyCycle':      0.1,
      'synPermActiveInc':           0.0,
      'synPermInactiveDec':         0.0,
      'dutyCyclePeriod':            10,
      'maxBoost':                   10.0,
      'seed':                       SEED,
    }
    print "SP seed set to:", self.params['seed']

  def debugPrint(self):
    """
    Helpful debug print statements while debugging this test.
    """

    minDutyCycle = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
    self.sp.getMinActiveDutyCycles(minDutyCycle)

    activeDutyCycle = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
    self.sp.getActiveDutyCycles(activeDutyCycle)

    boost = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
    self.sp.getBoostFactors(boost)
    print "\n--------- ITERATION", (
      self.sp.getIterationNum() ),"-----------------------"
    print "SP implementation:", self.spImplementation
    print "Learning iteration:",
    print "minactiveDutyCycle (lower cycles cause boosting to start):",(
      minDutyCycle[0] )
    print "Max/min active duty cycle:", (
      activeDutyCycle.max(), activeDutyCycle.min() )
    print "Average non-zero active duty cycle:", (
      activeDutyCycle[activeDutyCycle>0].mean() )
    print "Active duty cycle", activeDutyCycle
    print
    print "Boost factor for sp:", boost
    print
    print "Last winning iteration for each column"
    print self.winningIteration
    print "Number of columns that have won at some point:", (
      self.columnDimensions - (self.winningIteration==0).sum() )

  def verifySDRProperties(self):
    """
    Verify that all SDRs have the properties desired for this test.

    The bounds for checking overlap are set fairly loosely here since there is
    some variance due to randomness and the artificial parameters used in this
    test.
    """
    # Verify that all SDR's are unique
    self.assertTrue(_areAllSDRsUnique(self.lastSDR), "All SDR's are not unique")

    # Verify that the first two SDR's have some overlap.
    self.assertGreater(_computeOverlap(self.lastSDR[0], self.lastSDR[1]), 9,
                       "First two SDR's don't overlap much")

    # Verify the last three SDR's have low overlap with everyone else.
    for i in [2, 3, 4]:
      for j in range(5):
        if (i!=j):
          self.assertLess(_computeOverlap(self.lastSDR[i], self.lastSDR[j]),
                          18, "One of the last three SDRs has high overlap")

  def boostTestPhase1(self):

    y = numpy.zeros(self.columnDimensions, dtype = uintType)
    self.sp._maxBoost = 1
    # Do one training batch through the input patterns
    for idx, v in enumerate(self.x):
      y.fill(0)
      self.sp.compute(v, True, y)
      self.winningIteration[y.nonzero()[0]] = self.sp.getIterationLearnNum()
      self.lastSDR[idx] = y.copy()

    # The boost factor for all columns should be at 1.
    boost = numpy.zeros(self.columnDimensions, dtype = GetNTAReal())
    self.sp.getBoostFactors(boost)
    self.assertEqual((boost==1).sum(), self.columnDimensions,
      "Boost factors are not all 1")

    # At least half of the columns should have never been active.
    self.assertGreaterEqual((self.winningIteration==0).sum(),
      self.columnDimensions/2, "More than half of the columns have been active")

    # All the never-active columns should have duty cycle of 0
    # All the at-least-once-active columns should have duty cycle >= 0.2
    dutyCycles = numpy.zeros(self.columnDimensions, dtype = GetNTAReal())
    self.sp.getActiveDutyCycles(dutyCycles)
    self.assertEqual(dutyCycles[self.winningIteration == 0].sum(), 0,
                     "Inactive columns have positive duty cycle.")
    self.assertGreaterEqual(dutyCycles[self.winningIteration > 0].min(),
                            0.2,
                            "Active columns have duty cycle that is too low.")

    self.verifySDRProperties()

  def boostTestPhase2(self):

    y = numpy.zeros(self.columnDimensions, dtype = uintType)
    boost = numpy.zeros(self.columnDimensions, dtype = GetNTAReal())

    # Do 9 training batch through the input patterns
    for _ in range(9):
      for idx, v in enumerate(self.x):
        y.fill(0)
        self.sp.compute(v, True, y)
        self.winningIteration[y.nonzero()[0]] = self.sp.getIterationLearnNum()
        self.lastSDR[idx] = y.copy()

        # The boost factor for all columns should be at 1.
        self.sp.getBoostFactors(boost)
        self.assertEqual((boost==1).sum(), self.columnDimensions,
          "Boost factors are not all 1")

    # Roughly half of the columns should have never been active.
    self.assertGreaterEqual((self.winningIteration==0).sum(),
      0.4*self.columnDimensions,
      "More than 60% of the columns have been active")

    # All the never-active columns should have duty cycle of 0
    dutyCycles = numpy.zeros(self.columnDimensions, dtype = GetNTAReal())
    self.sp.getActiveDutyCycles(dutyCycles)
    self.assertEqual(dutyCycles[self.winningIteration == 0].sum(), 0,
                     "Inactive columns have positive duty cycle.")

    # The average at-least-once-active columns should have duty cycle >= 0.15
    # and <= 0.30
    avg = dutyCycles[dutyCycles>0].mean()
    self.assertGreaterEqual(avg, 0.15,
                "Average on-columns duty cycle is too low.")
    self.assertLessEqual(avg, 0.30,
                "Average on-columns duty cycle is too high.")

    self.verifySDRProperties()

  def boostTestPhase3(self):
    self.sp._maxBoost = 10
    # Do two more training batches through the input patterns
    y = numpy.zeros(self.columnDimensions, dtype = uintType)
    for _ in range(2):
      for idx, v in enumerate(self.x):
        y.fill(0)
        self.sp.compute(v, True, y)
        self.winningIteration[y.nonzero()[0]] = self.sp.getIterationLearnNum()
        self.lastSDR[idx] = y.copy()

    # By now, every column should have been sufficiently boosted to win at least
    # once. The number of columns that have never won should now be 0
    numLosersAfter = (self.winningIteration==0).sum()
    self.assertEqual(numLosersAfter, 0)

    # Because of the artificially induced thrashing, even the first two patterns
    # should have low overlap. Verify that the first two SDR's now have little
    # overlap
    self.assertLess(_computeOverlap(self.lastSDR[0], self.lastSDR[1]), 7,
                       "First two SDR's overlap significantly when they "
                       "shouldn't")

  def boostTestPhase4(self):

    # The boost factor for all columns that just won should be at 1.
    boostAtBeg = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
    self.sp.getBoostFactors(boostAtBeg)

    # Do one more iteration through the input patterns with learning OFF
    y = numpy.zeros(self.columnDimensions, dtype=uintType)
    for _, v in enumerate(self.x):
      y.fill(0)
      self.sp.compute(v, False, y)

      # The boost factor for all columns that just won should be at 1.
      boost = numpy.zeros(self.columnDimensions, dtype=GetNTAReal())
      self.sp.getBoostFactors(boost)
      self.assertEqual(boost.sum(), boostAtBeg.sum(),
        "Boost factors changed when learning is off")

  def boostTestLoop(self, imp):
    """Main test loop."""
    self.sp = CreateSP(imp, self.params)
    self.spImplementation = imp
    self.winningIteration.fill(0)
    self.lastSDR = {}

    self.boostTestPhase1()
    self.boostTestPhase2()
    self.boostTestPhase3()
    self.boostTestPhase4()

  def testBoostingPY(self):
    self.boostTestLoop("py")

  # def testBoostingCPP(self):
  #   self.boostTestLoop("cpp")

if __name__ == "__main__":
  unittest.main()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'openstackdocstheme',
    'reno.sphinxext',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'puppet-trove Release Notes'
copyright = u'2017, Puppet OpenStack Developers'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '12.0.0'
# The full version, including alpha/beta/rc tags.
release = '12.0.0-dev'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'openstackdocs'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'puppet-troveReleaseNotesdoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
  ('index', 'puppet-troveReleaseNotes.tex', u'puppet-trove Release Notes Documentation',
   u'2017, Puppet OpenStack Developers', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    ('index', 'puppet-trovereleasenotes', u'puppet-trove Release Notes Documentation',
     [u'2017, Puppet OpenStack Developers'], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  ('index', 'puppet-troveReleaseNotes', u'puppet-trove Release Notes Documentation',
   u'2017, Puppet OpenStack Developers', 'puppet-troveReleaseNotes', 'One line description of project.',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False

# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']

# openstackdocstheme options
repository_name = 'openstack/puppet-trove'
bug_project = 'puppet-trove'
bug_tag = ''
<commit_msg>Use Python to compute release notes version<commit_after># -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'openstackdocstheme',
    'reno.sphinxext',
]

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = u'puppet-trove Release Notes'
copyright = u'2017, Puppet OpenStack Developers'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = ''
# The full version, including alpha/beta/rc tags.
release = ''

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = []

# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'openstackdocs'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%Y-%m-%d %H:%M'

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# Output file base name for HTML help builder.
htmlhelp_basename = 'puppet-troveReleaseNotesdoc'

# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
  ('index', 'puppet-troveReleaseNotes.tex', u'puppet-trove Release Notes Documentation',
   u'2017, Puppet OpenStack Developers', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    ('index', 'puppet-trovereleasenotes', u'puppet-trove Release Notes Documentation',
     [u'2017, Puppet OpenStack Developers'], 1)
]

# If true, show URL addresses after external links.
#man_show_urls = False

# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
  ('index', 'puppet-troveReleaseNotes', u'puppet-trove Release Notes Documentation',
   u'2017, Puppet OpenStack Developers', 'puppet-troveReleaseNotes', 'One line description of project.',
   'Miscellaneous'),
]

# Documents to append as an appendix to all manuals.
#texinfo_appendices = []

# If false, no module index is generated.
#texinfo_domain_indices = True

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False

# -- Options for Internationalization output ------------------------------
locale_dirs = ['locale/']

# openstackdocstheme options
repository_name = 'openstack/puppet-trove'
bug_project = 'puppet-trove'
bug_tag = ''
<|endoftext|>