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
feat(ep-factory): Implemented Event Factory and User Event Factory #194
Conversation
# Conflicts: # optimizely/event/entity/conversion_event.py # optimizely/event/entity/decision.py # optimizely/event/entity/event_context.py # optimizely/event/entity/impression_event.py
@mikeng13... This is ready from our side, It would be great if we can get a review on this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some comments about how invalid data might be handled. The handling of None
seems a little haphazard to me.
If the intent is for the event factory to be robust against missing data and return potentially invalid LogEvent
s, as opposed to crashing or returning None
s, I'd like that to be more obvious. We could have comments explaining what's going on, or validation/cleaning steps performed explicitly in their own areas, instead of mixed throughout the other logic.
But in the first place, why would we create a LogEvent
with some missing field, instead of logging error messages and returning None
? It seems simpler to only create valid LogEvent
s.
optimizely/event/event_factory.py
Outdated
LogEvent instance. | ||
""" | ||
|
||
def _dict_clean(obj): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think _dict_clean
should be a static method, not declared inside create_log_event
.
optimizely/event/event_factory.py
Outdated
|
||
@classmethod | ||
def _create_visitor(cls, user_event, logger): | ||
if not user_event: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this check necssary? In what circumstances is user_event
falsy?
optimizely/event/event_factory.py
Outdated
visitors = [] | ||
|
||
for user_event in user_events: | ||
visitors.append(cls._create_visitor(user_event, logger)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cls._create_visitor
could return None
. We have to filter out None
values from the return value before appending it to visitors
.
optimizely/event/event_factory.py
Outdated
return visitor | ||
|
||
else: | ||
# include log message for invalid event type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to log an error message here.
optimizely/event/event_factory.py
Outdated
|
||
if isinstance(user_event, ImpressionEvent): | ||
decision = Decision( | ||
user_event.experiment.layerId if user_event.experiment else None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If user_event.experiment
is None
or user_event.variation
is None
, that seems like an error or invalid data, right? Shouldn't all ImpressionEvents
have experiment
and variation
?
optimizely/event/event_factory.py
Outdated
""" Create Vistor Attribute List. | ||
|
||
Args: | ||
attributes: Dict representing user attributes and values which need to be recorded. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If attributes
might not be a dict
, let's mention what else it could be.
LogEvent is not missing a field here. EventBatch, which is sent as |
Event object encapsulating the impression event. | ||
""" | ||
|
||
experiment_key = activated_experiment.key if activated_experiment else None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API requires experiment id to be sent in a decision. So I don't think we can create a valid impression event if activated_experiment
is None
.
Wouldn't it be better to return None
from create_impression_event
in this case? Otherwise we're creating an ImpressionEvent
that won't be counted by the API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left one more comment about the return value of build_attribute_list
. After that, this LGTM. Thanks for addressing all my concerns.
optimizely/event/event_factory.py
Outdated
""" | ||
|
||
if project_config is None: | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's return an empty list in this case, instead of None
.
optimizely/event/log_event.py
Outdated
|
||
|
||
class LogEvent(object): | ||
""" Representation of an event which can be sent to the Optimizely logging endpoint. """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit. Optimizely events API
.
# Conflicts: # optimizely/event/payload.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, but I missed reviewing the import order. Please address and then it will be good to merge.
optimizely/event/event_factory.py
Outdated
|
||
from .user_event import ConversionEvent, ImpressionEvent | ||
from .payload import Decision, EventBatch, Snapshot, SnapshotEvent, Visitor, VisitorAttribute | ||
from .log_event import LogEvent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should have mentioned earlier, but import order here is incorrect
It should be external packages (alphabetic), internal packages (alphabetic), files from same package (alphabetic)
optimizely/event/log_event.py
Outdated
def __init__(self, url, params, http_verb=None, headers=None): | ||
self.url = url | ||
self.params = params | ||
self.http_verb = http_verb or 'GET' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit. Let's start defaulting to POST
. It used to be GET
long ago.
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from .user_event import EventContext, ConversionEvent, ImpressionEvent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not import user_event rather than import classes like this? Also, follow alphabetic order.
tests/test_event_factory.py
Outdated
import unittest | ||
import uuid | ||
|
||
from . import base |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue here as well as base belongs to same package and should be imported last.
from optimizely.helpers import validator | ||
from . import user_event | ||
from . import payload | ||
from . import log_event |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit. Last 3 imports are not alphabetic.
Summary
Test plan