Пример #1
0
 def test_equality(self):
     for i in range(MAX_BELL):
         for j in range(MAX_BELL):
             # Bells with indices `i` and `j` should be equal precisely when `i == j`
             self.assertEqual(Bell.from_index(i) == Bell.from_index(j), i == j)
             # Bells with indices `i` and `j` should be not equal precisely when `i != j`
             self.assertEqual(Bell.from_index(i) != Bell.from_index(j), i != j)
Пример #2
0
 def test_from_number_error(self):
     for i in [-1, 0, MAX_BELL + 1, MAX_BELL + 2, 100, -1100]:
         with self.assertRaises(ValueError):
             Bell.from_number(i)
     # This should be disallowed by the type checker, but may as well test that it does fail
     with self.assertRaises(TypeError):
         Bell.from_number(None)
    def __init__(self, comp_id: int, access_key: Optional[str] = None) -> None:
        # Generate URL and request the rows
        url = self.complib_url + str(comp_id) + "/rows"
        if access_key:
            url += "?accessKey=" + access_key
        request_rows = requests.get(url)
        # Check for the status of the requests
        if request_rows.status_code == 404:
            raise InvalidCompError(comp_id)
        if request_rows.status_code == 403:
            raise PrivateCompError(comp_id)
        request_rows.raise_for_status()
        # Parse the request responses as JSON
        response_rows = json.loads(request_rows.text)
        unparsed_rows = response_rows['rows']
        # Determine the start row of the composition
        num_starting_rounds = 0
        while unparsed_rows[num_starting_rounds][0] == unparsed_rows[0][0]:
            num_starting_rounds += 1
        self._start_stroke = Stroke.from_index(num_starting_rounds)

        # Derive the rows, calls and stage from the JSON response
        self.loaded_rows: List[Tuple[Row, Optional[str]]] = [
            (Row([Bell.from_str(bell)
                  for bell in row]), None if call == '' else call) for row,
            call, property_bitmap in unparsed_rows[num_starting_rounds:]
        ]
        stage = response_rows['stage']

        # Variables from which the summary string is generated
        self.comp_id = comp_id
        self.comp_title = response_rows['title']
        self.is_comp_private = access_key is not None

        super().__init__(stage)
Пример #4
0
def generate_starting_row(number_of_bells: int, start_row_string: Optional[str] = None) -> Row:
    """Generate the starting row as rounds or a custom input."""
    if start_row_string is None:
        return rounds(number_of_bells)

    start_row = [Bell.from_str(i) for i in start_row_string]
    if len(start_row) > len(set(start_row)):
        raise ValueError(f"starting row '{start_row_string}' contains the same bell multiple times")

    # If there are more bells than given in the starting row
    # add the missing ones in sequential order as cover bells
    for i in range(1, number_of_bells + 1):
        if Bell.from_number(i) not in start_row:
            start_row.append(Bell.from_number(i))

    return Row(start_row)
    def __init__(self,
                 comp_id: int,
                 access_key: Optional[str] = None,
                 substituted_method_id: Optional[int] = None) -> None:
        def process_call_string(calls: str) -> List[str]:
            """Parse a sequence of calls, and remove 'Stand'."""
            stripped_calls = [x.strip() for x in calls.split(";")]
            return [c for c in stripped_calls if c != "Stand"]

        # Generate URL from the params
        query_sections: List[str] = []
        if access_key:
            query_sections.append(f"accessKey={access_key}")
        if substituted_method_id:
            query_sections.append(
                f"substitutedmethodid={substituted_method_id}")
        url = self.complib_url + str(comp_id) + "/rows"
        if query_sections:
            url += "?" + "&".join(query_sections)
        # Create an HTTP request for the rows, and deal with potential error codes
        request_rows = requests.get(url)
        if request_rows.status_code == 404:
            raise InvalidCompError(comp_id)
        if request_rows.status_code == 403:
            raise PrivateCompError(comp_id)
        request_rows.raise_for_status()
        # Parse the request responses as JSON
        response_rows = json.loads(request_rows.text)
        unparsed_rows = response_rows["rows"]
        # Determine the start row of the composition
        num_starting_rounds = 0
        while unparsed_rows[num_starting_rounds][0] == unparsed_rows[0][0]:
            num_starting_rounds += 1
        self._start_stroke = Stroke.from_index(num_starting_rounds)
        # Derive the rows, calls and stage from the JSON response
        loaded_rows: List[Tuple[Row, List[str]]] = [
            (Row([Bell.from_str(bell) for bell in row]),
             [] if calls == "" else process_call_string(calls))
            for row, calls, _property_bitmap in unparsed_rows
        ]
        # Convert these parsed rows into a format that we can read more easily when ringing.  I.e.
        # load the non-rounds rows as-is, but keep the calls that should be called in rounds
        self.loaded_rows = loaded_rows[num_starting_rounds:]
        self._early_calls = {
            num_starting_rounds - i: calls
            for i, (_row,
                    calls) in enumerate(loaded_rows[:num_starting_rounds])
            if calls != []
        }
        # Set the variables from which the summary string is generated
        self.comp_id = comp_id
        self.comp_title = response_rows["title"]
        self.is_comp_private = access_key is not None
        # Propogate the initialisation up to the parent class
        super().__init__(response_rows["stage"])
Пример #6
0
def parse_start_row(start_row: str) -> int:
    """
    Parses a start row written in the format "12345" to ensure all characters are valid bells
    and there are no missing bells.
    """
    def exit_with_message(error_text: str) -> NoReturn:
        """ Raise an exception with a useful error message. """
        raise StartRowParseError(start_row, error_text)

    if start_row is None:
        return 0

    max_bell: int = 0

    if start_row is None:
        return max_bell

    # First loop checks for invalid bells and finds the largest bell in the row
    for char in start_row:
        try:
            bell = Bell.from_str(char)
        except ValueError as e:
            exit_with_message(str(e))

        max_bell = max(max_bell, bell.number)

    # Second loop checks if there are duplicates and if every bell is specified
    expected_bells = list(range(1, max_bell + 1))
    for char in start_row:
        bell_number = Bell.from_str(char).number

        if bell_number not in expected_bells:
            exit_with_message(
                f"Start row contains bell {bell_number} mutiple times")

        expected_bells.remove(bell_number)

    if len(expected_bells) > 0:
        exit_with_message(
            f"Start row does not contain bell(s) {expected_bells}")

    return len(start_row)
Пример #7
0
 def _on_bell_rung(self, data: JSON) -> None:
     """ Callback called when the client receives a signal that a bell has been rung. """
     # Unpack the data and assign it expected types
     global_bell_state: List[bool] = data['global_bell_state']
     who_rang_raw: int = data["who_rang"]
     # Run callbacks for updating the bell state
     self._update_bell_state([Stroke(b) for b in global_bell_state])
     # Convert 'who_rang' to a Bell
     who_rang = Bell.from_number(who_rang_raw)
     # Only run the callbacks if the bells exist
     for bell_ring_callback in self.invoke_on_bell_rung:
         new_stroke = self.get_stroke(who_rang)
         if new_stroke is None:
             self.logger.warning(
                 f"Bell {who_rang} rang, but the tower only has {self.number_of_bells} bells."
             )
         else:
             bell_ring_callback(who_rang, new_stroke)
Пример #8
0
    def _on_assign_user(self, data: JSON) -> None:
        """ Callback called when a bell assignment is changed. """
        raw_bell: int = data["bell"]
        bell: Bell = Bell.from_number(raw_bell)
        user: Optional[int] = data["user"] or None

        assert isinstance(user, int) or user is None, \
               f"User ID {user} is not an integer (it has type {type(user)})."

        if user is None:
            self.logger.info(f"RECEIVED: Unassigned bell '{bell}'")
            if bell in self._assigned_users:
                del self._assigned_users[bell]
        else:
            self._assigned_users[bell] = user
            self.logger.info(
                f"RECEIVED: Assigned bell '{bell}' to '{self.user_name_from_id(user)}'"
            )
Пример #9
0
def rounds(number_of_bells: int) -> Row:
    """Generate rounds on the given number of bells."""
    return Row([Bell.from_number(i) for i in range(1, number_of_bells + 1)])
Пример #10
0
 def test_to_from_string_success(self):
     for name in BELL_NAMES:
         with self.subTest(name=name):
             self.assertEqual(str(Bell.from_str(name)), name)
Пример #11
0
import time
import unittest
from threading import Thread

from unittest.mock import Mock

from wheatley.stroke import HANDSTROKE, BACKSTROKE
from wheatley.bell import Bell
from wheatley.rhythm import Rhythm, WaitForUserRhythm

treble = Bell.from_number(1)
second = Bell.from_number(2)


class WaitForUserRhythmTests(unittest.TestCase):
    def setUp(self):
        self.mock_inner_rhythm = Mock(spec=Rhythm)
        self.wait_rhythm = WaitForUserRhythm(self.mock_inner_rhythm)
        self.wait_rhythm.sleep = self._patched_sleep

        self.waiting_for_bell_time = False

        self._finished_test = False
        self._sleeping = False
        self._return_from_sleep = False
        self.total_sleep_calls = 0
        self.total_sleep_delay = 0

    def tearDown(self):
        self._finished_test = True
Пример #12
0
 def test_from_number_success(self):
     for i in range(1, MAX_BELL + 1):
         self.assertEqual(Bell.from_number(i).number, i)
Пример #13
0
 def test_generate_without_custom_row(self):
     self.assertListEqual(Row([Bell(0), Bell(1), Bell(2), Bell(3)]), generate_starting_row(4))
Пример #14
0
 def test_from_index_success(self):
     for i in range(MAX_BELL):
         self.assertEqual(Bell.from_index(i).index, i)
Пример #15
0
 def test_from_string_error(self):
     for name in ['p', 'a', 'x', 'O', '?', '14', 'q1', '00']:
         with self.subTest(name=name):
             with self.assertRaises(ValueError):
                 Bell.from_str(name)
Пример #16
0
 def test_generate_with_custom_row(self):
     self.assertListEqual(Row([Bell(3), Bell(2), Bell(1), Bell(0)]), generate_starting_row(4, "4321"))
     self.assertListEqual(Row([Bell(3), Bell(2), Bell(1), Bell(0)]), generate_starting_row(4, "432"))
     self.assertListEqual(Row([Bell(3), Bell(1), Bell(0), Bell(2)]), generate_starting_row(4, "42"))
     self.assertListEqual(Row([Bell(1), Bell(0), Bell(2), Bell(3)]), generate_starting_row(4, "2"))
Пример #17
0
 def test_from_string_error(self):
     for name in ["p", "a", "x", "O", "?", "14", "q1", "00"]:
         with self.subTest(name=name):
             with self.assertRaises(ValueError):
                 Bell.from_str(name)
Пример #18
0
def as_bells(nums: List[int]) -> List[Bell]:
    return [Bell.from_number(x) for x in nums]