242 lines
8.3 KiB
Python
242 lines
8.3 KiB
Python
|
"""
|
||
|
tracker.py
|
||
|
|
||
|
Copyright (c) 2013-2014 Snowplow Analytics Ltd. All rights reserved.
|
||
|
|
||
|
This program is licensed to you under the Apache License Version 2.0,
|
||
|
and you may not use this file except in compliance with the Apache License
|
||
|
Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
|
||
|
http://www.apache.org/licenses/LICENSE-2.0.
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing,
|
||
|
software distributed under the Apache License Version 2.0 is distributed on
|
||
|
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||
|
express or implied. See the Apache License Version 2.0 for the specific
|
||
|
language governing permissions and limitations there under.
|
||
|
|
||
|
Authors: Anuj More, Alex Dean, Fred Blundun
|
||
|
Copyright: Copyright (c) 2013-2014 Snowplow Analytics Ltd
|
||
|
License: Apache License Version 2.0
|
||
|
"""
|
||
|
|
||
|
import time
|
||
|
import uuid
|
||
|
import six
|
||
|
|
||
|
from snowplow_tracker import payload, _version, SelfDescribingJson
|
||
|
from snowplow_tracker import subject as _subject
|
||
|
from snowplow_tracker.timestamp import Timestamp, TrueTimestamp, DeviceTimestamp
|
||
|
|
||
|
|
||
|
"""
|
||
|
Constants & config
|
||
|
"""
|
||
|
|
||
|
VERSION = "py-%s" % _version.__version__
|
||
|
DEFAULT_ENCODE_BASE64 = True
|
||
|
BASE_SCHEMA_PATH = "iglu:com.snowplowanalytics.snowplow"
|
||
|
SCHEMA_TAG = "jsonschema"
|
||
|
CONTEXT_SCHEMA = "%s/contexts/%s/1-0-1" % (BASE_SCHEMA_PATH, SCHEMA_TAG)
|
||
|
UNSTRUCT_EVENT_SCHEMA = "%s/unstruct_event/%s/1-0-0" % (BASE_SCHEMA_PATH, SCHEMA_TAG)
|
||
|
|
||
|
"""
|
||
|
Tracker class
|
||
|
"""
|
||
|
|
||
|
|
||
|
class Tracker:
|
||
|
def __init__(self, emitters, subject=None,
|
||
|
namespace=None, app_id=None, encode_base64=DEFAULT_ENCODE_BASE64):
|
||
|
"""
|
||
|
:param emitters: Emitters to which events will be sent
|
||
|
:type emitters: list[>0](emitter) | emitter
|
||
|
:param subject: Subject to be tracked
|
||
|
:type subject: subject | None
|
||
|
:param namespace: Identifier for the Tracker instance
|
||
|
:type namespace: string_or_none
|
||
|
:param app_id: Application ID
|
||
|
:type app_id: string_or_none
|
||
|
:param encode_base64: Whether JSONs in the payload should be base-64 encoded
|
||
|
:type encode_base64: bool
|
||
|
"""
|
||
|
if subject is None:
|
||
|
subject = _subject.Subject()
|
||
|
|
||
|
if type(emitters) is list:
|
||
|
self.emitters = emitters
|
||
|
else:
|
||
|
self.emitters = [emitters]
|
||
|
|
||
|
self.subject = subject
|
||
|
self.encode_base64 = encode_base64
|
||
|
|
||
|
self.standard_nv_pairs = {
|
||
|
"tv": VERSION,
|
||
|
"tna": namespace,
|
||
|
"aid": app_id
|
||
|
}
|
||
|
self.timer = None
|
||
|
|
||
|
@staticmethod
|
||
|
def get_uuid():
|
||
|
"""
|
||
|
Set transaction ID for the payload once during the lifetime of the
|
||
|
event.
|
||
|
|
||
|
:rtype: string
|
||
|
"""
|
||
|
return str(uuid.uuid4())
|
||
|
|
||
|
@staticmethod
|
||
|
def get_timestamp(tstamp=None):
|
||
|
"""
|
||
|
:param tstamp: User-input timestamp or None
|
||
|
:type tstamp: int | float | None
|
||
|
:rtype: int
|
||
|
"""
|
||
|
if tstamp is None:
|
||
|
return int(time.time() * 1000)
|
||
|
elif isinstance(tstamp, (int, float, )):
|
||
|
return int(tstamp)
|
||
|
|
||
|
|
||
|
"""
|
||
|
Tracking methods
|
||
|
"""
|
||
|
def track(self, pb):
|
||
|
"""
|
||
|
Send the payload to a emitter
|
||
|
|
||
|
:param pb: Payload builder
|
||
|
:type pb: payload
|
||
|
:rtype: tracker
|
||
|
"""
|
||
|
for emitter in self.emitters:
|
||
|
emitter.input(pb.nv_pairs)
|
||
|
return self
|
||
|
|
||
|
def complete_payload(self, pb, context, tstamp):
|
||
|
"""
|
||
|
Called by all tracking events to add the standard name-value pairs
|
||
|
to the Payload object irrespective of the tracked event.
|
||
|
|
||
|
:param pb: Payload builder
|
||
|
:type pb: payload
|
||
|
:param context: Custom context for the event
|
||
|
:type context: context_array | None
|
||
|
:param tstamp: Optional user-provided timestamp for the event
|
||
|
:type tstamp: timestamp | int | float | None
|
||
|
:rtype: tracker
|
||
|
"""
|
||
|
pb.add("eid", Tracker.get_uuid())
|
||
|
|
||
|
if isinstance(tstamp, TrueTimestamp):
|
||
|
pb.add("ttm", tstamp.value)
|
||
|
if isinstance(tstamp, DeviceTimestamp):
|
||
|
pb.add("dtm", Tracker.get_timestamp(tstamp.value))
|
||
|
elif isinstance(tstamp, (int, float, type(None))):
|
||
|
pb.add("dtm", Tracker.get_timestamp(tstamp))
|
||
|
|
||
|
if context is not None:
|
||
|
context_jsons = list(map(lambda c: c.to_json(), context))
|
||
|
context_envelope = SelfDescribingJson(CONTEXT_SCHEMA, context_jsons).to_json()
|
||
|
pb.add_json(context_envelope, self.encode_base64, "cx", "co")
|
||
|
|
||
|
pb.add_dict(self.standard_nv_pairs)
|
||
|
|
||
|
pb.add_dict(self.subject.standard_nv_pairs)
|
||
|
|
||
|
return self.track(pb)
|
||
|
|
||
|
def track_struct_event(self, category, action, label=None, property_=None, value=None,
|
||
|
context=None,
|
||
|
tstamp=None):
|
||
|
"""
|
||
|
:param category: Category of the event
|
||
|
:type category: non_empty_string
|
||
|
:param action: The event itself
|
||
|
:type action: non_empty_string
|
||
|
:param label: Refer to the object the action is
|
||
|
performed on
|
||
|
:type label: string_or_none
|
||
|
:param property_: Property associated with either the action
|
||
|
or the object
|
||
|
:type property_: string_or_none
|
||
|
:param value: A value associated with the user action
|
||
|
:type value: int | float | None
|
||
|
:param context: Custom context for the event
|
||
|
:type context: context_array | None
|
||
|
:rtype: tracker
|
||
|
"""
|
||
|
pb = payload.Payload()
|
||
|
pb.add("e", "se")
|
||
|
pb.add("se_ca", category)
|
||
|
pb.add("se_ac", action)
|
||
|
pb.add("se_la", label)
|
||
|
pb.add("se_pr", property_)
|
||
|
pb.add("se_va", value)
|
||
|
|
||
|
return self.complete_payload(pb, context, tstamp)
|
||
|
|
||
|
def track_unstruct_event(self, event_json, context=None, tstamp=None):
|
||
|
"""
|
||
|
:param event_json: The properties of the event. Has two field:
|
||
|
A "data" field containing the event properties and
|
||
|
A "schema" field identifying the schema against which the data is validated
|
||
|
:type event_json: self_describing_json
|
||
|
:param context: Custom context for the event
|
||
|
:type context: context_array | None
|
||
|
:param tstamp: User-set timestamp
|
||
|
:type tstamp: timestamp | int | None
|
||
|
:rtype: tracker
|
||
|
"""
|
||
|
|
||
|
envelope = SelfDescribingJson(UNSTRUCT_EVENT_SCHEMA, event_json.to_json()).to_json()
|
||
|
|
||
|
pb = payload.Payload()
|
||
|
|
||
|
pb.add("e", "ue")
|
||
|
pb.add_json(envelope, self.encode_base64, "ue_px", "ue_pr")
|
||
|
|
||
|
return self.complete_payload(pb, context, tstamp)
|
||
|
|
||
|
# Alias
|
||
|
track_self_describing_event = track_unstruct_event
|
||
|
|
||
|
def flush(self, asynchronous=False):
|
||
|
"""
|
||
|
Flush the emitter
|
||
|
|
||
|
:param asynchronous: Whether the flush is done asynchronously. Default is False
|
||
|
:type asynchronous: bool
|
||
|
:rtype: tracker
|
||
|
"""
|
||
|
for emitter in self.emitters:
|
||
|
if asynchronous:
|
||
|
emitter.flush()
|
||
|
else:
|
||
|
emitter.sync_flush()
|
||
|
return self
|
||
|
|
||
|
def set_subject(self, subject):
|
||
|
"""
|
||
|
Set the subject of the events fired by the tracker
|
||
|
|
||
|
:param subject: Subject to be tracked
|
||
|
:type subject: subject | None
|
||
|
:rtype: tracker
|
||
|
"""
|
||
|
self.subject = subject
|
||
|
return self
|
||
|
|
||
|
def add_emitter(self, emitter):
|
||
|
"""
|
||
|
Add a new emitter to which events should be passed
|
||
|
|
||
|
:param emitter: New emitter
|
||
|
:type emitter: emitter
|
||
|
:rtype: tracker
|
||
|
"""
|
||
|
self.emitters.append(emitter)
|
||
|
return self
|