<commit_before>from django.test import TestCase
from django.utils import timezone
from freezegun import freeze_time
import datetime

from core.tests.mommy_utils import make_recipe
from cla_eventlog.models import Log
from complaints.models import Complaint
from legalaid.models import Case, EODDetails, EligibilityCheck, PersonalDetails
from diagnosis.models import DiagnosisTraversal
from cla_auditlog.models import AuditLog
from cla_eventlog.management.commands.find_and_delete_old_cases import Command

def _make_datetime(year=None, month=None, day=None, hour=0, minute=0, second=0):
    today = datetime.date.today()
    year = year if year else today.year
    month = month if month else today.month
    day = day if day else today.day
    dt = datetime.datetime(year, month, day, hour, minute, second)
    return timezone.make_aware(dt, timezone.get_current_timezone())

class FindAndDeleteOldCases(TestCase):
    def setUp(self):
        super(FindAndDeleteOldCases, self).setUp()
        self.command = Command()

    def create_case(self, current_time, case_type="legalaid.case"):
        freezer = freeze_time(current_time)
        freezer.start()
        case = make_recipe(case_type)
        freezer.stop()
        return case

    def create_event_log_for_case(self, case, code, created):
        make_recipe("cla_eventlog.log", case=case, code=code, created=created)

    def delete_old_cases(self, current_time):
        freezer = freeze_time(current_time)
        freezer.start()
        self.command.execute("delete")
        freezer.stop()

    def find_old_cases(self, current_time):
        freezer = freeze_time(current_time)
        freezer.start()
        # Using handle method because command execute method can only return a string i.e it can't return a queryset
        cases = self.command.handle()
        freezer.stop()
        return cases

    def test_old_case_with_recent_event_logs_is_not_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date)

        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2018, month=4, day=27, hour=9))
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2020, month=4, day=27, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 3)

    def test_old_case_with_recent_complaint_is_not_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date)

        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2018, month=4, day=27, hour=9))

        complaint_date = _make_datetime(year=2020, month=4, day=27, hour=9)
        eod = make_recipe("legalaid.eod_details", case=case)
        log = make_recipe("cla_auditlog.audit_log")
        make_recipe("complaints.complaint", eod=eod, audit_log=[log], created=complaint_date)
        self.create_event_log_for_case(case, "COMPLAINT_CREATED", complaint_date)

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Complaint.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 3)
        self.assertEqual(EODDetails.objects.count(), 1)

    def test_old_case_with_recent_means_test_change_is_not_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date, "legalaid.eligible_case")

        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case, "MT_CHANGED", _make_datetime(year=2020, month=4, day=27, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 2)
        self.assertEqual(EligibilityCheck.objects.count(), 1)

    def test_old_case_with_two_logs_with_same_date_of_creation_is_not_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date, "legalaid.eligible_case")

        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2020, month=4, day=27, hour=9))
        self.create_event_log_for_case(case, "MT_CHANGED", _make_datetime(year=2020, month=4, day=27, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 2)
        self.assertEqual(EligibilityCheck.objects.count(), 1)

    def test_relatively_new_case_with_no_event_logs_is_not_deleted(self):
        date = _make_datetime(year=2020, month=4, day=27, hour=9)
        self.create_case(date)

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 0)

    def test_old_case_with_no_event_logs_is_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        self.create_case(date)
        self.create_case(date)
        self.create_case(date)

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)

        # Only need to check for this once as every test case uses this method via calling self.find_old_cases
        with self.assertNumQueries(1):
            oldCasesFound = self.find_old_cases(dt)
            oldCasesFound.count()

        self.assertEqual(oldCasesFound.count(), 3)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 0)
        self.assertEqual(Log.objects.count(), 0)
        self.assertEqual(AuditLog.objects.count(), 0)
        self.assertEqual(Complaint.objects.count(), 0)
        self.assertEqual(EODDetails.objects.count(), 0)
        self.assertEqual(EligibilityCheck.objects.count(), 0)
        self.assertEqual(DiagnosisTraversal.objects.count(), 0)
        self.assertEqual(PersonalDetails.objects.count(), 0)

    def test_old_case_with_old_event_logs_is_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date)
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2018, month=4, day=27, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 1)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 0)
        self.assertEqual(Log.objects.count(), 0)
        self.assertEqual(AuditLog.objects.count(), 0)
        self.assertEqual(Complaint.objects.count(), 0)
        self.assertEqual(EODDetails.objects.count(), 0)
        self.assertEqual(EligibilityCheck.objects.count(), 0)
        self.assertEqual(DiagnosisTraversal.objects.count(), 0)
        self.assertEqual(PersonalDetails.objects.count(), 0)

    def test_old_case_with_old_means_test_changed_is_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date, "legalaid.eligible_case")
        self.create_event_log_for_case(case, "MT_CHANGED", _make_datetime(year=2014, month=5, day=30, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 1)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 0)
        self.assertEqual(Log.objects.count(), 0)
        self.assertEqual(AuditLog.objects.count(), 0)
        self.assertEqual(Complaint.objects.count(), 0)
        self.assertEqual(EODDetails.objects.count(), 0)
        self.assertEqual(EligibilityCheck.objects.count(), 0)
        self.assertEqual(DiagnosisTraversal.objects.count(), 0)
        self.assertEqual(PersonalDetails.objects.count(), 0)

    def test_old_and_new_cases_are_deleted_correctly(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case_0 = self.create_case(date, "legalaid.eligible_case")
        case_1 = self.create_case(date, "legalaid.eligible_case")
        case_2 = self.create_case(date, "legalaid.eligible_case")
        self.create_event_log_for_case(case_0, "MT_CHANGED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case_0, "MT_CHANGED", _make_datetime(year=2018, month=5, day=30, hour=9))
        self.create_event_log_for_case(case_1, "MT_CHANGED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case_2, "MT_CHANGED", _make_datetime(year=2020, month=4, day=27, hour=9))

        audit_log_1 = make_recipe("cla_auditlog.audit_log")
        audit_log_2 = make_recipe("cla_auditlog.audit_log")
        audit_log_3 = make_recipe("cla_auditlog.audit_log")
        eod_0 = make_recipe("legalaid.eod_details", case=case_0)
        eod_1 = make_recipe("legalaid.eod_details", case=case_1)
        eod_2 = make_recipe("legalaid.eod_details", case=case_2)
        make_recipe("complaints.complaint", eod=eod_0, audit_log=[audit_log_1])
        make_recipe("complaints.complaint", eod=eod_1, audit_log=[audit_log_2])
        make_recipe("complaints.complaint", eod=eod_2, audit_log=[audit_log_3])

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 2)

        self.delete_old_cases(dt)
        valid_case = Case.objects.filter(id=case_2.id)
        self.assertEqual(valid_case.count(), 1)
        self.assertEqual(Log.objects.count(), 1)
        self.assertEqual(AuditLog.objects.count(), 1)
        self.assertEqual(Complaint.objects.count(), 1)
        self.assertEqual(EODDetails.objects.count(), 1)
        self.assertEqual(EligibilityCheck.objects.count(), 1)
        self.assertEqual(DiagnosisTraversal.objects.count(), 1)
        self.assertEqual(PersonalDetails.objects.count(), 1)
<commit_msg>Check there is only 1 case and that case is the relatively new case<commit_after>from django.test import TestCase
from django.utils import timezone
from freezegun import freeze_time
import datetime

from core.tests.mommy_utils import make_recipe
from cla_eventlog.models import Log
from complaints.models import Complaint
from legalaid.models import Case, EODDetails, EligibilityCheck, PersonalDetails
from diagnosis.models import DiagnosisTraversal
from cla_auditlog.models import AuditLog
from cla_eventlog.management.commands.find_and_delete_old_cases import Command

def _make_datetime(year=None, month=None, day=None, hour=0, minute=0, second=0):
    today = datetime.date.today()
    year = year if year else today.year
    month = month if month else today.month
    day = day if day else today.day
    dt = datetime.datetime(year, month, day, hour, minute, second)
    return timezone.make_aware(dt, timezone.get_current_timezone())

class FindAndDeleteOldCases(TestCase):
    def setUp(self):
        super(FindAndDeleteOldCases, self).setUp()
        self.command = Command()

    def create_case(self, current_time, case_type="legalaid.case"):
        freezer = freeze_time(current_time)
        freezer.start()
        case = make_recipe(case_type)
        freezer.stop()
        return case

    def create_event_log_for_case(self, case, code, created):
        make_recipe("cla_eventlog.log", case=case, code=code, created=created)

    def delete_old_cases(self, current_time):
        freezer = freeze_time(current_time)
        freezer.start()
        self.command.execute("delete")
        freezer.stop()

    def find_old_cases(self, current_time):
        freezer = freeze_time(current_time)
        freezer.start()
        # Using handle method because command execute method can only return a string i.e it can't return a queryset
        cases = self.command.handle()
        freezer.stop()
        return cases

    def test_old_case_with_recent_event_logs_is_not_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date)

        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2018, month=4, day=27, hour=9))
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2020, month=4, day=27, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 3)

    def test_old_case_with_recent_complaint_is_not_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date)

        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2018, month=4, day=27, hour=9))

        complaint_date = _make_datetime(year=2020, month=4, day=27, hour=9)
        eod = make_recipe("legalaid.eod_details", case=case)
        log = make_recipe("cla_auditlog.audit_log")
        make_recipe("complaints.complaint", eod=eod, audit_log=[log], created=complaint_date)
        self.create_event_log_for_case(case, "COMPLAINT_CREATED", complaint_date)

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Complaint.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 3)
        self.assertEqual(EODDetails.objects.count(), 1)

    def test_old_case_with_recent_means_test_change_is_not_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date, "legalaid.eligible_case")

        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case, "MT_CHANGED", _make_datetime(year=2020, month=4, day=27, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 2)
        self.assertEqual(EligibilityCheck.objects.count(), 1)

    def test_old_case_with_two_logs_with_same_date_of_creation_is_not_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date, "legalaid.eligible_case")

        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2020, month=4, day=27, hour=9))
        self.create_event_log_for_case(case, "MT_CHANGED", _make_datetime(year=2020, month=4, day=27, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 2)
        self.assertEqual(EligibilityCheck.objects.count(), 1)

    def test_relatively_new_case_with_no_event_logs_is_not_deleted(self):
        date = _make_datetime(year=2020, month=4, day=27, hour=9)
        self.create_case(date)

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 0)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 1)
        self.assertEqual(Log.objects.count(), 0)

    def test_old_case_with_no_event_logs_is_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        self.create_case(date)
        self.create_case(date)
        self.create_case(date)

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)

        # Only need to check for this once as every test case uses this method via calling self.find_old_cases
        with self.assertNumQueries(1):
            oldCasesFound = self.find_old_cases(dt)
            oldCasesFound.count()

        self.assertEqual(oldCasesFound.count(), 3)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 0)
        self.assertEqual(Log.objects.count(), 0)
        self.assertEqual(AuditLog.objects.count(), 0)
        self.assertEqual(Complaint.objects.count(), 0)
        self.assertEqual(EODDetails.objects.count(), 0)
        self.assertEqual(EligibilityCheck.objects.count(), 0)
        self.assertEqual(DiagnosisTraversal.objects.count(), 0)
        self.assertEqual(PersonalDetails.objects.count(), 0)

    def test_old_case_with_old_event_logs_is_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date)
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case, "CASE_VIEWED", _make_datetime(year=2018, month=4, day=27, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 1)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 0)
        self.assertEqual(Log.objects.count(), 0)
        self.assertEqual(AuditLog.objects.count(), 0)
        self.assertEqual(Complaint.objects.count(), 0)
        self.assertEqual(EODDetails.objects.count(), 0)
        self.assertEqual(EligibilityCheck.objects.count(), 0)
        self.assertEqual(DiagnosisTraversal.objects.count(), 0)
        self.assertEqual(PersonalDetails.objects.count(), 0)

    def test_old_case_with_old_means_test_changed_is_deleted(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case = self.create_case(date, "legalaid.eligible_case")
        self.create_event_log_for_case(case, "MT_CHANGED", _make_datetime(year=2014, month=5, day=30, hour=9))

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 1)

        self.delete_old_cases(dt)
        self.assertEqual(Case.objects.count(), 0)
        self.assertEqual(Log.objects.count(), 0)
        self.assertEqual(AuditLog.objects.count(), 0)
        self.assertEqual(Complaint.objects.count(), 0)
        self.assertEqual(EODDetails.objects.count(), 0)
        self.assertEqual(EligibilityCheck.objects.count(), 0)
        self.assertEqual(DiagnosisTraversal.objects.count(), 0)
        self.assertEqual(PersonalDetails.objects.count(), 0)

    def test_old_and_new_cases_are_deleted_correctly(self):
        date = _make_datetime(year=2014, month=4, day=27, hour=9)
        case_0 = self.create_case(date, "legalaid.eligible_case")
        case_1 = self.create_case(date, "legalaid.eligible_case")
        case_2 = self.create_case(date, "legalaid.eligible_case")
        self.create_event_log_for_case(case_0, "MT_CHANGED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case_0, "MT_CHANGED", _make_datetime(year=2018, month=5, day=30, hour=9))
        self.create_event_log_for_case(case_1, "MT_CHANGED", _make_datetime(year=2014, month=5, day=30, hour=9))
        self.create_event_log_for_case(case_2, "MT_CHANGED", _make_datetime(year=2020, month=4, day=27, hour=9))

        audit_log_1 = make_recipe("cla_auditlog.audit_log")
        audit_log_2 = make_recipe("cla_auditlog.audit_log")
        audit_log_3 = make_recipe("cla_auditlog.audit_log")
        eod_0 = make_recipe("legalaid.eod_details", case=case_0)
        eod_1 = make_recipe("legalaid.eod_details", case=case_1)
        eod_2 = make_recipe("legalaid.eod_details", case=case_2)
        make_recipe("complaints.complaint", eod=eod_0, audit_log=[audit_log_1])
        make_recipe("complaints.complaint", eod=eod_1, audit_log=[audit_log_2])
        make_recipe("complaints.complaint", eod=eod_2, audit_log=[audit_log_3])

        dt = _make_datetime(year=2021, month=1, day=1, hour=9)
        oldCasesFound = self.find_old_cases(dt)
        self.assertEqual(oldCasesFound.count(), 2)

        self.delete_old_cases(dt)
        case_ids = Case.objects.values_list("id", flat=True)
        # Check there is only 1 case and that case is the relatively new case
        self.assertEqual(list(case_ids), [case_2.id])
        self.assertEqual(Log.objects.count(), 1)
        self.assertEqual(AuditLog.objects.count(), 1)
        self.assertEqual(Complaint.objects.count(), 1)
        self.assertEqual(EODDetails.objects.count(), 1)
        self.assertEqual(EligibilityCheck.objects.count(), 1)
        self.assertEqual(DiagnosisTraversal.objects.count(), 1)
        self.assertEqual(PersonalDetails.objects.count(), 1)
<|endoftext|>


<commit_before>"""
-----------------
Theia Event Model
-----------------

Basic model of an Event, serializers and parsers for Event manipulation.
"""

from time import time
from collections import namedtuple
from io import StringIO, SEEK_CUR
import re

EventPreamble = namedtuple('EventPreamble', ['total', 'header', 'content'])
"""A preamble to an :class:`Event`.

The preamble is present only in the serialized representation of an event and
holds the information about the size of the event and its parts.
"""

EventPreamble.total.__doc__ = """
    ``int``, the size of the serialized event in bytes.
"""

EventPreamble.header.__doc__ = """
    ``int``, the size of the serialized event header in bytes.
"""

EventPreamble.content.__doc__ = """
    ``int``, the size of the serialized event content in bytes.
"""

class Header:
    """Header represents an Event header. The header contains the following
    properties:

    * ``id``, unique identifier for the event. Usually UUIDv4.
    * ``timestamp``, floating point of the number of milliseconds since epoch
      start (1970-1-1T00:00:00.00).
    * ``source``, string, the name of the event source.
    * ``tags``, list of strings, arbitrary tags attached to the event.

    The header is usefull and usually used when serializing/parsing an event.
    """

    def __init__(self, id=None, timestamp=None, source=None, tags=None):
        self.id = id
        self.timestamp = timestamp
        self.source = source
        self.tags = tags

class Event:
    """Event represnts some event occuring at a specfic time.

    Each event is uniquely identified by its ``id`` in the whole system. An event
    comes from a ``source`` and always has an associated ``timestamp``. The
    timestamp is usually generated by the event producer.

    The *content* of an event is an arbitrary string. It may be a log file line,
    some generated record, readings from a sensor or other non-structured or
    structured text.

    Each event may have a list of ``tags`` associcated with it. These are arbitrary
    strings and help in filtering the events.

    An event may look like this ::

        id:331c531d-6eb4-4fb5-84d3-ea6937b01fdd
        timestamp: 1509989630.6749051
        source:/dev/sensors/door1-sensor
        tags:sensors,home,doors,door1
        Door has been unlocked.

    The constructor takes multiple arguments, of which only the id and source are
    required.

    :param id: ``str``, the event unique identifier. Must be system-wide unique. An
        UUID version 4 (random UUID) would be a good choice for ``id``.
    :param source: ``str``, the source of the event. It usually is the name of the
        monitored file, but if the event does not originate from a file, it should be
        set to the name of the process, system or entity that generated the event.
    :param timestamp: ``float``, time when the event occured in seconds (like UNIX
        time). The value is a floating point number with nanoseconds precission. If
        no value is given, then the current time will be used.
    :param tags: ``list``, list of ``str`` tags to add to this event.
    :param content: ``str``, the actual content of the event. The content may have
        an arbitrary lenght (or none at all).

    """

    def __init__(self, id, source, timestamp=None, tags=None, content=None):
        self.id = id
        self.source = source
        self.timestamp = timestamp or time()  # time in nanoseconds UTC
        self.tags = tags or []
        self.content = content or ''

    def match(self, id=None, source=None, start=None, end=None, content=None, tags=None):
        """Check if this event matches the provided criteria.

        The event will match only if **all** criteria is statisfied. Calling match
        without any criteria, yields ``True``.

        The criteria is processed as a regular expression. Each value is first
        converted to string, then matched against the provided regular expression -
        see :func:`re.match`. The exception of this rule are the criteria for
        ``start`` and ``end`` wich expect numeric values, as they operate on the
        :class:`Event` timestamp.

        :param id: ``str``, regular expression against which to match the :class:`Event`
            ``id``.
        :param source: ``str``, regular expression against which to match the :class:`Event`
            ``source``.
        :param start: ``float`` or ``int``, match true if the :class:`Event` timestamp
            is greater than or equal to this value.
        :param start: ``float`` or ``int``, match true if the :class:`Event` timestamp
            is less than or equal to this value.
        :param content: ``str``, regular expression against which to match the :class:`Event`
            ``content``.
        :param tags: ``list``, list of ``str`` regular expressions against which to
            match the :class:`Event` tags. Matches true only if **all** of the provided
            criteria match the Event tags.

        Returns ``True`` if this :class:`Event` matches the criteria, otherwise ``False``.
        """
        return all([self._match_header_id_and_source(id, source),
                    self._match_timestamp(start, end),
                    self._match_tags(tags),
                    self._match_content(content)])

    def _match_header_id_and_source(self, id, source):
        matches = True
        if id is not None:
            matches = _match(id, self.id)
        if matches and source is not None:
            matches = _match(source, self.source)
        return matches

    def _match_timestamp(self, start, end):
        matches = True
        if self.timestamp:
            if start is not None:
                matches = self.timestamp >= start
            if matches and end is not None:
                matches = self.timestamp <= end
        return matches

    def _match_tags(self, tags):
        if tags:
            for tag in tags:
                if tag not in self.tags:
                    return False
        return True

    def _match_content(self, content):
        if not content:
            return True
        return _match(content, self.content)

def _match(pattern, value):
    """Match the value against a regular expression pattern.
    """
    if value is None:
        return False
    return re.match(pattern, value) is not None

class EventSerializer:
    """Serialized for instances of type :class:`Event`.

    This serializes the :class:`Event` in a plain text representation of the Event.
    The serialized text is encoded in UTF-8 and the actual ``bytes`` are returned.

    The representation consists of three parts: preamble, header and content.
    The preamble is the first line of every event and has this format:

        event: <total_size> <header_size> <content_size>

    where:

    * ``total_size`` is the total size of the Event (after the heading) in bytes.
    * ``header_size`` is the size of the header in bytes.
    * ``content_size`` is the size of the content (after the Header) in bytes.

    The header holds the values for the Event's id, source, tags and timestamp.
    Each value is serialized on a single line. The line starts with the name of
    the property, separated by a colon(``:``), then the property value.

    The content starts after the final header and is separated by a newline.

    Here is an example of a fully serialized :class:`Event` (Python's ``bytes``)::

        b'event: 155 133 22\\nid:331c531d-6eb4-4fb5-84d3-ea6937b01fdd\\ntimestamp: 1509989630.6749051\\nsource:/dev/sensors/door1-sensor\\ntags:sensors,home,doors,door1\\nDoor has been unlocked\\n'

    or as a textual representation::

        event: 155 133 22
        id:331c531d-6eb4-4fb5-84d3-ea6937b01fdd
        timestamp: 1509989630.6749051
        source:/dev/sensors/door1-sensor
        tags:sensors,home,doors,door1
        Door has been unlocked

    **Note** that the :class:`EventSerializer` adds a trailing newline (``\\n``) at
    the end.

    The serializer constructor takes the encoding as an argument. By default "utf-8"
    is used.

    :param encoding: ``str``, the encoding of the serialized string. Default ``utf-8``.

    """
    def __init__(self, encoding='utf-8'):
        self.encoding = encoding

    def serialize(self, event):
        """Serializes an :class:`Event`.

        See :class:`EventSerializer` for details on the serialization format.

        :param event: :class:`Event`, the event to be serialized.

        Returns the serialized event as ``bytes``.
        """
        event_str = ''
        hdr = self._serialize_header(event)
        hdr_size = len(hdr.encode(self.encoding))
        cnt = event.content or ''
        cnt_size = len(cnt.encode(self.encoding))
        total_size = hdr_size + cnt_size
        event_str += 'event: %d %d %d\n' % (total_size, hdr_size, cnt_size)
        event_str += hdr
        event_str += cnt
        event_str += '\n'
        return event_str.encode(self.encoding)

    def _serialize_header(self, event):
        hdr = ''
        hdr += 'id:' + str(event.id) + '\n'
        hdr += 'timestamp: %.7f' % event.timestamp + '\n'
        hdr += 'source:' + str(event.source) + '\n'
        hdr += 'tags:' + ','.join(event.tags) + '\n'
        return hdr

_HEADER_FIELDS = {
    'id': lambda value: value,
    'timestamp': lambda value: float(value),
    'source': lambda value: value,
    'tags': lambda value: value.split(',')
}

class EventParser:
    """Parses an incoming bytes stream into an :class:`Event`.

    Offers methods for parsing parts of an :class:`Event` or parsing the full
    event from the incoming :class:`io.BytesIO` stream.

    The stream will be decoded before converting it to ``str``. By default the
    parser assumes that the stream is ``utf-8`` encoded.

    :param encoding: ``str``, the encoding to be ued for decoding the stream bytes.
        The default is ``utf-8``.

    """
    def __init__(self, encoding='utf-8'):
        self.encoding = encoding

    def parse_header(self, hdr_size, stream):
        """Parses the :class:`Event` header into a :class:`Header` object from the
        incoming stream.

        First ``hdr_size`` bytes are read from the :class:`io.BytesIO` stream and
        are decoded to ``str``.

        Then, the parser parses each line by splitting it by the first colon (``:``).
        The first part is ued to determine the :class:`Header` property. The part
        after the colon is the propery value.

        :param hdr_size: ``int``, the size of the header in bytes. See
            :meth:`EventParser.parse_preamble` on how to determine the header size
            in bytes.
        :param stream: :class:`io.BytesIO`, the incoming stream to parse.

        Returns the parsed :class:`Header` for the event.

        Raises :class:`Exception` if an unknown property is encountered in the header.
        """
        hbytes = stream.read(hdr_size)
        if len(hbytes) != hdr_size:
            raise Exception('Invalid read size from buffer. The stream is either unreadable \
                             or corrupted. %d read, expected %d' % (len(hbytes), hdr_size))
        hdr_str = hbytes.decode(self.encoding)
        header = Header()
        sio = StringIO(hdr_str)

        line = sio.readline()
        while line:
            line = line.strip()
            if not line:
                raise Exception('Invalid header')
            idx = line.index(':')
            prop = line[0:idx].lower()
            value = line[idx + 1:]
            parse_field = _HEADER_FIELDS.get(prop)
            if not parse_field:
                raise Exception('Unknown property in header %s' % prop)
            setattr(header, prop, parse_field(value))
            line = sio.readline()
        sio.close()
        return header

    def parse_preamble(self, stream):
        """Parses the event preamble from the incoming stream into a :class:`EventPreamble`.

        The event preamble is a single line read from the stream with the following
        structure::

            <total_size> <header_size> <content_size>

        where:

        * ``total_size`` is the total size of the Event (after the heading) in bytes.
        * ``header_size`` is the size of the header in bytes.
        * ``content_size`` is the size of the content (after the Header) in bytes.

        Note that the sizes are expressed in bytes.

        :param stream: :class:`io.BytesIO`, the stream to parse.

        Returns the parsed :class:`EventPreamble` from the incoming stream.
        """
        pstr = stream.readline()
        if not pstr:
            raise EOFException()
        if pstr:
            pstr = pstr.decode(self.encoding).strip()

        if not pstr.startswith('event:'):
            raise Exception('Invalid preamble line: [%s]' % pstr)

        values = pstr[len('event:') + 1:].split(' ')
        if len(values) != 3:
            raise Exception('Invalid preamble values')

        return EventPreamble(total=int(values[0]), header=int(values[1]), content=int(values[2]))

    def parse_event(self, stream, skip_content=False):
        """Parses an event from the incoming stream.

        The parsing is done in two phases:

        1. The preamble is parsed to determine the total size of the event and the
            the size of the event header.
        2. Then the actual event is read, either with or without the event content.

        If ``skip_content`` is set to ``True``, then the actual content of the event
        is not read. This is usefull in event readers that do matching of the event
        header values, without wasting memory and performance for reading the content.
        In this case, the event content will be set to ``None``.

        :param stream: :class:`io.BytesIO`, the stream to parse from.
        :param skip_content: ``bool``, whether to skip the fetching of the content and
            to fetch only the :class:`Event` header. Default is ``False``.

        Returns the parsed :class:`Event` from the incoming stream.
        """
        preamble = self.parse_preamble(stream)
        header = self.parse_header(preamble.header, stream)
        content = None
        if skip_content:
            stream.seek(preamble.content, SEEK_CUR)
        else:
            content = stream.read(preamble.content)
            content = content.decode(self.encoding)

        stream.seek(1, SEEK_CUR)  # new line after each event

        if not skip_content and len(content) != preamble.content:
            raise Exception('Invalid content size. The stream is either unreadable or corrupted.')

        return Event(id=header.id, source=header.source, timestamp=header.timestamp,
                     tags=header.tags, content=content)

class EOFException(Exception):
    """Represents an error in parsing an :class:`Event` from an underlyign stream
    that occurs when the end of stream is reached prematurely.
    """
    pass
<commit_msg>Accurately calculating read lenght from stream.<commit_after>"""
-----------------
Theia Event Model
-----------------

Basic model of an Event, serializers and parsers for Event manipulation.
"""

from time import time
from collections import namedtuple
from io import StringIO, SEEK_CUR
import re

EventPreamble = namedtuple('EventPreamble', ['total', 'header', 'content'])
"""A preamble to an :class:`Event`.

The preamble is present only in the serialized representation of an event and
holds the information about the size of the event and its parts.
"""

EventPreamble.total.__doc__ = """
    ``int``, the size of the serialized event in bytes.
"""

EventPreamble.header.__doc__ = """
    ``int``, the size of the serialized event header in bytes.
"""

EventPreamble.content.__doc__ = """
    ``int``, the size of the serialized event content in bytes.
"""

class Header:
    """Header represents an Event header. The header contains the following
    properties:

    * ``id``, unique identifier for the event. Usually UUIDv4.
    * ``timestamp``, floating point of the number of milliseconds since epoch
      start (1970-1-1T00:00:00.00).
    * ``source``, string, the name of the event source.
    * ``tags``, list of strings, arbitrary tags attached to the event.

    The header is usefull and usually used when serializing/parsing an event.
    """

    def __init__(self, id=None, timestamp=None, source=None, tags=None):
        self.id = id
        self.timestamp = timestamp
        self.source = source
        self.tags = tags

class Event:
    """Event represnts some event occuring at a specfic time.

    Each event is uniquely identified by its ``id`` in the whole system. An event
    comes from a ``source`` and always has an associated ``timestamp``. The
    timestamp is usually generated by the event producer.

    The *content* of an event is an arbitrary string. It may be a log file line,
    some generated record, readings from a sensor or other non-structured or
    structured text.

    Each event may have a list of ``tags`` associcated with it. These are arbitrary
    strings and help in filtering the events.

    An event may look like this ::

        id:331c531d-6eb4-4fb5-84d3-ea6937b01fdd
        timestamp: 1509989630.6749051
        source:/dev/sensors/door1-sensor
        tags:sensors,home,doors,door1
        Door has been unlocked.

    The constructor takes multiple arguments, of which only the id and source are
    required.

    :param id: ``str``, the event unique identifier. Must be system-wide unique. An
        UUID version 4 (random UUID) would be a good choice for ``id``.
    :param source: ``str``, the source of the event. It usually is the name of the
        monitored file, but if the event does not originate from a file, it should be
        set to the name of the process, system or entity that generated the event.
    :param timestamp: ``float``, time when the event occured in seconds (like UNIX
        time). The value is a floating point number with nanoseconds precission. If
        no value is given, then the current time will be used.
    :param tags: ``list``, list of ``str`` tags to add to this event.
    :param content: ``str``, the actual content of the event. The content may have
        an arbitrary lenght (or none at all).

    """

    def __init__(self, id, source, timestamp=None, tags=None, content=None):
        self.id = id
        self.source = source
        self.timestamp = timestamp or time()  # time in nanoseconds UTC
        self.tags = tags or []
        self.content = content or ''

    def match(self, id=None, source=None, start=None, end=None, content=None, tags=None):
        """Check if this event matches the provided criteria.

        The event will match only if **all** criteria is statisfied. Calling match
        without any criteria, yields ``True``.

        The criteria is processed as a regular expression. Each value is first
        converted to string, then matched against the provided regular expression -
        see :func:`re.match`. The exception of this rule are the criteria for
        ``start`` and ``end`` wich expect numeric values, as they operate on the
        :class:`Event` timestamp.

        :param id: ``str``, regular expression against which to match the :class:`Event`
            ``id``.
        :param source: ``str``, regular expression against which to match the :class:`Event`
            ``source``.
        :param start: ``float`` or ``int``, match true if the :class:`Event` timestamp
            is greater than or equal to this value.
        :param start: ``float`` or ``int``, match true if the :class:`Event` timestamp
            is less than or equal to this value.
        :param content: ``str``, regular expression against which to match the :class:`Event`
            ``content``.
        :param tags: ``list``, list of ``str`` regular expressions against which to
            match the :class:`Event` tags. Matches true only if **all** of the provided
            criteria match the Event tags.

        Returns ``True`` if this :class:`Event` matches the criteria, otherwise ``False``.
        """
        return all([self._match_header_id_and_source(id, source),
                    self._match_timestamp(start, end),
                    self._match_tags(tags),
                    self._match_content(content)])

    def _match_header_id_and_source(self, id, source):
        matches = True
        if id is not None:
            matches = _match(id, self.id)
        if matches and source is not None:
            matches = _match(source, self.source)
        return matches

    def _match_timestamp(self, start, end):
        matches = True
        if self.timestamp:
            if start is not None:
                matches = self.timestamp >= start
            if matches and end is not None:
                matches = self.timestamp <= end
        return matches

    def _match_tags(self, tags):
        if tags:
            for tag in tags:
                if tag not in self.tags:
                    return False
        return True

    def _match_content(self, content):
        if not content:
            return True
        return _match(content, self.content)

def _match(pattern, value):
    """Match the value against a regular expression pattern.
    """
    if value is None:
        return False
    return re.match(pattern, value) is not None

class EventSerializer:
    """Serialized for instances of type :class:`Event`.

    This serializes the :class:`Event` in a plain text representation of the Event.
    The serialized text is encoded in UTF-8 and the actual ``bytes`` are returned.

    The representation consists of three parts: preamble, header and content.
    The preamble is the first line of every event and has this format:

        event: <total_size> <header_size> <content_size>

    where:

    * ``total_size`` is the total size of the Event (after the heading) in bytes.
    * ``header_size`` is the size of the header in bytes.
    * ``content_size`` is the size of the content (after the Header) in bytes.

    The header holds the values for the Event's id, source, tags and timestamp.
    Each value is serialized on a single line. The line starts with the name of
    the property, separated by a colon(``:``), then the property value.

    The content starts after the final header and is separated by a newline.

    Here is an example of a fully serialized :class:`Event` (Python's ``bytes``)::

        b'event: 155 133 22\\nid:331c531d-6eb4-4fb5-84d3-ea6937b01fdd\\ntimestamp: 1509989630.6749051\\nsource:/dev/sensors/door1-sensor\\ntags:sensors,home,doors,door1\\nDoor has been unlocked\\n'

    or as a textual representation::

        event: 155 133 22
        id:331c531d-6eb4-4fb5-84d3-ea6937b01fdd
        timestamp: 1509989630.6749051
        source:/dev/sensors/door1-sensor
        tags:sensors,home,doors,door1
        Door has been unlocked

    **Note** that the :class:`EventSerializer` adds a trailing newline (``\\n``) at
    the end.

    The serializer constructor takes the encoding as an argument. By default "utf-8"
    is used.

    :param encoding: ``str``, the encoding of the serialized string. Default ``utf-8``.

    """
    def __init__(self, encoding='utf-8'):
        self.encoding = encoding

    def serialize(self, event):
        """Serializes an :class:`Event`.

        See :class:`EventSerializer` for details on the serialization format.

        :param event: :class:`Event`, the event to be serialized.

        Returns the serialized event as ``bytes``.
        """
        event_str = ''
        hdr = self._serialize_header(event)
        hdr_size = len(hdr.encode(self.encoding))
        cnt = event.content or ''
        cnt_size = len(cnt.encode(self.encoding))
        total_size = hdr_size + cnt_size
        event_str += 'event: %d %d %d\n' % (total_size, hdr_size, cnt_size)
        event_str += hdr
        event_str += cnt
        event_str += '\n'
        return event_str.encode(self.encoding)

    def _serialize_header(self, event):
        hdr = ''
        hdr += 'id:' + str(event.id) + '\n'
        hdr += 'timestamp: %.7f' % event.timestamp + '\n'
        hdr += 'source:' + str(event.source) + '\n'
        hdr += 'tags:' + ','.join(event.tags) + '\n'
        return hdr

_HEADER_FIELDS = {
    'id': lambda value: value,
    'timestamp': lambda value: float(value),
    'source': lambda value: value,
    'tags': lambda value: value.split(',')
}

class EventParser:
    """Parses an incoming bytes stream into an :class:`Event`.

    Offers methods for parsing parts of an :class:`Event` or parsing the full
    event from the incoming :class:`io.BytesIO` stream.

    The stream will be decoded before converting it to ``str``. By default the
    parser assumes that the stream is ``utf-8`` encoded.

    :param encoding: ``str``, the encoding to be ued for decoding the stream bytes.
        The default is ``utf-8``.

    """
    def __init__(self, encoding='utf-8'):
        self.encoding = encoding

    def parse_header(self, hdr_size, stream):
        """Parses the :class:`Event` header into a :class:`Header` object from the
        incoming stream.

        First ``hdr_size`` bytes are read from the :class:`io.BytesIO` stream and
        are decoded to ``str``.

        Then, the parser parses each line by splitting it by the first colon (``:``).
        The first part is ued to determine the :class:`Header` property. The part
        after the colon is the propery value.

        :param hdr_size: ``int``, the size of the header in bytes. See
            :meth:`EventParser.parse_preamble` on how to determine the header size
            in bytes.
        :param stream: :class:`io.BytesIO`, the incoming stream to parse.

        Returns the parsed :class:`Header` for the event.

        Raises :class:`Exception` if an unknown property is encountered in the header.
        """
        hbytes = stream.read(hdr_size)
        if len(hbytes) != hdr_size:
            raise Exception('Invalid read size from buffer. The stream is either unreadable \
                             or corrupted. %d read, expected %d' % (len(hbytes), hdr_size))
        hdr_str = hbytes.decode(self.encoding)
        header = Header()
        sio = StringIO(hdr_str)

        line = sio.readline()
        while line:
            line = line.strip()
            if not line:
                raise Exception('Invalid header')
            idx = line.index(':')
            prop = line[0:idx].lower()
            value = line[idx + 1:]
            parse_field = _HEADER_FIELDS.get(prop)
            if not parse_field:
                raise Exception('Unknown property in header %s' % prop)
            setattr(header, prop, parse_field(value))
            line = sio.readline()
        sio.close()
        return header

    def parse_preamble(self, stream):
        """Parses the event preamble from the incoming stream into a :class:`EventPreamble`.

        The event preamble is a single line read from the stream with the following
        structure::

            <total_size> <header_size> <content_size>

        where:

        * ``total_size`` is the total size of the Event (after the heading) in bytes.
        * ``header_size`` is the size of the header in bytes.
        * ``content_size`` is the size of the content (after the Header) in bytes.

        Note that the sizes are expressed in bytes.

        :param stream: :class:`io.BytesIO`, the stream to parse.

        Returns the parsed :class:`EventPreamble` from the incoming stream.
        """
        pstr = stream.readline()
        if not pstr:
            raise EOFException()
        if pstr:
            pstr = pstr.decode(self.encoding).strip()

        if not pstr.startswith('event:'):
            raise Exception('Invalid preamble line: [%s]' % pstr)

        values = pstr[len('event:') + 1:].split(' ')
        if len(values) != 3:
            raise Exception('Invalid preamble values')

        return EventPreamble(total=int(values[0]), header=int(values[1]), content=int(values[2]))

    def parse_event(self, stream, skip_content=False):
        """Parses an event from the incoming stream.

        The parsing is done in two phases:

        1. The preamble is parsed to determine the total size of the event and the
            the size of the event header.
        2. Then the actual event is read, either with or without the event content.

        If ``skip_content`` is set to ``True``, then the actual content of the event
        is not read. This is usefull in event readers that do matching of the event
        header values, without wasting memory and performance for reading the content.
        In this case, the event content will be set to ``None``.

        :param stream: :class:`io.BytesIO`, the stream to parse from.
        :param skip_content: ``bool``, whether to skip the fetching of the content and
            to fetch only the :class:`Event` header. Default is ``False``.

        Returns the parsed :class:`Event` from the incoming stream.
        """
        preamble = self.parse_preamble(stream)
        header = self.parse_header(preamble.header, stream)
        content = None
        read_length = None
        if skip_content:
            stream.seek(preamble.content, SEEK_CUR)
        else:
            content = stream.read(preamble.content)
            read_length = len(content)
            content = content.decode(self.encoding)

        stream.seek(1, SEEK_CUR)  # new line after each event

        if not skip_content and read_length != preamble.content:
            raise Exception('Invalid content size. The stream is either unreadable or corrupted. ' +
                            'Preamble declares %d bytes, but content length is %d' % (preamble.content, len(content)))

        return Event(id=header.id, source=header.source, timestamp=header.timestamp,
                     tags=header.tags, content=content)

class EOFException(Exception):
    """Represents an error in parsing an :class:`Event` from an underlyign stream
    that occurs when the end of stream is reached prematurely.
    """
    pass
<|endoftext|>


<commit_before>import modules.plugins.Salesforce.utility as util

def SFDCVersion(messageDetail):
    ep = util.sfdcBaseURL + '/services/data/'

    response = util.SFDC_REST('GET', ep, {})
    ver = response.JSON[0]['label']
    messageDetail.ReplyToSender(ver)

def AccountSearch(messageDetail):
    ep = util.sfdcBaseURL + '/services/data/'
    pass

def SubmitUserFeedback(messageDetail):
    ep = util.sfdcBaseURL + '/services/apexrest/symphony/feedback'

    # etree.findall()
    hashtags = messageDetail.Command.MessageXML.findall('.//hash')

    clients = []
    for ele in hashtags:
        if 'client' in ele.attrib['tag'] and ele.tail:
            clients = [x.strip() for x in ele.tail.split(',')]

    sfdcBody = {
        "messageid": messageDetail.MessageId,
        "streamid": messageDetail.StreamId,
        "submitteremail": messageDetail.Sender.Email,
        "hashtags": messageDetail.Command.Hashtags,
        "mentionedusers": messageDetail.Command.Mentions,
        "summary": messageDetail.Command.MessageFlattened[:50],
        "comments": messageDetail.Command.MessageFlattened,
        "companylist": clients
    }

    if messageDetail.Attachments:
        sfdcBody['attachments'] = messageDetail.Attachments

    response = util.SFDC_REST('POST', ep, sfdcBody)

    if response.IsSuccess:
        msg = "User feedback submitted successfully"
    else:
        msg = "Failed to send feedback. Contact Biz Ops"

    messageDetail.ReplyToSender(msg)

<commit_msg>Intercept unescaped double quotes<commit_after>import modules.plugins.Salesforce.utility as util

def SFDCVersion(messageDetail):
    ep = util.sfdcBaseURL + '/services/data/'

    response = util.SFDC_REST('GET', ep, {})
    ver = response.JSON[0]['label']
    messageDetail.ReplyToSender(ver)

def AccountSearch(messageDetail):
    ep = util.sfdcBaseURL + '/services/data/'
    pass

def SubmitUserFeedback(messageDetail):
    ep = util.sfdcBaseURL + '/services/apexrest/symphony/feedback'

    # etree.findall()
    hashtags = messageDetail.Command.MessageXML.findall('.//hash')

    clients = []
    for ele in hashtags:
        if 'client' in ele.attrib['tag'] and ele.tail:
            clients = [x.strip() for x in ele.tail.split(',')]

    sfdcBody = {
        "messageid": messageDetail.MessageId,
        "streamid": messageDetail.StreamId,
        "submitteremail": messageDetail.Sender.Email,
        "hashtags": messageDetail.Command.Hashtags,
        "mentionedusers": messageDetail.Command.Mentions,
        "summary": messageDetail.Command.MessageFlattened[:50].replace('"', '\''),
        "comments": messageDetail.Command.MessageFlattened.replace(r'\"', '\'').replace('"', '\''),
        "companylist": clients
    }

    if messageDetail.Attachments:
        sfdcBody['attachments'] = messageDetail.Attachments

    response = util.SFDC_REST('POST', ep, sfdcBody)

    if response.IsSuccess:
        msg = "User feedback submitted successfully"
    else:
        msg = "Failed to send feedback. Contact Biz Ops"

    messageDetail.ReplyToSender(msg)

<|endoftext|>


<commit_before>from datetime import date, timedelta

from theme.models import QuotaMessage

def get_quota_message(user):
    """
    get quota warning, grace period, or enforcement message to email users and display
    when the user logins in and display on user profile page
    :param user: The User instance
    :return: quota message string
    """
    if not QuotaMessage.objects.exists():
        QuotaMessage.objects.create()
    qmsg = QuotaMessage.objects.first()
    soft_limit = qmsg.soft_limit_percent
    hard_limit = qmsg.hard_limit_percent
    return_msg = ''
    for uq in user.quotas.all():
        percent = round(uq.used_value * 100.0 / uq.allocated_value)
        if percent >= hard_limit or uq.remaining_grace_period == 0:
            # return quota enforcement message
            msg_template_str = ' - {}{}\n'.format(qmsg.enforce_content_prepend, qmsg.content)
            return_msg += msg_template_str.format(used=round(uq.used_value),
                                                  unit=uq.unit,
                                                  allocated=uq.allocated_value,
                                                  zone=uq.zone,
                                                  percent=percent)
        elif uq.remaining_grace_period > 0:
            # return quota grace period message
            cut_off_date = date.today() - timedelta(days=uq.remaining_grace_period)
            msg_template_str = ' - {}{}\n'.format(qmsg.grace_period_content_prepend, qmsg.content)
            return_msg += msg_template_str.format(used=round(uq.used_value),
                                                  unit=uq.unit,
                                                  allocated=uq.allocated_value,
                                                  zone=uq.zone,
                                                  percent=percent,
                                                  cut_off_date=cut_off_date)
        elif percent >= soft_limit:
            # return quota warning message
            msg_template_str = ' - {}{}\n'.format(qmsg.warning_content_prepend, qmsg.content)
            return_msg += msg_template_str.format(used=round(uq.used_value),
                                                  unit=uq.unit,
                                                  allocated=uq.allocated_value,
                                                  zone=uq.zone,
                                                  percent=percent)
        else:
            # return quota informational message
            return_msg += ' - Your quota for HydroShare resources is {allocated}{unit} in {zone} ' \
                          'zone. You currently have resources that consume {used}{unit}, ' \
                          '{percent}% of your quota.'.format(allocated=uq.allocated_value,
                                                            unit=uq.unit,
                                                            used=round(uq.used_value),
                                                            zone=uq.zone,
                                                            percent=percent)
        return return_msg
<commit_msg>fixed flake8<commit_after>from datetime import date, timedelta

from theme.models import QuotaMessage

def get_quota_message(user):
    """
    get quota warning, grace period, or enforcement message to email users and display
    when the user logins in and display on user profile page
    :param user: The User instance
    :return: quota message string
    """
    if not QuotaMessage.objects.exists():
        QuotaMessage.objects.create()
    qmsg = QuotaMessage.objects.first()
    soft_limit = qmsg.soft_limit_percent
    hard_limit = qmsg.hard_limit_percent
    return_msg = ''
    for uq in user.quotas.all():
        percent = round(uq.used_value * 100.0 / uq.allocated_value)
        if percent >= hard_limit or uq.remaining_grace_period == 0:
            # return quota enforcement message
            msg_template_str = ' - {}{}\n'.format(qmsg.enforce_content_prepend, qmsg.content)
            return_msg += msg_template_str.format(used=round(uq.used_value),
                                                  unit=uq.unit,
                                                  allocated=uq.allocated_value,
                                                  zone=uq.zone,
                                                  percent=percent)
        elif uq.remaining_grace_period > 0:
            # return quota grace period message
            cut_off_date = date.today() - timedelta(days=uq.remaining_grace_period)
            msg_template_str = ' - {}{}\n'.format(qmsg.grace_period_content_prepend, qmsg.content)
            return_msg += msg_template_str.format(used=round(uq.used_value),
                                                  unit=uq.unit,
                                                  allocated=uq.allocated_value,
                                                  zone=uq.zone,
                                                  percent=percent,
                                                  cut_off_date=cut_off_date)
        elif percent >= soft_limit:
            # return quota warning message
            msg_template_str = ' - {}{}\n'.format(qmsg.warning_content_prepend, qmsg.content)
            return_msg += msg_template_str.format(used=round(uq.used_value),
                                                  unit=uq.unit,
                                                  allocated=uq.allocated_value,
                                                  zone=uq.zone,
                                                  percent=percent)
        else:
            # return quota informational message
            return_msg += ' - Your quota for HydroShare resources is {allocated}{unit} in {zone} ' \
                          'zone. You currently have resources that consume {used}{unit}, ' \
                          '{percent}% of your quota.'.format(allocated=uq.allocated_value,
                                                             unit=uq.unit,
                                                             used=round(uq.used_value),
                                                             zone=uq.zone,
                                                             percent=percent)
        return return_msg
<|endoftext|>


<commit_before># inspired from:

# https://python-packaging-user-guide.readthedocs.org/en/latest/single_source_version/
# https://github.com/pypa/warehouse/blob/master/warehouse/__about__.py

__name__ = 'thinc'
__version__ = '6.9.0'
__summary__ = "Practical Machine Learning for NLP"
__uri__ = 'https://github.com/explosion/thinc'
__author__ = 'Matthew Honnibal'
__email__ = 'matt@explosion.ai'
__license__ = 'MIT'
__title__ = "thinc"
__release__ = True
<commit_msg>Increment version<commit_after># inspired from:

# https://python-packaging-user-guide.readthedocs.org/en/latest/single_source_version/
# https://github.com/pypa/warehouse/blob/master/warehouse/__about__.py

__name__ = 'thinc'
__version__ = '6.9.1'
__summary__ = "Practical Machine Learning for NLP"
__uri__ = 'https://github.com/explosion/thinc'
__author__ = 'Matthew Honnibal'
__email__ = 'matt@explosion.ai'
__license__ = 'MIT'
__title__ = "thinc"
__release__ = False
<|endoftext|>


<commit_before>import datetime
import gzip
import time
from cStringIO import StringIO
from urllib import urlencode

try:
    import xml.etree.cElementTree as ET
except:
    import xml.etree.ElementTree as ET

#from django.conf import settings
from django.db.models import Count, Q
from django.template import RequestContext

from coffin.shortcuts import render_to_response

# ---------------------------------------------------------------------------
# Wrapper around render_to_response
def render_page(template, data, request, character_ids=None, corporation_ids=None):
    from thing.models import Character, Corporation, Contract, APIKey, IndustryJob

    utcnow = datetime.datetime.utcnow()

    if character_ids is None:
        character_ids = list(Character.objects.filter(apikeys__user=request.user.id).values_list('id', flat=True))

    if corporation_ids is None:
        #corporation_ids = list(Corporation.objects.filter(pk__in=APIKey.objects.filter(user=request.user).exclude(corp_character=None).values('corp_character__corporation')).values_list('id', flat=True))
        corporation_ids = list(APIKey.objects.filter(user=request.user).exclude(corp_character=None).values_list('corp_character__corporation', flat=True))

    # Aggregate outstanding contracts
    contracts = Contract.objects.filter(
        Q(assignee_id__in=character_ids)
        |
        Q(assignee_id__in=corporation_ids)
    )
    contracts = contracts.filter(status='Outstanding')
    data['nav_contracts'] = contracts.aggregate(t=Count('id'))['t']

    # Aggregate ready industry jobs
    jobs = IndustryJob.objects.filter(
        Q(character__in=character_ids, corporation=None)
        |
        Q(corporation__in=corporation_ids)
    )
    jobs = jobs.filter(completed=False, end_time__lte=utcnow)
    data['nav_industryjobs'] = jobs.aggregate(t=Count('id'))['t']

    return render_to_response(template, data, RequestContext(request))

# ---------------------------------------------------------------------------
# Times things
class TimerThing:
    def __init__(self, name):
        self.times = []
        self.add_time(name)

    def add_time(self, name):
        self.times.append([time.time(), name])

    def finished(self):
        print 'TimerThing: %s' % (self.times[0][1])
        print '-' * 23
        for i in range(1, len(self.times)):
            t, name = self.times[i]
            print '%-15s: %.3fs' % (name, t - self.times[i-1][0])
        print '-' * 23
        print '%-15s: %.3fs' % ('total', self.times[-1][0] - self.times[0][0])

# ---------------------------------------------------------------------------
# Fetch all rows from a cursor as a list of dictionaries
def dictfetchall(cursor):
    "Returns all rows from a cursor as a dict"
    desc = cursor.description
    return [
        dict(zip([col[0] for col in desc], row))
        for row in cursor.fetchall()
    ]

# ---------------------------------------------------------------------------
# Convert a datetime.timedelta object into a number of seconds
def total_seconds(delta):
    return (delta.days * 24 * 60 * 60) + delta.seconds

# ---------------------------------------------------------------------------

def build_filter(filters, filter_type, filter_comp, filter_value):
    params = []

    for ft, stuff in filters.items():
        if ft == filter_type:
            continue

        for fc, fv in stuff:
            params.append(('ft', ft))
            params.append(('fc', fc))
            params.append(('fv', fv))

    params.append(('ft', filter_type))
    params.append(('fc', filter_comp))
    params.append(('fv', filter_value))

    return urlencode(params)

# ---------------------------------------------------------------------------
# Parse filter GET variables
def parse_filters(request, expected):
    # retrieve any supplied filter values
    f_types = request.GET.getlist('ft')
    f_comps = request.GET.getlist('fc')
    f_values = request.GET.getlist('fv')

    # run.
    filters = {}

    min_len = min(len(f_types), len(f_comps), len(f_values))
    for ft, fc, fv in zip(f_types[:min_len], f_comps[:min_len], f_values[:min_len]):
        ex = expected.get(ft)
        if ex is None:
            continue

        # If the entry must be a number, verify that
        if ex.get('number', False):
            try:
                fv = int(fv)
            except ValueError:
                continue

        # Make sure the comparison is valid
        if fc not in ex.get('comps', []):
            continue

        # Keep it
        filters.setdefault(ft, []).append([fc, fv])

    return filters

# ---------------------------------------------------------------------------

def q_reduce_or(a, b):
    return a | b

def q_reduce_and(a, b):
    return a & b

# ---------------------------------------------------------------------------
<commit_msg>Fix stuff.render_page properly - Django's worthless page long traceback was apparently a nice way to say "the user is not authenticated", who knew<commit_after>import datetime
import gzip
import time
from cStringIO import StringIO
from urllib import urlencode

try:
    import xml.etree.cElementTree as ET
except:
    import xml.etree.ElementTree as ET

#from django.conf import settings
from django.db.models import Count, Q
from django.template import RequestContext

from coffin.shortcuts import render_to_response

# ---------------------------------------------------------------------------
# Wrapper around render_to_response
def render_page(template, data, request, character_ids=None, corporation_ids=None):
    from thing.models import Character, Corporation, Contract, APIKey, IndustryJob

    utcnow = datetime.datetime.utcnow()

    if request.user.is_authenticated():
        if character_ids is None:
            character_ids = list(Character.objects.filter(apikeys__user=request.user.id).values_list('id', flat=True))

        if corporation_ids is None:
            #corporation_ids = list(Corporation.objects.filter(pk__in=APIKey.objects.filter(user=request.user).exclude(corp_character=None).values('corp_character__corporation')).values_list('id', flat=True))
            corporation_ids = list(APIKey.objects.filter(user=request.user).exclude(corp_character=None).values_list('corp_character__corporation', flat=True))

        # Aggregate outstanding contracts
        contracts = Contract.objects.filter(
            Q(assignee_id__in=character_ids)
            |
            Q(assignee_id__in=corporation_ids)
        )
        contracts = contracts.filter(status='Outstanding')
        data['nav_contracts'] = contracts.aggregate(t=Count('id'))['t']

        # Aggregate ready industry jobs
        jobs = IndustryJob.objects.filter(
            Q(character__in=character_ids, corporation=None)
            |
            Q(corporation__in=corporation_ids)
        )
        jobs = jobs.filter(completed=False, end_time__lte=utcnow)
        data['nav_industryjobs'] = jobs.aggregate(t=Count('id'))['t']

    return render_to_response(template, data, RequestContext(request))

# ---------------------------------------------------------------------------
# Times things
class TimerThing:
    def __init__(self, name):
        self.times = []
        self.add_time(name)

    def add_time(self, name):
        self.times.append([time.time(), name])

    def finished(self):
        print 'TimerThing: %s' % (self.times[0][1])
        print '-' * 23
        for i in range(1, len(self.times)):
            t, name = self.times[i]
            print '%-15s: %.3fs' % (name, t - self.times[i-1][0])
        print '-' * 23
        print '%-15s: %.3fs' % ('total', self.times[-1][0] - self.times[0][0])

# ---------------------------------------------------------------------------
# Fetch all rows from a cursor as a list of dictionaries
def dictfetchall(cursor):
    "Returns all rows from a cursor as a dict"
    desc = cursor.description
    return [
        dict(zip([col[0] for col in desc], row))
        for row in cursor.fetchall()
    ]

# ---------------------------------------------------------------------------
# Convert a datetime.timedelta object into a number of seconds
def total_seconds(delta):
    return (delta.days * 24 * 60 * 60) + delta.seconds

# ---------------------------------------------------------------------------

def build_filter(filters, filter_type, filter_comp, filter_value):
    params = []

    for ft, stuff in filters.items():
        if ft == filter_type:
            continue

        for fc, fv in stuff:
            params.append(('ft', ft))
            params.append(('fc', fc))
            params.append(('fv', fv))

    params.append(('ft', filter_type))
    params.append(('fc', filter_comp))
    params.append(('fv', filter_value))

    return urlencode(params)

# ---------------------------------------------------------------------------
# Parse filter GET variables
def parse_filters(request, expected):
    # retrieve any supplied filter values
    f_types = request.GET.getlist('ft')
    f_comps = request.GET.getlist('fc')
    f_values = request.GET.getlist('fv')

    # run.
    filters = {}

    min_len = min(len(f_types), len(f_comps), len(f_values))
    for ft, fc, fv in zip(f_types[:min_len], f_comps[:min_len], f_values[:min_len]):
        ex = expected.get(ft)
        if ex is None:
            continue

        # If the entry must be a number, verify that
        if ex.get('number', False):
            try:
                fv = int(fv)
            except ValueError:
                continue

        # Make sure the comparison is valid
        if fc not in ex.get('comps', []):
            continue

        # Keep it
        filters.setdefault(ft, []).append([fc, fv])

    return filters

# ---------------------------------------------------------------------------

def q_reduce_or(a, b):
    return a | b

def q_reduce_and(a, b):
    return a & b

# ---------------------------------------------------------------------------
<|endoftext|>


<commit_before>"""
Optional IPython extension for working with Parameters.

This extension offers extended but completely optional functionality
for IPython users.  From within IPython, it may be loaded using:

%load_ext param.ipython

This will register the %params line magic to allow easy inspection of
all the parameters defined on a parameterized class or object:

%params <parameterized class or object>

All parameters of the class or object will be listed in the IPython
pager together with all their corresponding attributes and
docstrings. Note that the class or object to be inspected must already
exist in the active namespace.
"""

__author__ = "Jean-Luc Stevens"

import textwrap
import param

# Whether to generate warnings when misformatted docstrings are found
WARN_MISFORMATTED_DOCSTRINGS = False

# ANSI color codes for the IPython pager
red   = '\x1b[1;31m%s\x1b[0m'
blue  = '\x1b[1;34m%s\x1b[0m'
green = '\x1b[1;32m%s\x1b[0m'
cyan = '\x1b[1;36m%s\x1b[0m'

class ParamPager(object):
    """
    Callable class that displays information about the supplied
    parameterized object or class in the IPython pager.
    """

    def __init__(self, metaclass=False):
        """
        If metaclass is set to True, the checks for Parameterized
        classes objects are disabled. This option is for use in
        ParameterizedMetaclass for automatic docstring generation.
        """
        # Order of the information to be listed in the table (left to right)
        self.order = ['name', 'changed', 'value', 'type', 'bounds', 'mode']
        self.metaclass = metaclass

    def _get_param_info(self, obj, include_super=True):
        """
        Get the parameter dictionary, the list of modifed parameters
        and the dictionary or parameter values. If include_super is
        True, parameters are also collected from the super classes.
        """

        params = dict(obj.params())
        if isinstance(obj,type):
            changed = []
            val_dict = dict((k,p.default) for (k,p) in params.items())
            self_class = obj
        else:
            changed = [name for (name,_) in obj.get_param_values(onlychanged=True)]
            val_dict = dict(obj.get_param_values())
            self_class = obj.__class__

        if not include_super:
            params = dict((k,v) for (k,v) in params.items()
                          if k in self_class.__dict__)

        params.pop('name') # Already displayed in the title.
        return (params, val_dict, changed)

    def _param_docstrings(self, info, max_col_len=100, only_changed=False):
        """
        Build a string to that presents all of the parameter
        docstrings in a clean format (alternating red and blue for
        readability).
        """

        (params, val_dict, changed) = info
        contents = []
        displayed_params = {}
        for name, p in params.items():
            if only_changed and not (name in changed):
                continue
            displayed_params[name] = p

        right_shift = max(len(name) for name in displayed_params.keys())+2

        for i, name in enumerate(sorted(displayed_params)):
            p = displayed_params[name]
            heading = "%s: " % name
            unindented = textwrap.dedent("< No docstring available >" if p.doc is None else p.doc)

            if (WARN_MISFORMATTED_DOCSTRINGS
                and not unindented.startswith("\n")  and len(unindented.splitlines()) > 1):
                param.main.warning("Multi-line docstring for %r is incorrectly formatted "
                                   " (should start with newline)" % name)
            # Strip any starting newlines
            while unindented.startswith("\n"):
                unindented = unindented[1:]

            lines = unindented.splitlines()
            if len(lines) > 1:
                tail = ['%s%s' % (' '  * right_shift, line) for line in lines[1:]]
                all_lines = [ heading.ljust(right_shift) + lines[0]] + tail
            else:
                all_lines = [ heading.ljust(right_shift) + lines[0]]

            if i % 2:  # Alternate red and blue for docstrings
                contents.extend([red %el for el in all_lines])
            else:
                contents.extend([blue %el for el in all_lines])

        return "\n".join(contents)

    def _build_table(self, info, order, max_col_len=40, only_changed=False):
        """
        Collect the information about parameters needed to build a
        properly formatted table and then tabulate it.
        """

        info_dict, bounds_dict = {}, {}
        (params, val_dict, changed) = info
        col_widths = dict((k,0) for k in order)

        for name, p in params.items():
            if only_changed and not (name in changed):
                continue

            constant = 'C' if p.constant else 'V'
            readonly = 'RO' if p.readonly else 'RW'
            allow_None = ' AN' if hasattr(p, 'allow_None') and p.allow_None else ''

            mode = '%s %s%s' % (constant, readonly, allow_None)
            info_dict[name] = {'name': name, 'type':p.__class__.__name__,
                               'mode':mode}

            if hasattr(p, 'bounds'):
                lbound, ubound = (None,None) if p.bounds is None else p.bounds

                mark_lbound, mark_ubound = False, False
                # Use soft_bounds when bounds not defined.
                if hasattr(p, 'get_soft_bounds'):
                    soft_lbound, soft_ubound = p.get_soft_bounds()
                    if lbound is None and soft_lbound is not None:
                        lbound = soft_lbound
                        mark_lbound = True
                    if ubound is None and soft_ubound is not None:
                        ubound = soft_ubound
                        mark_ubound = True

                if (lbound, ubound) != (None,None):
                    bounds_dict[name] = (mark_lbound, mark_ubound)
                    info_dict[name]['bounds'] = '(%s, %s)' % (lbound, ubound)

            value = repr(val_dict[name])
            if len(value) > (max_col_len - 3):
                value = value[:max_col_len-3] + '...'
            info_dict[name]['value'] = value

            for col in info_dict[name]:
                max_width = max([col_widths[col], len(info_dict[name][col])])
                col_widths[col] = max_width

        return self._tabulate(info_dict, col_widths, changed, order, bounds_dict)

    def _tabulate(self, info_dict, col_widths, changed, order, bounds_dict):
        """
        Returns the supplied information as a table suitable for
        printing or paging.

        info_dict:  Dictionary of the parameters name, type and mode.
        col_widths: Dictionary of column widths in characters
        changed:    List of parameters modified from their defaults.
        order:      The order of the table columns
        bound_dict: Dictionary of appropriately formatted bounds
        """

        contents, tail = [], []
        column_set = set(k for row in info_dict.values() for k in row)
        columns = [col for col in order if col in column_set]

        title_row = []
        # Generate the column headings
        for i, col in enumerate(columns):
            width = col_widths[col]+2
            col = col.capitalize()
            formatted = col.ljust(width) if i == 0 else col.center(width)
            title_row.append(formatted)
        contents.append(blue % ''.join(title_row)+"\n")

        # Format the table rows
        for row in sorted(info_dict):
            row_list = []
            info = info_dict[row]
            for i,col in enumerate(columns):
                width = col_widths[col]+2
                val = info[col] if (col in info) else ''
                formatted = val.ljust(width) if i==0 else val.center(width)

                if col == 'bounds' and bounds_dict.get(row,False):
                    (mark_lbound, mark_ubound) = bounds_dict[row]
                    lval, uval = formatted.rsplit(',')
                    lspace, lstr = lval.rsplit('(')
                    ustr, uspace = uval.rsplit(')')
                    lbound = lspace + '('+(cyan % lstr) if mark_lbound else lval
                    ubound = (cyan % ustr)+')'+uspace if mark_ubound else uval
                    formatted = "%s,%s" % (lbound, ubound)
                row_list.append(formatted)

            row_text = ''.join(row_list)
            if row in changed:
                row_text = red % row_text

            contents.append(row_text)

        return '\n'.join(contents+tail)

    def __call__(self, param_obj):
        """
        Given a parameterized object or class, display information
        about the parameters in the IPython pager.
        """

        if not self.metaclass:
            parameterized_object = isinstance(param_obj, param.Parameterized)
            parameterized_class = (isinstance(param_obj,type)
                                   and  issubclass(param_obj,param.Parameterized))

            if not (parameterized_object or parameterized_class):
                print("Object is not a parameterized class or object.")
                return

        title = 'Parameters of %r' % param_obj.name
        heading_line = '=' * len(title)
        heading_text = "%s\n%s\n" % (title, heading_line)

        param_info = self._get_param_info(param_obj, include_super=True)

        if not param_info[0]:
            return "%s\n%s" % ((green % heading_text), "Object has no parameters.")

        table = self._build_table(param_info, self.order, max_col_len=40,
                                  only_changed=False)

        docstrings = self._param_docstrings(param_info, max_col_len=100, only_changed=False)

        dflt_msg = "Parameters changed from their default values are marked in red."
        top_heading = (green % heading_text)
        top_heading += "\n%s" % (red % dflt_msg)
        top_heading += "\n%s" % (cyan % "Soft bound values are marked in cyan.")
        top_heading += '\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None'

        heading_text = 'Parameter docstrings:'
        heading_string = "%s\n%s" % (heading_text, '=' * len(heading_text))
        docstring_heading = (green % heading_string)
        return "%s\n\n%s\n\n%s\n\n%s" % (top_heading, table, docstring_heading, docstrings)

message = """Welcome to the param IPython extension! (http://ioam.github.io/param/)"""
message += '\nAvailable magics: %params'

_loaded = False

def load_ipython_extension(ip, verbose=True):

    from IPython.core.magic import Magics, magics_class, line_magic
    from IPython.core import page

    @magics_class
    class ParamMagics(Magics):
        """
        Implements the %params line magic used to inspect the parameters
        of a parameterized class or object.
        """
        def __init__(self, *args, **kwargs):
            super(ParamMagics, self).__init__(*args, **kwargs)
            self.param_pager = ParamPager()

        @line_magic
        def params(self, parameter_s='', namespaces=None):
            """
            The %params line magic accepts a single argument which is a
            handle on the parameterized object to be inspected. If the
            object can be found in the active namespace, information about
            the object's parameters is displayed in the IPython pager.

            Usage: %params <parameterized class or object>
            """
            if parameter_s=='':
                print("Please specify an object to inspect.")
                return

            # Beware! Uses IPython internals that may change in future...
            obj = self.shell._object_find(parameter_s)
            if obj.found is False:
                print("Object %r not found in the namespace." % parameter_s)
                return

            page.page(self.param_pager(obj.obj))

    if verbose: print(message)

    global _loaded
    if not _loaded:
        _loaded = True
        ip.register_magics(ParamMagics)
<commit_msg>Improved docstrings and error messages in ParamPager<commit_after>"""
Optional IPython extension for working with Parameters.

This extension offers extended but completely optional functionality
for IPython users.  From within IPython, it may be loaded using:

%load_ext param.ipython

This will register the %params line magic to allow easy inspection of
all the parameters defined on a parameterized class or object:

%params <parameterized class or object>

All parameters of the class or object will be listed in the IPython
pager together with all their corresponding attributes and
docstrings. Note that the class or object to be inspected must already
exist in the active namespace.
"""

__author__ = "Jean-Luc Stevens"

import textwrap
import param

# Whether to generate warnings when misformatted docstrings are found
WARN_MISFORMATTED_DOCSTRINGS = False

# ANSI color codes for the IPython pager
red   = '\x1b[1;31m%s\x1b[0m'
blue  = '\x1b[1;34m%s\x1b[0m'
green = '\x1b[1;32m%s\x1b[0m'
cyan = '\x1b[1;36m%s\x1b[0m'

class ParamPager(object):
    """
    Callable class that displays information about the supplied
    Parameterized object or class in the IPython pager.
    """

    def __init__(self, metaclass=False):
        """
        If metaclass is set to True, the checks for Parameterized
        classes objects are disabled. This option is for use in
        ParameterizedMetaclass for automatic docstring generation.
        """
        # Order of the information to be listed in the table (left to right)
        self.order = ['name', 'changed', 'value', 'type', 'bounds', 'mode']
        self.metaclass = metaclass

    def _get_param_info(self, obj, include_super=True):
        """
        Get the parameter dictionary, the list of modifed parameters
        and the dictionary or parameter values. If include_super is
        True, parameters are also collected from the super classes.
        """

        params = dict(obj.params())
        if isinstance(obj,type):
            changed = []
            val_dict = dict((k,p.default) for (k,p) in params.items())
            self_class = obj
        else:
            changed = [name for (name,_) in obj.get_param_values(onlychanged=True)]
            val_dict = dict(obj.get_param_values())
            self_class = obj.__class__

        if not include_super:
            params = dict((k,v) for (k,v) in params.items()
                          if k in self_class.__dict__)

        params.pop('name') # Already displayed in the title.
        return (params, val_dict, changed)

    def _param_docstrings(self, info, max_col_len=100, only_changed=False):
        """
        Build a string to that presents all of the parameter
        docstrings in a clean format (alternating red and blue for
        readability).
        """

        (params, val_dict, changed) = info
        contents = []
        displayed_params = {}
        for name, p in params.items():
            if only_changed and not (name in changed):
                continue
            displayed_params[name] = p

        right_shift = max(len(name) for name in displayed_params.keys())+2

        for i, name in enumerate(sorted(displayed_params)):
            p = displayed_params[name]
            heading = "%s: " % name
            unindented = textwrap.dedent("< No docstring available >" if p.doc is None else p.doc)

            if (WARN_MISFORMATTED_DOCSTRINGS
                and not unindented.startswith("\n")  and len(unindented.splitlines()) > 1):
                param.main.warning("Multi-line docstring for %r is incorrectly formatted "
                                   " (should start with newline)" % name)
            # Strip any starting newlines
            while unindented.startswith("\n"):
                unindented = unindented[1:]

            lines = unindented.splitlines()
            if len(lines) > 1:
                tail = ['%s%s' % (' '  * right_shift, line) for line in lines[1:]]
                all_lines = [ heading.ljust(right_shift) + lines[0]] + tail
            else:
                all_lines = [ heading.ljust(right_shift) + lines[0]]

            if i % 2:  # Alternate red and blue for docstrings
                contents.extend([red %el for el in all_lines])
            else:
                contents.extend([blue %el for el in all_lines])

        return "\n".join(contents)

    def _build_table(self, info, order, max_col_len=40, only_changed=False):
        """
        Collect the information about parameters needed to build a
        properly formatted table and then tabulate it.
        """

        info_dict, bounds_dict = {}, {}
        (params, val_dict, changed) = info
        col_widths = dict((k,0) for k in order)

        for name, p in params.items():
            if only_changed and not (name in changed):
                continue

            constant = 'C' if p.constant else 'V'
            readonly = 'RO' if p.readonly else 'RW'
            allow_None = ' AN' if hasattr(p, 'allow_None') and p.allow_None else ''

            mode = '%s %s%s' % (constant, readonly, allow_None)
            info_dict[name] = {'name': name, 'type':p.__class__.__name__,
                               'mode':mode}

            if hasattr(p, 'bounds'):
                lbound, ubound = (None,None) if p.bounds is None else p.bounds

                mark_lbound, mark_ubound = False, False
                # Use soft_bounds when bounds not defined.
                if hasattr(p, 'get_soft_bounds'):
                    soft_lbound, soft_ubound = p.get_soft_bounds()
                    if lbound is None and soft_lbound is not None:
                        lbound = soft_lbound
                        mark_lbound = True
                    if ubound is None and soft_ubound is not None:
                        ubound = soft_ubound
                        mark_ubound = True

                if (lbound, ubound) != (None,None):
                    bounds_dict[name] = (mark_lbound, mark_ubound)
                    info_dict[name]['bounds'] = '(%s, %s)' % (lbound, ubound)

            value = repr(val_dict[name])
            if len(value) > (max_col_len - 3):
                value = value[:max_col_len-3] + '...'
            info_dict[name]['value'] = value

            for col in info_dict[name]:
                max_width = max([col_widths[col], len(info_dict[name][col])])
                col_widths[col] = max_width

        return self._tabulate(info_dict, col_widths, changed, order, bounds_dict)

    def _tabulate(self, info_dict, col_widths, changed, order, bounds_dict):
        """
        Returns the supplied information as a table suitable for
        printing or paging.

        info_dict:  Dictionary of the parameters name, type and mode.
        col_widths: Dictionary of column widths in characters
        changed:    List of parameters modified from their defaults.
        order:      The order of the table columns
        bound_dict: Dictionary of appropriately formatted bounds
        """

        contents, tail = [], []
        column_set = set(k for row in info_dict.values() for k in row)
        columns = [col for col in order if col in column_set]

        title_row = []
        # Generate the column headings
        for i, col in enumerate(columns):
            width = col_widths[col]+2
            col = col.capitalize()
            formatted = col.ljust(width) if i == 0 else col.center(width)
            title_row.append(formatted)
        contents.append(blue % ''.join(title_row)+"\n")

        # Format the table rows
        for row in sorted(info_dict):
            row_list = []
            info = info_dict[row]
            for i,col in enumerate(columns):
                width = col_widths[col]+2
                val = info[col] if (col in info) else ''
                formatted = val.ljust(width) if i==0 else val.center(width)

                if col == 'bounds' and bounds_dict.get(row,False):
                    (mark_lbound, mark_ubound) = bounds_dict[row]
                    lval, uval = formatted.rsplit(',')
                    lspace, lstr = lval.rsplit('(')
                    ustr, uspace = uval.rsplit(')')
                    lbound = lspace + '('+(cyan % lstr) if mark_lbound else lval
                    ubound = (cyan % ustr)+')'+uspace if mark_ubound else uval
                    formatted = "%s,%s" % (lbound, ubound)
                row_list.append(formatted)

            row_text = ''.join(row_list)
            if row in changed:
                row_text = red % row_text

            contents.append(row_text)

        return '\n'.join(contents+tail)

    def __call__(self, param_obj):
        """
        Given a Parameterized object or class, display information
        about the parameters in the IPython pager.
        """

        if not self.metaclass:
            parameterized_object = isinstance(param_obj, param.Parameterized)
            parameterized_class = (isinstance(param_obj,type)
                                   and  issubclass(param_obj,param.Parameterized))

            if not (parameterized_object or parameterized_class):
                print("Object is not a Parameterized class or object.")
                return

        title = 'Parameters of %r' % param_obj.name
        heading_line = '=' * len(title)
        heading_text = "%s\n%s\n" % (title, heading_line)

        param_info = self._get_param_info(param_obj, include_super=True)

        if not param_info[0]:
            return "%s\n%s" % ((green % heading_text), "Object has no parameters.")

        table = self._build_table(param_info, self.order, max_col_len=40,
                                  only_changed=False)

        docstrings = self._param_docstrings(param_info, max_col_len=100, only_changed=False)

        dflt_msg = "Parameters changed from their default values are marked in red."
        top_heading = (green % heading_text)
        top_heading += "\n%s" % (red % dflt_msg)
        top_heading += "\n%s" % (cyan % "Soft bound values are marked in cyan.")
        top_heading += '\nC/V= Constant/Variable, RO/RW = ReadOnly/ReadWrite, AN=Allow None'

        heading_text = 'Parameter docstrings:'
        heading_string = "%s\n%s" % (heading_text, '=' * len(heading_text))
        docstring_heading = (green % heading_string)
        return "%s\n\n%s\n\n%s\n\n%s" % (top_heading, table, docstring_heading, docstrings)

message = """Welcome to the param IPython extension! (http://ioam.github.io/param/)"""
message += '\nAvailable magics: %params'

_loaded = False

def load_ipython_extension(ip, verbose=True):

    from IPython.core.magic import Magics, magics_class, line_magic
    from IPython.core import page

    @magics_class
    class ParamMagics(Magics):
        """
        Implements the %params line magic used to inspect the parameters
        of a parameterized class or object.
        """
        def __init__(self, *args, **kwargs):
            super(ParamMagics, self).__init__(*args, **kwargs)
            self.param_pager = ParamPager()

        @line_magic
        def params(self, parameter_s='', namespaces=None):
            """
            The %params line magic accepts a single argument which is a
            handle on the parameterized object to be inspected. If the
            object can be found in the active namespace, information about
            the object's parameters is displayed in the IPython pager.

            Usage: %params <parameterized class or object>
            """
            if parameter_s=='':
                print("Please specify an object to inspect.")
                return

            # Beware! Uses IPython internals that may change in future...
            obj = self.shell._object_find(parameter_s)
            if obj.found is False:
                print("Object %r not found in the namespace." % parameter_s)
                return

            page.page(self.param_pager(obj.obj))

    if verbose: print(message)

    global _loaded
    if not _loaded:
        _loaded = True
        ip.register_magics(ParamMagics)
<|endoftext|>