Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/base reporter #15

Draft
wants to merge 12 commits into
base: develop
Choose a base branch
from
85 changes: 85 additions & 0 deletions circle_evolution/reporter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Reporters for capturing events and notifying the user about them.

You can make your own Reporter class or use the already implemented one's.
Notice the abstract class before implementing your Reporter to see which
functions should be implemented.
"""
from abc import ABC, abstractmethod

import logging
import logging.config


class Reporter(ABC):
"""Base Reporter class.

The Reporter is responsible for capturing particular events and sending
them for visualization. Please, use this class if you want to implement
your own Reporter.
"""
def __init__(self):
"""Initialization calls setup to configure Reporter"""
self.setup()

def setup(self):
"""Function for configuring the reporter.

Some reporters may need configuring some internal parameters or even
creating objects to warmup. This function deals with this
"""

@abstractmethod
def update(self, report):
"""Receives report from subject.

This is the main function for reporting events. The Reporter receives a
report and have to deal with what to do with that. For Circle-Evolution
you can expect anything from strings to `numpy.array`.
"""
Copy link
Owner

Choose a reason for hiding this comment

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

First comment

I don't like the idea of having a single function to handle all commands.

My suggestion is to add many types of functions to deal with different events:

Examples

For example, we can have an onStart method which is called when evolution is about to start.
We can have an onEnd method which is called when evolution ends.
We can have an onStop method, called when the user forcibly closes simulation.
onUpdate called when mutated species replaces the parent species.
onNextGen.... e.t.c

Second comment

I also don't like the idea of having arguments passed as many types as this kind of code is prone to errors. I suggest using a class that holds information on the report.
For example, this class can hold the current generation number, current fitness, and best specie.

Caveats

The problem with my first comment is that it can slow things down especially if using something like onNextGen... So tell me if there is a faster way to do it, or if you want to suggest something else.

The problem with my second comment is that if we pass in the species reference the programmer might modify it and it will modify the actual specie because it is only a reference. But again I believe whoever is programming should be wary, and know about this through the docs.

Conclusion

Please tell me what you think about my two comments. If you have a better idea you may propose it to me.



class LoggerReporter(Reporter):
"""Reporter for logging.

This Reporter is responsible for setting up a Logger object and logging all
events that happened during circle-evolution cycle. Notice that this
reporter only cares about strings and will notify minimal details about
other types.
"""
def setup(self):
"""Sets up Logger"""
config_initial = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'simple': {
'format': '%(asctime)s %(name)s %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
},
'loggers': {
'circle-evolution': {
'handlers': ['console'],
'level': 'DEBUG',
}
},
'root': {
'handlers': ['console'],
'level': 'DEBUG'
}
}
logging.config.dictConfig(config_initial)
self.logger = logging.getLogger(__name__) # Creating new logger

def update(self, report):
"""Logs events using logger"""
self.logger.debug("Received event...")
if isinstance(report, str):
# Only deals with string messages
self.logger.info(report)
28 changes: 28 additions & 0 deletions circle_evolution/runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Base Class for Reported Objects.

If you want to receive reports about any objects in Circle-Evolution you just
need to extend your class with the base Runner. It provides an interface for
attaching reporters and notifying all reporters of a particular event.
"""


class Runner:
"""Base Runner class.

The Runner class is responsible for managing reporters and sending events
to them. If you need to receive updates by a particular reporter you just
need to use this base class.

Attributes:
_reporters: list of reporters that are going to receive reports.
"""
_reporters = []

def attach(self, reporter):
"""Attaches reporter for notifications"""
self._reporters.append(reporter)

def notify(self, report):
"""Send report to all attached reporters"""
for reporter in self._reporters:
reporter.update(report)