Skip to content

Commit

Permalink
First changes for #104 and #109
Browse files Browse the repository at this point in the history
* Added a receive method to Communicator receive a Query
* Added recipient.receive(self) to the send method of Query, to make the recipient receive the Query. Indeed, not forcing him to do so would require a completely different coding mechanism (using triggers, timeouts, etc, with the recipient periodically checking for any pending query)
* Used the \_\_getattr\_\_ attribute to pass unknown methods to communicator. This way, a sender or recipient can handle queries in a transparent way (while in practice it passes them to its communicator, exactly as a person woul process a query through its brain)
* Added recipient, owner attributes whenever needed, more to come; indeed the most important thing when handling question/answers is to know who speaks and to whom
* changed a few method names
* added arguments to the check_sender and check_recipient methods in Query so we can check the names against a list passed as an argument (useful to test if a Query is indeed addressed to you)
  • Loading branch information
jmmelko committed Feb 21, 2020
1 parent 84e444e commit 2d3743f
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 61 deletions.
18 changes: 13 additions & 5 deletions python/command_line.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
#!/usr/bin/env python3
from responder import Responder
from modes import ResponseMode
from music_sheet_maker import MusicSheetMaker
from communicator import Communicator
#from modes import ResponseMode
#song_responder = Responder()
#song_responder.set_response_mode(ResponseMode.COMMAND_LINE)
#song_responder.create_song_command_line()
me = 'bot'

song_responder = Responder()
song_responder.set_response_mode(ResponseMode.COMMAND_LINE)
song_responder.create_song_command_line()

comm = Communicator(owner=me)

maker = MusicSheetMaker()

q = comm.formulate('song_creation')
comm.store(q)
q.send(recipient=maker)

#maker.ask



78 changes: 43 additions & 35 deletions python/communication.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,44 +203,51 @@ def check_and_pack(self):
self.build_result()
self.hash_identifier()

def check_sender(self):
if self.sender is None:
raise InvalidQueryError('invalid sender for ID=' + str(self.get_identifier()) +': ' + str(self.sender))
else:
if len(self.valid_locutors) == 0:
return True
else:
def check_locutor(self, locutor, allowed='all', forbidden=None):

locutor_ok = True

if locutor is None:
locutor_ok = False
raise InvalidQueryError('invalid locutor for ID=' + str(self.get_identifier()) +': ' + str(self.locutor))

if len(self.valid_locutors) != 0:
# TODO: more elaborate checking
# if isinstance(self.sender, bot) or ...
if type(self.sender) != type(self.valid_locutors[0]):
raise InvalidQueryError('invalid sender for ID=' + str(self.get_identifier()) +'. It must be of ' \
+ repr(type(self.valid_locutors[0]).__name__) \
+ ' type and among ' + str(self.valid_locutors))
else:
if self.sender in self.valid_locutors:
return True

return False

def check_recipient(self):
if self.recipient is None:
raise InvalidQueryError('invalid recipient for ID=' + str(self.get_identifier()) +': ' + str(self.recipient))
else:
if len(self.valid_locutors) == 0:
return True
else:
# TODO: more elaborate checking
if self.recipient == self.sender:
raise InvalidQueryError('sender cannot ask a question to itself')
if type(locutor) != type(self.valid_locutors[0]):
locutor_ok = False
raise InvalidQueryError('invalid locutor for ID=' + str(self.get_identifier()) +'. It must be of ' \
+ repr(type(self.valid_locutors[0]).__name__) \
+ ' type and among ' + str(self.valid_locutors))

if locutor not in self.valid_locutors:
locutor_ok = False

if allowed != 'all':
if locutor != allowed and locutor not in allowed:
locutor_ok = False

if forbidden != None:
if locutor == forbidden or locutor in forbidden:
locutor_ok = False

return locutor_ok


if type(self.recipient) != type(self.valid_locutors[0]):
raise InvalidQueryError('invalid recipient for ID=' + str(self.get_identifier()) +'. It must be of ' \
+ repr(type(self.valid_locutors[0]).__name__) \
+ ' type and among ' + str(self.valid_locutors))
def check_sender(self, allowed='all', forbidden=None):

sender_ok = self.check_locutor(self.sender, allowed, forbidden)

return sender_ok

return True
def check_recipient(self, allowed='all', forbidden=None):

recipient_ok = self.check_locutor(self.recipient, allowed, forbidden)

if self.recipient == self.sender:
raise InvalidQueryError('sender cannot ask a question to itself')

return False
return recipient_ok

def check_question(self):
if self.question is not None:
Expand Down Expand Up @@ -352,7 +359,7 @@ def stamp(self):

self.sent_time

def send(self):
def send(self, recipient=None):
"""
Querying is a protocol during which you first check that you are allowed to speak, that
there is someone to listen, that your question is meaningful (or allowed) and then you can send your query
Expand All @@ -365,10 +372,11 @@ def send(self):
self.is_sent = True
# TODO: decide whether asking again resets the question to unreplied to (as here)
self.is_replied = False
recipient.receive(self) #Assumes that all recipients contain a method to handle queries

return self.is_sent

def receive(self, reply_result, prerequisite=None):
def reply_to(self, reply_result, prerequisite=None):
'''
Assigns a Reply to the Query
'''
Expand Down
82 changes: 66 additions & 16 deletions python/communicator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
#import os
from modes import InputMode, ReplyType
from communication import QueryOpen, QueryChoice, QueryBoolean, QueryMemory, Information
#from communication import QueryOpen, QueryChoice, QueryBoolean, QueryMemory, Information
import communication

"""
Classes to ask and answer questions called Query and Reply between the bot and the music sheet maker
Expand All @@ -22,19 +23,69 @@
"""

class CommunicatorError(Exception):
def __init__(self, explanation):
self.explanation = explanation

# song_dir_in = 'test_songs'
# song_dir_out = 'songs_out'
def __str__(self):
return str(self.explanation)

pass

class Communicator:

def __init__(self):
self.brain = QueryMemory()
self.create_messages()

def create_messages(self):

i_instructions = Information('Instructions')
class Communicator:

def __init__(self, owner=None):
self.brain = communication.QueryMemory()
self.listening = True
self.owner = owner

#A dictionary of standard queries arguments
self.standard_queries = {}
self.standard_queries['song_creation'] = {'class': communication.QueryOpen.__name__, 'sender': 'bot', 'recipient': 'music-sheet-maker', 'question': 'Please create a Visual Sheet'}

def __getattr__(self, name):
'''
Default function to call in case no one else is found.
'''
def default_handler_function(*args, **kwargs):
try:
thefunc = getattr(self.brain, name) #If it's not for me, it's for my brain
return thefunc
except AttributeError:
try:
if isinstance(args[0], communication.Query):
self.receive(*args, **kwargs)
except:
return AttributeError

return default_handler_function

def formulate(self, standard_query_name):

try:
standard_query_name = standard_query_name.lower().replace(' ','_')
method_name = self.standard_queries[standard_query_name]['class']
method_args = self.standard_queries[standard_query_name].copy()
method_args.pop('class')
query_method = getattr(communication, method_name)
q = query_method(**method_args)
return q
except (KeyError, AttributeError):
raise CommunicatorError(str(standard_query_name) + ' is not a standard query')


def receive(self, query):

query.check_recipient(forbidden=self.owner)
self.brain.store(query)


def create_song_messages(self, recipient=None):

i_instructions = communication.Information('Instructions')

# TODO: outline queries

Expand All @@ -43,16 +94,15 @@ def create_messages(self):

# TODO: this is meant to be set after calculated by Song
modes_list = [InputMode.JIANPU, InputMode.SKY]

q_mode = QueryChoice(sender='music-sheet-maker', recipient='bot', question="Mode (1-" + str(len(modes_list)) + "): ",
q_mode = communication.QueryChoice(sender=self.owner, recipient=recipient, question="Mode (1-" + str(len(modes_list)) + "): ",
foreword="Please choose your note format:\n", afterword=None,
reply_type=ReplyType.INPUTMODE,
limits=modes_list)
self.brain.store(q_mode)

# query song key
possible_keys = []
q_song_key = QueryChoice(sender='music-sheet-maker', recipient='bot', question='What is the song key?',
q_song_key = communication.QueryChoice(sender=self.owner, recipient=recipient, question='What is the song key?',
foreword=None, afterword=None, reply_type=ReplyType.TEXT, limits=possible_keys)
self.brain.store(q_song_key)

Expand All @@ -61,19 +111,19 @@ def create_messages(self):
# info error ratio

# query song title
q_song_title = QueryOpen(sender='music-sheet-maker', recipient='bot', question='What is the song title?',
q_song_title = communication.QueryOpen(sender=self.owner, recipient=recipient, question='What is the song title?',
foreword='',
afterword=None,
reply_type=ReplyType.TEXT, limits=None)
self.brain.store(q_song_title)

# query original_artists
q_original_artists = QueryOpen(sender='music-sheet-maker', recipient='bot', question='Original artist(s): ',
q_original_artists = communication.QueryOpen(sender=self.owner, recipient=recipient, question='Original artist(s): ',
foreword='Please fill song info or press ENTER to skip:', afterword=None,
reply_type=ReplyType.TEXT, limits=None)
self.brain.store(q_original_artists)
# query transcript_writer
q_transcript_writer = QueryOpen(sender='music-sheet-maker', recipient='bot', question='Transcribed by: ',
q_transcript_writer = communication.QueryOpen(sender=self.owner, recipient=recipient, question='Transcribed by: ',
foreword=None,
afterword=None, reply_type=ReplyType.TEXT, limits=None)
self.brain.store(q_transcript_writer)
Expand Down
53 changes: 48 additions & 5 deletions python/music_sheet_maker.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,53 @@
from communicator import Communicator
from parsers import SongParser
from communicator import Communicator, CommunicatorError
#from parsers import SongParser


class MusicSheetMaker:

def __init__(self):
self.communicator = Communicator()
self.parser = SongParser()
self.song = None
self.song = None #Song object
self.name = 'music-sheet-maker'
self.communicator = Communicator(owner=self.name)
#self.parser = SongParser()
#self.receive = self.communicator.receive

def __getattr__(self, name):
'''
Default function to call in case no one else is found.
'''
return getattr(self.communicator, name)

def create_song(self, client_listening=True, recipient=None):

if not self.listening:
raise CommunicatorError(self.name+' is not listening')

self.communicator.create_song_messages(recipient=recipient)

#self.set_parser(SongParser(self))

#os.chdir(self.get_directory_base())

#communicator.output_instructions()

#communicator.ask_first_line()
#fp = self.load_file(self.get_song_dir_in(), first_line) # loads file or asks for next line
#song_lines = self.read_lines(first_line, fp)

# Parse song
# TODO: refactor song_lines, song_keys, parse_song to be in Song class
#communicator.ask_input_mode(song_lines)
#song_key = self.ask_song_key(self.get_parser().get_input_mode(), song_lines)
#note_shift = communicator.ask_note_shift()
#self.set_song(self.parse_song(song_lines, song_key, note_shift))

#self.calculate_error_ratio()

# Song information
#communicator.ask_song_title()
#communicator.ask_song_headers(song_key)

# Output
#communicator.send_song(RenderMode.PNG)


2 comments on commit 2d3743f

@jmmelko
Copy link
Collaborator Author

@jmmelko jmmelko commented on 2d3743f Feb 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to say:

  • I have created a dictionary of default queries in Communicator, I don’t know if it will be useful. We can see this as a dictionary of shortcuts. My first intention was to store in advance the usual queries (asking for key, notation, title, etc) in a convenient fashion, but as some of them require variables to be built (e.g. possibles_keys, possible_modes), they’ll have to stay in the main code, for now
  • I have commented the lines of codes that are unnecessary for now. They will be uncommented step by step.

@jmmelko
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry this commit is silly: I have looked again at the Music Cog and it opened my eyes.
Hopefully the changes were minimal so there is no need to undo the commit.

Please sign in to comment.