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)
    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"])
Exemple #3
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)
Exemple #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)
Exemple #5
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)
Exemple #6
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)
Exemple #7
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)