• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

snowplow / snowplow-python-tracker / 3956992538

pending completion
3956992538

Pull #312

github

GitHub
Merge bbb276e87 into ecca49d70
Pull Request #312: Release/0.13.0

511 of 582 new or added lines in 15 files covered. (87.8%)

2 existing lines in 1 file now uncovered.

2391 of 2522 relevant lines covered (94.81%)

11.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.76
/snowplow_tracker/tracker.py
1
# """
2
#     tracker.py
3

4
#     Copyright (c) 2013-2022 Snowplow Analytics Ltd. All rights reserved.
5

6
#     This program is licensed to you under the Apache License Version 2.0,
7
#     and you may not use this file except in compliance with the Apache License
8
#     Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
9
#     http://www.apache.org/licenses/LICENSE-2.0.
10

11
#     Unless required by applicable law or agreed to in writing,
12
#     software distributed under the Apache License Version 2.0 is distributed on
13
#     an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
14
#     express or implied. See the Apache License Version 2.0 for the specific
15
#     language governing permissions and limitations there under.
16

17
#     Authors: Anuj More, Alex Dean, Fred Blundun, Paul Boocock
18
#     Copyright: Copyright (c) 2013-2022 Snowplow Analytics Ltd
19
#     License: Apache License Version 2.0
20
# """
21

22
import time
12✔
23
import uuid
12✔
24
from typing import Any, Optional, Union, List, Dict, Sequence
12✔
25
from warnings import warn
12✔
26

27
from snowplow_tracker import payload, _version, SelfDescribingJson
12✔
28
from snowplow_tracker import subject as _subject
12✔
29
from snowplow_tracker.contracts import non_empty_string, one_of, non_empty, form_element
12✔
30
from snowplow_tracker.typing import (
12✔
31
    JsonEncoderFunction,
32
    EmitterProtocol,
33
    FORM_NODE_NAMES,
34
    FORM_TYPES,
35
    FormNodeName,
36
    ElementClasses,
37
    FormClasses,
38
)
39

40
"""
4✔
41
Constants & config
42
"""
43

44
VERSION = "py-%s" % _version.__version__
12✔
45
DEFAULT_ENCODE_BASE64 = True
12✔
46
BASE_SCHEMA_PATH = "iglu:com.snowplowanalytics.snowplow"
12✔
47
SCHEMA_TAG = "jsonschema"
12✔
48
CONTEXT_SCHEMA = "%s/contexts/%s/1-0-1" % (BASE_SCHEMA_PATH, SCHEMA_TAG)
12✔
49
UNSTRUCT_EVENT_SCHEMA = "%s/unstruct_event/%s/1-0-0" % (BASE_SCHEMA_PATH, SCHEMA_TAG)
12✔
50
ContextArray = List[SelfDescribingJson]
12✔
51

52
"""
4✔
53
Tracker class
54
"""
55

56

57
class Tracker:
12✔
58
    def __init__(
12✔
59
        self,
60
        emitters: Union[List[EmitterProtocol], EmitterProtocol],
61
        subject: Optional[_subject.Subject] = None,
62
        namespace: Optional[str] = None,
63
        app_id: Optional[str] = None,
64
        encode_base64: bool = DEFAULT_ENCODE_BASE64,
65
        json_encoder: Optional[JsonEncoderFunction] = None,
66
    ) -> None:
67
        """
68
        :param emitters:         Emitters to which events will be sent
69
        :type  emitters:         list[>0](emitter) | emitter
70
        :param subject:          Subject to be tracked
71
        :type  subject:          subject | None
72
        :param namespace:        Identifier for the Tracker instance
73
        :type  namespace:        string_or_none
74
        :param app_id:           Application ID
75
        :type  app_id:           string_or_none
76
        :param encode_base64:    Whether JSONs in the payload should be base-64 encoded
77
        :type  encode_base64:    bool
78
        :param json_encoder:     Custom JSON serializer that gets called on non-serializable object
79
        :type  json_encoder:     function | None
80
        """
81
        if subject is None:
12✔
82
            subject = _subject.Subject()
12✔
83

84
        if type(emitters) is list:
12✔
85
            non_empty(emitters)
12✔
86
            self.emitters = emitters
12✔
87
        else:
88
            self.emitters = [emitters]
12✔
89

90
        self.subject = subject
12✔
91
        self.encode_base64 = encode_base64
12✔
92
        self.json_encoder = json_encoder
12✔
93

94
        self.standard_nv_pairs = {"tv": VERSION, "tna": namespace, "aid": app_id}
12✔
95
        self.timer = None
12✔
96

97
    @staticmethod
12✔
98
    def get_uuid() -> str:
12✔
99
        """
100
        Set transaction ID for the payload once during the lifetime of the
101
        event.
102

103
        :rtype:           string
104
        """
105
        return str(uuid.uuid4())
12✔
106

107
    @staticmethod
12✔
108
    def get_timestamp(tstamp: Optional[float] = None) -> int:
12✔
109
        """
110
        :param tstamp:    User-input timestamp or None
111
        :type  tstamp:    int | float | None
112
        :rtype:           int
113
        """
114
        if isinstance(
12✔
115
            tstamp,
116
            (
117
                int,
118
                float,
119
            ),
120
        ):
121
            return int(tstamp)
12✔
122
        return int(time.time() * 1000)
12✔
123

124
    """
4✔
125
    Tracking methods
126
    """
127

128
    def track(self, pb: payload.Payload) -> "Tracker":
12✔
129
        """
130
        Send the payload to a emitter
131

132
        :param  pb:              Payload builder
133
        :type   pb:              payload
134
        :rtype:                  tracker
135
        """
136
        for emitter in self.emitters:
12✔
137
            emitter.input(pb.nv_pairs)
12✔
138
        return self
12✔
139

140
    def complete_payload(
12✔
141
        self,
142
        pb: payload.Payload,
143
        context: Optional[List[SelfDescribingJson]],
144
        tstamp: Optional[float],
145
        event_subject: Optional[_subject.Subject],
146
    ) -> "Tracker":
147
        """
148
        Called by all tracking events to add the standard name-value pairs
149
        to the Payload object irrespective of the tracked event.
150

151
        :param  pb:              Payload builder
152
        :type   pb:              payload
153
        :param  context:         Custom context for the event
154
        :type   context:         context_array | None
155
        :param  tstamp:          Optional event timestamp in milliseconds
156
        :type   tstamp:          int | float | None
157
        :param  event_subject:   Optional per event subject
158
        :type   event_subject:   subject | None
159
        :rtype:                  tracker
160
        """
161
        pb.add("eid", Tracker.get_uuid())
12✔
162

163
        pb.add("dtm", Tracker.get_timestamp())
12✔
164
        if tstamp is not None:
12✔
165
            pb.add("ttm", Tracker.get_timestamp(tstamp))
12✔
166

167
        if context is not None:
12✔
168
            context_jsons = list(map(lambda c: c.to_json(), context))
12✔
169
            context_envelope = SelfDescribingJson(
12✔
170
                CONTEXT_SCHEMA, context_jsons
171
            ).to_json()
172
            pb.add_json(
12✔
173
                context_envelope, self.encode_base64, "cx", "co", self.json_encoder
174
            )
175

176
        pb.add_dict(self.standard_nv_pairs)
12✔
177

178
        fin_subject = event_subject if event_subject is not None else self.subject
12✔
179
        pb.add_dict(fin_subject.standard_nv_pairs)
12✔
180

181
        return self.track(pb)
12✔
182

183
    def track_page_view(
12✔
184
        self,
185
        page_url: str,
186
        page_title: Optional[str] = None,
187
        referrer: Optional[str] = None,
188
        context: Optional[List[SelfDescribingJson]] = None,
189
        tstamp: Optional[float] = None,
190
        event_subject: Optional[_subject.Subject] = None,
191
    ) -> "Tracker":
192
        """
193
        :param  page_url:       URL of the viewed page
194
        :type   page_url:       non_empty_string
195
        :param  page_title:     Title of the viewed page
196
        :type   page_title:     string_or_none
197
        :param  referrer:       Referrer of the page
198
        :type   referrer:       string_or_none
199
        :param  context:        Custom context for the event
200
        :type   context:        context_array | None
201
        :param  tstamp:         Optional event timestamp in milliseconds
202
        :type   tstamp:         int | float | None
203
        :param  event_subject:  Optional per event subject
204
        :type   event_subject:  subject | None
205
        :rtype:                 tracker
206
        """
207
        non_empty_string(page_url)
12✔
208

209
        pb = payload.Payload()
12✔
210
        pb.add("e", "pv")  # pv: page view
12✔
211
        pb.add("url", page_url)
12✔
212
        pb.add("page", page_title)
12✔
213
        pb.add("refr", referrer)
12✔
214

215
        return self.complete_payload(pb, context, tstamp, event_subject)
12✔
216

217
    def track_page_ping(
12✔
218
        self,
219
        page_url: str,
220
        page_title: Optional[str] = None,
221
        referrer: Optional[str] = None,
222
        min_x: Optional[int] = None,
223
        max_x: Optional[int] = None,
224
        min_y: Optional[int] = None,
225
        max_y: Optional[int] = None,
226
        context: Optional[List[SelfDescribingJson]] = None,
227
        tstamp: Optional[float] = None,
228
        event_subject: Optional[_subject.Subject] = None,
229
    ) -> "Tracker":
230
        """
231
        :param  page_url:       URL of the viewed page
232
        :type   page_url:       non_empty_string
233
        :param  page_title:     Title of the viewed page
234
        :type   page_title:     string_or_none
235
        :param  referrer:       Referrer of the page
236
        :type   referrer:       string_or_none
237
        :param  min_x:          Minimum page x offset seen in the last ping period
238
        :type   min_x:          int | None
239
        :param  max_x:          Maximum page x offset seen in the last ping period
240
        :type   max_x:          int | None
241
        :param  min_y:          Minimum page y offset seen in the last ping period
242
        :type   min_y:          int | None
243
        :param  max_y:          Maximum page y offset seen in the last ping period
244
        :type   max_y:          int | None
245
        :param  context:        Custom context for the event
246
        :type   context:        context_array | None
247
        :param  tstamp:         Optional event timestamp in milliseconds
248
        :type   tstamp:         int | float | None
249
        :param  event_subject:  Optional per event subject
250
        :type   event_subject:  subject | None
251
        :rtype:                 tracker
252
        """
253
        non_empty_string(page_url)
12✔
254

255
        pb = payload.Payload()
12✔
256
        pb.add("e", "pp")  # pp: page ping
12✔
257
        pb.add("url", page_url)
12✔
258
        pb.add("page", page_title)
12✔
259
        pb.add("refr", referrer)
12✔
260
        pb.add("pp_mix", min_x)
12✔
261
        pb.add("pp_max", max_x)
12✔
262
        pb.add("pp_miy", min_y)
12✔
263
        pb.add("pp_may", max_y)
12✔
264

265
        return self.complete_payload(pb, context, tstamp, event_subject)
12✔
266

267
    def track_link_click(
12✔
268
        self,
269
        target_url: str,
270
        element_id: Optional[str] = None,
271
        element_classes: Optional[ElementClasses] = None,
272
        element_target: Optional[str] = None,
273
        element_content: Optional[str] = None,
274
        context: Optional[List[SelfDescribingJson]] = None,
275
        tstamp: Optional[float] = None,
276
        event_subject: Optional[_subject.Subject] = None,
277
    ) -> "Tracker":
278
        """
279
        :param  target_url:     Target URL of the link
280
        :type   target_url:     non_empty_string
281
        :param  element_id:     ID attribute of the HTML element
282
        :type   element_id:     string_or_none
283
        :param  element_classes:    Classes of the HTML element
284
        :type   element_classes:    list(str) | tuple(str,\\*) | None
285
        :param  element_target:     ID attribute of the HTML element
286
        :type   element_target:     string_or_none
287
        :param  element_content:    The content of the HTML element
288
        :type   element_content:    string_or_none
289
        :param  context:        Custom context for the event
290
        :type   context:        context_array | None
291
        :param  tstamp:         Optional event timestamp in milliseconds
292
        :type   tstamp:         int | float | None
293
        :param  event_subject:  Optional per event subject
294
        :type   event_subject:  subject | None
295
        :rtype:                 tracker
296
        """
297
        non_empty_string(target_url)
12✔
298

299
        properties = {}
12✔
300
        properties["targetUrl"] = target_url
12✔
301
        if element_id is not None:
12✔
302
            properties["elementId"] = element_id
12✔
303
        if element_classes is not None:
12✔
304
            properties["elementClasses"] = element_classes
12✔
305
        if element_target is not None:
12✔
306
            properties["elementTarget"] = element_target
12✔
307
        if element_content is not None:
12✔
308
            properties["elementContent"] = element_content
12✔
309

310
        event_json = SelfDescribingJson(
12✔
311
            "%s/link_click/%s/1-0-1" % (BASE_SCHEMA_PATH, SCHEMA_TAG), properties
312
        )
313

314
        return self.track_self_describing_event(
12✔
315
            event_json, context, tstamp, event_subject
316
        )
317

318
    def track_add_to_cart(
12✔
319
        self,
320
        sku: str,
321
        quantity: int,
322
        name: Optional[str] = None,
323
        category: Optional[str] = None,
324
        unit_price: Optional[float] = None,
325
        currency: Optional[str] = None,
326
        context: Optional[List[SelfDescribingJson]] = None,
327
        tstamp: Optional[float] = None,
328
        event_subject: Optional[_subject.Subject] = None,
329
    ) -> "Tracker":
330
        """
331
        :param  sku:            Item SKU or ID
332
        :type   sku:            non_empty_string
333
        :param  quantity:       Number added to cart
334
        :type   quantity:       int
335
        :param  name:           Item's name
336
        :type   name:           string_or_none
337
        :param  category:       Item's category
338
        :type   category:       string_or_none
339
        :param  unit_price:     Item's price
340
        :type   unit_price:     int | float | None
341
        :param  currency:       Type of currency the price is in
342
        :type   currency:       string_or_none
343
        :param  context:        Custom context for the event
344
        :type   context:        context_array | None
345
        :param  tstamp:         Optional event timestamp in milliseconds
346
        :type   tstamp:         int | float | None
347
        :param  event_subject:  Optional per event subject
348
        :type   event_subject:  subject | None
349
        :rtype:                 tracker
350
        """
351
        non_empty_string(sku)
12✔
352

353
        properties = {}
12✔
354
        properties["sku"] = sku
12✔
355
        properties["quantity"] = quantity
12✔
356
        if name is not None:
12✔
357
            properties["name"] = name
12✔
358
        if category is not None:
12✔
359
            properties["category"] = category
12✔
360
        if unit_price is not None:
12✔
361
            properties["unitPrice"] = unit_price
12✔
362
        if currency is not None:
12✔
363
            properties["currency"] = currency
12✔
364

365
        event_json = SelfDescribingJson(
12✔
366
            "%s/add_to_cart/%s/1-0-0" % (BASE_SCHEMA_PATH, SCHEMA_TAG), properties
367
        )
368

369
        return self.track_self_describing_event(
12✔
370
            event_json, context, tstamp, event_subject
371
        )
372

373
    def track_remove_from_cart(
12✔
374
        self,
375
        sku: str,
376
        quantity: int,
377
        name: Optional[str] = None,
378
        category: Optional[str] = None,
379
        unit_price: Optional[float] = None,
380
        currency: Optional[str] = None,
381
        context: Optional[List[SelfDescribingJson]] = None,
382
        tstamp: Optional[float] = None,
383
        event_subject: Optional[_subject.Subject] = None,
384
    ) -> "Tracker":
385
        """
386
        :param  sku:            Item SKU or ID
387
        :type   sku:            non_empty_string
388
        :param  quantity:       Number added to cart
389
        :type   quantity:       int
390
        :param  name:           Item's name
391
        :type   name:           string_or_none
392
        :param  category:       Item's category
393
        :type   category:       string_or_none
394
        :param  unit_price:     Item's price
395
        :type   unit_price:     int | float | None
396
        :param  currency:       Type of currency the price is in
397
        :type   currency:       string_or_none
398
        :param  context:        Custom context for the event
399
        :type   context:        context_array | None
400
        :param  tstamp:         Optional event timestamp in milliseconds
401
        :type   tstamp:         int | float | None
402
        :param  event_subject:  Optional per event subject
403
        :type   event_subject:  subject | None
404
        :rtype:                 tracker
405
        """
406
        non_empty_string(sku)
12✔
407

408
        properties = {}
12✔
409
        properties["sku"] = sku
12✔
410
        properties["quantity"] = quantity
12✔
411
        if name is not None:
12✔
412
            properties["name"] = name
12✔
413
        if category is not None:
12✔
414
            properties["category"] = category
12✔
415
        if unit_price is not None:
12✔
416
            properties["unitPrice"] = unit_price
12✔
417
        if currency is not None:
12✔
418
            properties["currency"] = currency
12✔
419

420
        event_json = SelfDescribingJson(
12✔
421
            "%s/remove_from_cart/%s/1-0-0" % (BASE_SCHEMA_PATH, SCHEMA_TAG), properties
422
        )
423

424
        return self.track_self_describing_event(
12✔
425
            event_json, context, tstamp, event_subject
426
        )
427

428
    def track_form_change(
12✔
429
        self,
430
        form_id: str,
431
        element_id: Optional[str],
432
        node_name: FormNodeName,
433
        value: Optional[str],
434
        type_: Optional[str] = None,
435
        element_classes: Optional[ElementClasses] = None,
436
        context: Optional[List[SelfDescribingJson]] = None,
437
        tstamp: Optional[float] = None,
438
        event_subject: Optional[_subject.Subject] = None,
439
    ) -> "Tracker":
440
        """
441
        :param  form_id:        ID attribute of the HTML form
442
        :type   form_id:        non_empty_string
443
        :param  element_id:     ID attribute of the HTML element
444
        :type   element_id:     string_or_none
445
        :param  node_name:      Type of input element
446
        :type   node_name:      form_node_name
447
        :param  value:          Value of the input element
448
        :type   value:          string_or_none
449
        :param  type_:          Type of data the element represents
450
        :type   type_:          non_empty_string, form_type
451
        :param  element_classes:    Classes of the HTML element
452
        :type   element_classes:    list(str) | tuple(str,\\*) | None
453
        :param  context:        Custom context for the event
454
        :type   context:        context_array | None
455
        :param  tstamp:         Optional event timestamp in milliseconds
456
        :type   tstamp:         int | float | None
457
        :param  event_subject:  Optional per event subject
458
        :type   event_subject:  subject | None
459
        :rtype:                 tracker
460
        """
461
        non_empty_string(form_id)
12✔
462
        one_of(node_name, FORM_NODE_NAMES)
12✔
463
        if type_ is not None:
12✔
464
            one_of(type_.lower(), FORM_TYPES)
12✔
465

466
        properties = dict()
12✔
467
        properties["formId"] = form_id
12✔
468
        properties["elementId"] = element_id
12✔
469
        properties["nodeName"] = node_name
12✔
470
        properties["value"] = value
12✔
471
        if type_ is not None:
12✔
472
            properties["type"] = type_
12✔
473
        if element_classes is not None:
12✔
474
            properties["elementClasses"] = element_classes
12✔
475

476
        event_json = SelfDescribingJson(
12✔
477
            "%s/change_form/%s/1-0-0" % (BASE_SCHEMA_PATH, SCHEMA_TAG), properties
478
        )
479

480
        return self.track_self_describing_event(
12✔
481
            event_json, context, tstamp, event_subject
482
        )
483

484
    def track_form_submit(
12✔
485
        self,
486
        form_id: str,
487
        form_classes: Optional[FormClasses] = None,
488
        elements: Optional[List[Dict[str, Any]]] = None,
489
        context: Optional[List[SelfDescribingJson]] = None,
490
        tstamp: Optional[float] = None,
491
        event_subject: Optional[_subject.Subject] = None,
492
    ) -> "Tracker":
493
        """
494
        :param  form_id:        ID attribute of the HTML form
495
        :type   form_id:        non_empty_string
496
        :param  form_classes:   Classes of the HTML form
497
        :type   form_classes:   list(str) | tuple(str,\\*) | None
498
        :param  elements:       Classes of the HTML form
499
        :type   elements:       list(form_element) | None
500
        :param  context:        Custom context for the event
501
        :type   context:        context_array | None
502
        :param  tstamp:         Optional event timestamp in milliseconds
503
        :type   tstamp:         int | float | None
504
        :param  event_subject:  Optional per event subject
505
        :type   event_subject:  subject | None
506
        :rtype:                 tracker
507
        """
508
        non_empty_string(form_id)
12✔
509
        for element in elements or []:
12✔
510
            form_element(element)
12✔
511

512
        properties = dict()
12✔
513
        properties["formId"] = form_id
12✔
514
        if form_classes is not None:
12✔
515
            properties["formClasses"] = form_classes
12✔
516
        if elements is not None and len(elements) > 0:
12✔
517
            properties["elements"] = elements
12✔
518

519
        event_json = SelfDescribingJson(
12✔
520
            "%s/submit_form/%s/1-0-0" % (BASE_SCHEMA_PATH, SCHEMA_TAG), properties
521
        )
522

523
        return self.track_self_describing_event(
12✔
524
            event_json, context, tstamp, event_subject
525
        )
526

527
    def track_site_search(
12✔
528
        self,
529
        terms: Sequence[str],
530
        filters: Optional[Dict[str, Union[str, bool]]] = None,
531
        total_results: Optional[int] = None,
532
        page_results: Optional[int] = None,
533
        context: Optional[List[SelfDescribingJson]] = None,
534
        tstamp: Optional[float] = None,
535
        event_subject: Optional[_subject.Subject] = None,
536
    ) -> "Tracker":
537
        """
538
        :param  terms:          Search terms
539
        :type   terms:          seq[>=1](str)
540
        :param  filters:        Filters applied to the search
541
        :type   filters:        dict(str:str|bool) | None
542
        :param  total_results:  Total number of results returned
543
        :type   total_results:  int | None
544
        :param  page_results:   Total number of pages of results
545
        :type   page_results:   int | None
546
        :param  context:        Custom context for the event
547
        :type   context:        context_array | None
548
        :param  tstamp:         Optional event timestamp in milliseconds
549
        :type   tstamp:         int | float | None
550
        :param  event_subject:  Optional per event subject
551
        :type   event_subject:  subject | None
552
        :rtype:                 tracker
553
        """
554
        non_empty(terms)
12✔
555

556
        properties = {}
12✔
557
        properties["terms"] = terms
12✔
558
        if filters is not None:
12✔
559
            properties["filters"] = filters
12✔
560
        if total_results is not None:
12✔
561
            properties["totalResults"] = total_results
12✔
562
        if page_results is not None:
12✔
563
            properties["pageResults"] = page_results
12✔
564

565
        event_json = SelfDescribingJson(
12✔
566
            "%s/site_search/%s/1-0-0" % (BASE_SCHEMA_PATH, SCHEMA_TAG), properties
567
        )
568

569
        return self.track_self_describing_event(
12✔
570
            event_json, context, tstamp, event_subject
571
        )
572

573
    def track_ecommerce_transaction_item(
12✔
574
        self,
575
        order_id: str,
576
        sku: str,
577
        price: float,
578
        quantity: int,
579
        name: Optional[str] = None,
580
        category: Optional[str] = None,
581
        currency: Optional[str] = None,
582
        context: Optional[List[SelfDescribingJson]] = None,
583
        tstamp: Optional[float] = None,
584
        event_subject: Optional[_subject.Subject] = None,
585
    ) -> "Tracker":
586
        """
587
        This is an internal method called by track_ecommerce_transaction.
588
        It is not for public use.
589

590
        :param  order_id:    Order ID
591
        :type   order_id:    non_empty_string
592
        :param  sku:         Item SKU
593
        :type   sku:         non_empty_string
594
        :param  price:       Item price
595
        :type   price:       int | float
596
        :param  quantity:    Item quantity
597
        :type   quantity:    int
598
        :param  name:        Item name
599
        :type   name:        string_or_none
600
        :param  category:    Item category
601
        :type   category:    string_or_none
602
        :param  currency:    The currency the price is expressed in
603
        :type   currency:    string_or_none
604
        :param  context:     Custom context for the event
605
        :type   context:     context_array | None
606
        :param  tstamp:      Optional event timestamp in milliseconds
607
        :type   tstamp:      int | float | None
608
        :param  event_subject:  Optional per event subject
609
        :type   event_subject:  subject | None
610
        :rtype:              tracker
611
        """
612
        non_empty_string(order_id)
12✔
613
        non_empty_string(sku)
12✔
614

615
        pb = payload.Payload()
12✔
616
        pb.add("e", "ti")
12✔
617
        pb.add("ti_id", order_id)
12✔
618
        pb.add("ti_sk", sku)
12✔
619
        pb.add("ti_nm", name)
12✔
620
        pb.add("ti_ca", category)
12✔
621
        pb.add("ti_pr", price)
12✔
622
        pb.add("ti_qu", quantity)
12✔
623
        pb.add("ti_cu", currency)
12✔
624

625
        return self.complete_payload(pb, context, tstamp, event_subject)
12✔
626

627
    def track_ecommerce_transaction(
12✔
628
        self,
629
        order_id: str,
630
        total_value: float,
631
        affiliation: Optional[str] = None,
632
        tax_value: Optional[float] = None,
633
        shipping: Optional[float] = None,
634
        city: Optional[str] = None,
635
        state: Optional[str] = None,
636
        country: Optional[str] = None,
637
        currency: Optional[str] = None,
638
        items: Optional[List[Dict[str, Any]]] = None,
639
        context: Optional[List[SelfDescribingJson]] = None,
640
        tstamp: Optional[float] = None,
641
        event_subject: Optional[_subject.Subject] = None,
642
    ) -> "Tracker":
643
        """
644
        :param  order_id:       ID of the eCommerce transaction
645
        :type   order_id:       non_empty_string
646
        :param  total_value:    Total transaction value
647
        :type   total_value:    int | float
648
        :param  affiliation:    Transaction affiliation
649
        :type   affiliation:    string_or_none
650
        :param  tax_value:      Transaction tax value
651
        :type   tax_value:      int | float | None
652
        :param  shipping:       Delivery cost charged
653
        :type   shipping:       int | float | None
654
        :param  city:           Delivery address city
655
        :type   city:           string_or_none
656
        :param  state:          Delivery address state
657
        :type   state:          string_or_none
658
        :param  country:        Delivery address country
659
        :type   country:        string_or_none
660
        :param  currency:       The currency the price is expressed in
661
        :type   currency:       string_or_none
662
        :param  items:          The items in the transaction
663
        :type   items:          list(dict(str:\\*)) | None
664
        :param  context:        Custom context for the event
665
        :type   context:        context_array | None
666
        :param  tstamp:         Optional event timestamp in milliseconds
667
        :type   tstamp:         int | float | None
668
        :param  event_subject:  Optional per event subject
669
        :type   event_subject:  subject | None
670
        :rtype:                 tracker
671
        """
672
        non_empty_string(order_id)
12✔
673

674
        pb = payload.Payload()
12✔
675
        pb.add("e", "tr")
12✔
676
        pb.add("tr_id", order_id)
12✔
677
        pb.add("tr_tt", total_value)
12✔
678
        pb.add("tr_af", affiliation)
12✔
679
        pb.add("tr_tx", tax_value)
12✔
680
        pb.add("tr_sh", shipping)
12✔
681
        pb.add("tr_ci", city)
12✔
682
        pb.add("tr_st", state)
12✔
683
        pb.add("tr_co", country)
12✔
684
        pb.add("tr_cu", currency)
12✔
685

686
        tstamp = Tracker.get_timestamp(tstamp)
12✔
687

688
        self.complete_payload(pb, context, tstamp, event_subject)
12✔
689

690
        if items is None:
12✔
691
            items = []
12✔
692
        for item in items:
12✔
693
            item["tstamp"] = tstamp
12✔
694
            item["event_subject"] = event_subject
12✔
695
            item["order_id"] = order_id
12✔
696
            item["currency"] = currency
12✔
697
            self.track_ecommerce_transaction_item(**item)
12✔
698

699
        return self
12✔
700

701
    def track_screen_view(
12✔
702
        self,
703
        name: Optional[str] = None,
704
        id_: Optional[str] = None,
705
        context: Optional[List[SelfDescribingJson]] = None,
706
        tstamp: Optional[float] = None,
707
        event_subject: Optional[_subject.Subject] = None,
708
    ) -> "Tracker":
709
        """
710
        :param  name:           The name of the screen view event
711
        :type   name:           string_or_none
712
        :param  id_:            Screen view ID
713
        :type   id_:            string_or_none
714
        :param  context:        Custom context for the event
715
        :type   context:        context_array | None
716
        :param  tstamp:         Optional event timestamp in milliseconds
717
        :type   tstamp:         int | float | None
718
        :param  event_subject:  Optional per event subject
719
        :type   event_subject:  subject | None
720
        :rtype:                 tracker
721
        """
722
        screen_view_properties = {}
12✔
723
        if name is not None:
12✔
724
            screen_view_properties["name"] = name
12✔
725
        if id_ is not None:
12✔
726
            screen_view_properties["id"] = id_
12✔
727

728
        event_json = SelfDescribingJson(
12✔
729
            "%s/screen_view/%s/1-0-0" % (BASE_SCHEMA_PATH, SCHEMA_TAG),
730
            screen_view_properties,
731
        )
732

733
        return self.track_self_describing_event(
12✔
734
            event_json, context, tstamp, event_subject
735
        )
736

737
    def track_struct_event(
12✔
738
        self,
739
        category: str,
740
        action: str,
741
        label: Optional[str] = None,
742
        property_: Optional[str] = None,
743
        value: Optional[float] = None,
744
        context: Optional[List[SelfDescribingJson]] = None,
745
        tstamp: Optional[float] = None,
746
        event_subject: Optional[_subject.Subject] = None,
747
    ) -> "Tracker":
748
        """
749
        :param  category:       Category of the event
750
        :type   category:       non_empty_string
751
        :param  action:         The event itself
752
        :type   action:         non_empty_string
753
        :param  label:          Refer to the object the action is
754
                                performed on
755
        :type   label:          string_or_none
756
        :param  property_:      Property associated with either the action
757
                                or the object
758
        :type   property_:      string_or_none
759
        :param  value:          A value associated with the user action
760
        :type   value:          int | float | None
761
        :param  context:        Custom context for the event
762
        :type   context:        context_array | None
763
        :param  tstamp:         Optional event timestamp in milliseconds
764
        :type   tstamp:         int | float | None
765
        :param  event_subject:  Optional per event subject
766
        :type   event_subject:  subject | None
767
        :rtype:                 tracker
768
        """
769
        non_empty_string(category)
12✔
770
        non_empty_string(action)
12✔
771

772
        pb = payload.Payload()
12✔
773
        pb.add("e", "se")
12✔
774
        pb.add("se_ca", category)
12✔
775
        pb.add("se_ac", action)
12✔
776
        pb.add("se_la", label)
12✔
777
        pb.add("se_pr", property_)
12✔
778
        pb.add("se_va", value)
12✔
779

780
        return self.complete_payload(pb, context, tstamp, event_subject)
12✔
781

782
    def track_self_describing_event(
12✔
783
        self,
784
        event_json: SelfDescribingJson,
785
        context: Optional[List[SelfDescribingJson]] = None,
786
        tstamp: Optional[float] = None,
787
        event_subject: Optional[_subject.Subject] = None,
788
    ) -> "Tracker":
789
        """
790
        :param  event_json:      The properties of the event. Has two field:
791
                                 A "data" field containing the event properties and
792
                                 A "schema" field identifying the schema against which the data is validated
793
        :type   event_json:      self_describing_json
794
        :param  context:         Custom context for the event
795
        :type   context:         context_array | None
796
        :param  tstamp:          Optional event timestamp in milliseconds
797
        :type   tstamp:          int | float | None
798
        :param  event_subject:   Optional per event subject
799
        :type   event_subject:   subject | None
800
        :rtype:                  tracker
801
        """
802

803
        envelope = SelfDescribingJson(
12✔
804
            UNSTRUCT_EVENT_SCHEMA, event_json.to_json()
805
        ).to_json()
806

807
        pb = payload.Payload()
12✔
808

809
        pb.add("e", "ue")
12✔
810
        pb.add_json(envelope, self.encode_base64, "ue_px", "ue_pr", self.json_encoder)
12✔
811

812
        return self.complete_payload(pb, context, tstamp, event_subject)
12✔
813

814
    # Alias
815
    def track_unstruct_event(
12✔
816
        self,
817
        event_json: SelfDescribingJson,
818
        context: Optional[List[SelfDescribingJson]] = None,
819
        tstamp: Optional[float] = None,
820
        event_subject: Optional[_subject.Subject] = None,
821
    ) -> "Tracker":
822
        """
823
        :param  event_json:      The properties of the event. Has two field:
824
                                 A "data" field containing the event properties and
825
                                 A "schema" field identifying the schema against which the data is validated
826
        :type   event_json:      self_describing_json
827
        :param  context:         Custom context for the event
828
        :type   context:         context_array | None
829
        :param  tstamp:          Optional event timestamp in milliseconds
830
        :type   tstamp:          int | float | None
831
        :param  event_subject:   Optional per event subject
832
        :type   event_subject:   subject | None
833
        :rtype:                  tracker
834
        """
NEW
835
        warn(
×
836
            "track_unstruct_event will be deprecated in future versions. Please use track_self_describing_event.",
837
            DeprecationWarning,
838
            stacklevel=2,
839
        )
NEW
840
        return self.track_self_describing_event(
×
841
            event_json, context, tstamp, event_subject
842
        )
843

844
    def flush(self, is_async: bool = False) -> "Tracker":
12✔
845
        """
846
        Flush the emitter
847

848
        :param  is_async:  Whether the flush is done asynchronously. Default is False
849
        :type   is_async:  bool
850
        :rtype:         tracker
851
        """
852
        for emitter in self.emitters:
12✔
853
            if is_async:
12✔
854
                if hasattr(emitter, "flush"):
12✔
855
                    emitter.flush()
12✔
856
            else:
857
                if hasattr(emitter, "sync_flush"):
12✔
858
                    emitter.sync_flush()
12✔
859
        return self
12✔
860

861
    def set_subject(self, subject: Optional[_subject.Subject]) -> "Tracker":
12✔
862
        """
863
        Set the subject of the events fired by the tracker
864

865
        :param subject: Subject to be tracked
866
        :type  subject: subject | None
867
        :rtype:         tracker
868
        """
869
        self.subject = subject
12✔
870
        return self
12✔
871

872
    def add_emitter(self, emitter: EmitterProtocol) -> "Tracker":
12✔
873
        """
874
        Add a new emitter to which events should be passed
875

876
        :param emitter: New emitter
877
        :type  emitter: emitter
878
        :rtype:         tracker
879
        """
880
        self.emitters.append(emitter)
12✔
881
        return self
12✔
882

883
    def get_namespace(self):
12✔
NEW
884
        return self.standard_nv_pairs["tna"]
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc