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 generate_next_row(self) -> None: """ Creates a new row from the row generator and tells the rhythm to expect the new bells. """ if self._is_ringing_rounds: self._row = self._rounds else: self._row, self._calls = self.row_generator.next_row_and_calls( self.stroke) # Add cover bells if needed if len(self._row) < len(self._rounds): self._row = Row(self._row + self._rounds[len(self._row):]) bells = " ".join([str(bell) for bell in self._row]) self.logger.info(f"ROW: {bells}")
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 permute(self, row: Row, places: Places) -> Row: """ Permute a row by a place notation given by `places`. """ new_row = list(row) i = 1 if places and places[0] % 2 == 0: # Skip 1 for implicit lead when lowest pn is even i += 1 while i < self.stage: if i in places: i += 1 continue # If not in place, must swap, index is 1 less than place new_row[i - 1], new_row[i] = new_row[i], new_row[i - 1] i += 2 return Row(new_row)
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)])
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"))
def test_generate_without_custom_row(self): self.assertListEqual(Row([Bell(0), Bell(1), Bell(2), Bell(3)]), generate_starting_row(4))