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"])
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)
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 test_to_from_string_success(self): for name in BELL_NAMES: with self.subTest(name=name): self.assertEqual(str(Bell.from_str(name)), name)
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)
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)