semetrics-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: semetrics-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Semetrics — AI-first product analytics platform
5
+ License: MIT
6
+ Project-URL: Homepage, https://semetrics.ru
7
+ Project-URL: Repository, https://github.com/trombocit/semetrics-python
8
+ Keywords: analytics,tracking,semetrics,product-analytics
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: httpx>=0.28
18
+
19
+ # Semetrics Python SDK
20
+
21
+ Python SDK для отправки аналитических событий на платформу Semetrics.
22
+
23
+ ## Установка
24
+
25
+ ```bash
26
+ pip install semetrics-sdk
27
+ ```
28
+
29
+ ## Быстрый старт
30
+
31
+ ```python
32
+ from semetrics import Semetrics
33
+
34
+ # Инициализация (один раз при старте приложения)
35
+ semetrics = Semetrics(
36
+ api_key="sm_live_ваш_ключ",
37
+ endpoint="https://semetrics.ru/events",
38
+ )
39
+
40
+ # Отправка событий (не блокирует — очередь + фоновый поток)
41
+ semetrics.track(
42
+ event_name="user_signed_up",
43
+ user_id="user_123",
44
+ properties={"plan": "pro", "source": "organic"},
45
+ )
46
+
47
+ semetrics.track(
48
+ event_name="checkout_completed",
49
+ user_id="user_123",
50
+ properties={"amount": 1990, "currency": "RUB", "items": 3},
51
+ )
52
+
53
+ # При завершении программы — отправить все накопленные события
54
+ semetrics.shutdown()
55
+ ```
56
+
57
+ ## Использование через контекстный менеджер
58
+
59
+ ```python
60
+ with Semetrics(api_key="sm_live_...", endpoint="https://semetrics.ru/events") as sm:
61
+ sm.track("page_viewed", user_id="u1", properties={"page": "/home"})
62
+ # shutdown() вызывается автоматически при выходе из блока
63
+ ```
64
+
65
+ ## Параметры конструктора
66
+
67
+ | Параметр | По умолчанию | Описание |
68
+ |----------|-------------|----------|
69
+ | `api_key` | обязательный | API-ключ проекта (`sm_live_...`) |
70
+ | `endpoint` | `https://semetrics.ru/events` | URL сервиса |
71
+ | `flush_interval` | `5` | Интервал фонового сброса (секунды) |
72
+ | `batch_size` | `50` | Максимум событий в одном запросе |
73
+ | `max_queue_size` | `10_000` | Максимум событий в памяти |
74
+ | `max_retries` | `3` | Попыток при ошибке отправки |
75
+ | `request_timeout` | `10` | Таймаут HTTP запроса (секунды) |
76
+ | `persistence_path` | `None` | Путь к SQLite для персистентной очереди |
77
+
78
+ ## Персистентная очередь (опционально)
79
+
80
+ Для серверных приложений с требованием "ни одно событие не должно потеряться":
81
+
82
+ ```python
83
+ semetrics = Semetrics(
84
+ api_key="sm_live_...",
85
+ endpoint="https://semetrics.ru/events",
86
+ persistence_path="/var/lib/myapp/semetrics_queue.db",
87
+ )
88
+ ```
89
+
90
+ События сохраняются в SQLite и отправляются даже после перезапуска процесса.
91
+
92
+ ## Принудительный сброс
93
+
94
+ ```python
95
+ # Отправить всё накопленное прямо сейчас (блокирует до завершения)
96
+ semetrics.flush()
97
+ ```
98
+
99
+ ## Поля события
100
+
101
+ | Поле | Тип | Обязательное | Описание |
102
+ |------|-----|-------------|----------|
103
+ | `event_name` | str | ✅ | Название события |
104
+ | `user_id` | str | — | ID аутентифицированного пользователя |
105
+ | `anonymous_id` | str | — | ID анонимного пользователя |
106
+ | `session_id` | str | — | ID сессии |
107
+ | `properties` | dict | — | Произвольные свойства события |
108
+ | `client_ts` | datetime | — | Время события (по умолчанию — `now()`) |
@@ -0,0 +1,90 @@
1
+ # Semetrics Python SDK
2
+
3
+ Python SDK для отправки аналитических событий на платформу Semetrics.
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ pip install semetrics-sdk
9
+ ```
10
+
11
+ ## Быстрый старт
12
+
13
+ ```python
14
+ from semetrics import Semetrics
15
+
16
+ # Инициализация (один раз при старте приложения)
17
+ semetrics = Semetrics(
18
+ api_key="sm_live_ваш_ключ",
19
+ endpoint="https://semetrics.ru/events",
20
+ )
21
+
22
+ # Отправка событий (не блокирует — очередь + фоновый поток)
23
+ semetrics.track(
24
+ event_name="user_signed_up",
25
+ user_id="user_123",
26
+ properties={"plan": "pro", "source": "organic"},
27
+ )
28
+
29
+ semetrics.track(
30
+ event_name="checkout_completed",
31
+ user_id="user_123",
32
+ properties={"amount": 1990, "currency": "RUB", "items": 3},
33
+ )
34
+
35
+ # При завершении программы — отправить все накопленные события
36
+ semetrics.shutdown()
37
+ ```
38
+
39
+ ## Использование через контекстный менеджер
40
+
41
+ ```python
42
+ with Semetrics(api_key="sm_live_...", endpoint="https://semetrics.ru/events") as sm:
43
+ sm.track("page_viewed", user_id="u1", properties={"page": "/home"})
44
+ # shutdown() вызывается автоматически при выходе из блока
45
+ ```
46
+
47
+ ## Параметры конструктора
48
+
49
+ | Параметр | По умолчанию | Описание |
50
+ |----------|-------------|----------|
51
+ | `api_key` | обязательный | API-ключ проекта (`sm_live_...`) |
52
+ | `endpoint` | `https://semetrics.ru/events` | URL сервиса |
53
+ | `flush_interval` | `5` | Интервал фонового сброса (секунды) |
54
+ | `batch_size` | `50` | Максимум событий в одном запросе |
55
+ | `max_queue_size` | `10_000` | Максимум событий в памяти |
56
+ | `max_retries` | `3` | Попыток при ошибке отправки |
57
+ | `request_timeout` | `10` | Таймаут HTTP запроса (секунды) |
58
+ | `persistence_path` | `None` | Путь к SQLite для персистентной очереди |
59
+
60
+ ## Персистентная очередь (опционально)
61
+
62
+ Для серверных приложений с требованием "ни одно событие не должно потеряться":
63
+
64
+ ```python
65
+ semetrics = Semetrics(
66
+ api_key="sm_live_...",
67
+ endpoint="https://semetrics.ru/events",
68
+ persistence_path="/var/lib/myapp/semetrics_queue.db",
69
+ )
70
+ ```
71
+
72
+ События сохраняются в SQLite и отправляются даже после перезапуска процесса.
73
+
74
+ ## Принудительный сброс
75
+
76
+ ```python
77
+ # Отправить всё накопленное прямо сейчас (блокирует до завершения)
78
+ semetrics.flush()
79
+ ```
80
+
81
+ ## Поля события
82
+
83
+ | Поле | Тип | Обязательное | Описание |
84
+ |------|-----|-------------|----------|
85
+ | `event_name` | str | ✅ | Название события |
86
+ | `user_id` | str | — | ID аутентифицированного пользователя |
87
+ | `anonymous_id` | str | — | ID анонимного пользователя |
88
+ | `session_id` | str | — | ID сессии |
89
+ | `properties` | dict | — | Произвольные свойства события |
90
+ | `client_ts` | datetime | — | Время события (по умолчанию — `now()`) |
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "semetrics-sdk"
7
+ version = "0.1.0"
8
+ description = "Python SDK for Semetrics — AI-first product analytics platform"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ keywords = ["analytics", "tracking", "semetrics", "product-analytics"]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.10",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+ dependencies = [
22
+ "httpx>=0.28",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://semetrics.ru"
27
+ Repository = "https://github.com/trombocit/semetrics-python"
28
+
29
+ [dependency-groups]
30
+ dev = ["pytest>=8", "pytest-mock>=3", "respx>=0.21"]
31
+
32
+ [tool.pytest.ini_options]
33
+ markers = ["integration: требует локального docker-compose (запускать вручную)"]
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["."]
37
+ include = ["semetrics*"]
@@ -0,0 +1,3 @@
1
+ from .client import Semetrics
2
+
3
+ __all__ = ["Semetrics"]
@@ -0,0 +1,84 @@
1
+ import logging
2
+ from datetime import datetime, timezone
3
+ from typing import Any, Optional
4
+
5
+ from .models import Event
6
+ from .queue import EventQueue
7
+ from .transport import HttpTransport
8
+ from .worker import BackgroundWorker
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ SDK_VERSION = "0.1.0"
13
+
14
+
15
+ class Semetrics:
16
+ """
17
+ Клиент Semetrics для Python.
18
+
19
+ Пример использования:
20
+ semetrics = Semetrics(api_key="sm_live_...", endpoint="https://semetrics.ru/events")
21
+ semetrics.track("user_signed_up", user_id="u123", properties={"plan": "pro"})
22
+ semetrics.shutdown() # при завершении программы
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ api_key: str,
28
+ endpoint: str = "https://semetrics.ru/events",
29
+ flush_interval: int = 5,
30
+ batch_size: int = 50,
31
+ max_queue_size: int = 10_000,
32
+ max_retries: int = 3,
33
+ request_timeout: int = 10,
34
+ persistence_path: Optional[str] = None,
35
+ ):
36
+ self._queue = EventQueue(max_size=max_queue_size, persistence_path=persistence_path)
37
+ self._transport = HttpTransport(api_key=api_key, endpoint=endpoint, timeout=request_timeout)
38
+ self._worker = BackgroundWorker(
39
+ queue=self._queue,
40
+ transport=self._transport,
41
+ flush_interval=flush_interval,
42
+ batch_size=batch_size,
43
+ max_retries=max_retries,
44
+ )
45
+ self._worker.start()
46
+
47
+ def track(
48
+ self,
49
+ event_name: str,
50
+ user_id: Optional[str] = None,
51
+ anonymous_id: Optional[str] = None,
52
+ session_id: Optional[str] = None,
53
+ properties: Optional[dict[str, Any]] = None,
54
+ client_ts: Optional[datetime] = None,
55
+ ) -> None:
56
+ """
57
+ Добавить событие в очередь.
58
+ Не блокирует вызывающий код — отправка происходит в фоне.
59
+ """
60
+ event = Event(
61
+ event_name=event_name,
62
+ user_id=user_id,
63
+ anonymous_id=anonymous_id,
64
+ session_id=session_id,
65
+ platform="python",
66
+ sdk_version=SDK_VERSION,
67
+ properties=properties,
68
+ client_ts=client_ts or datetime.now(timezone.utc),
69
+ )
70
+ self._queue.enqueue(event)
71
+
72
+ def flush(self) -> None:
73
+ """Синхронно отправить все накопленные события прямо сейчас."""
74
+ self._worker.flush_sync()
75
+
76
+ def shutdown(self) -> None:
77
+ """Остановить фоновый worker и отправить всё накопленное перед выходом."""
78
+ self._worker.stop()
79
+
80
+ def __enter__(self):
81
+ return self
82
+
83
+ def __exit__(self, *args):
84
+ self.shutdown()
@@ -0,0 +1,29 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime, timezone
3
+ from typing import Any, Optional
4
+ import uuid
5
+
6
+
7
+ @dataclass
8
+ class Event:
9
+ event_name: str
10
+ client_ts: datetime
11
+ user_id: Optional[str] = None
12
+ anonymous_id: Optional[str] = None
13
+ session_id: Optional[str] = None
14
+ platform: str = "python"
15
+ sdk_version: str = "0.1.0"
16
+ properties: Optional[dict[str, Any]] = None
17
+ db_id: Optional[int] = None # SQLite rowid, None для in-memory событий
18
+
19
+ def to_dict(self) -> dict:
20
+ return {
21
+ "event_name": self.event_name,
22
+ "user_id": self.user_id,
23
+ "anonymous_id": self.anonymous_id,
24
+ "session_id": self.session_id,
25
+ "platform": self.platform,
26
+ "sdk_version": self.sdk_version,
27
+ "client_ts": self.client_ts.isoformat(),
28
+ "properties": self.properties or {},
29
+ }
@@ -0,0 +1,104 @@
1
+ import json
2
+ import logging
3
+ import sqlite3
4
+ import threading
5
+ from collections import deque
6
+ from typing import Optional
7
+
8
+ from .models import Event
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class EventQueue:
14
+ """
15
+ Thread-safe очередь событий.
16
+ По умолчанию — in-memory.
17
+ При указании persistence_path — дополнительно сохраняет в SQLite
18
+ (события переживают перезапуск процесса).
19
+ """
20
+
21
+ def __init__(self, max_size: int = 10_000, persistence_path: Optional[str] = None):
22
+ self._max_size = max_size
23
+ self._queue: deque[Event] = deque()
24
+ self._lock = threading.Lock()
25
+ self._db: Optional[sqlite3.Connection] = None
26
+
27
+ if persistence_path:
28
+ self._init_db(persistence_path)
29
+
30
+ def _init_db(self, path: str) -> None:
31
+ self._db = sqlite3.connect(path, check_same_thread=False)
32
+ self._db.execute("""
33
+ CREATE TABLE IF NOT EXISTS queued_events (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ payload TEXT NOT NULL,
36
+ created_at REAL DEFAULT (unixepoch('now', 'subsec'))
37
+ )
38
+ """)
39
+ self._db.commit()
40
+ self._restore_from_db()
41
+
42
+ def _restore_from_db(self) -> None:
43
+ """Загрузить непереданные события из SQLite при старте."""
44
+ rows = self._db.execute("SELECT id, payload FROM queued_events ORDER BY id").fetchall()
45
+ for row_id, payload in rows:
46
+ try:
47
+ data = json.loads(payload)
48
+ from datetime import datetime
49
+ data["client_ts"] = datetime.fromisoformat(data["client_ts"])
50
+ event = Event(**data)
51
+ event.db_id = row_id
52
+ self._queue.append(event)
53
+ except Exception as exc:
54
+ logger.warning(f"Не удалось восстановить событие {row_id}: {exc}")
55
+ if rows:
56
+ logger.info(f"Восстановлено {len(rows)} событий из SQLite.")
57
+
58
+ def enqueue(self, event: Event) -> bool:
59
+ """Добавить событие в очередь. Возвращает False если очередь переполнена."""
60
+ with self._lock:
61
+ if len(self._queue) >= self._max_size:
62
+ logger.warning("Очередь переполнена, событие отброшено.")
63
+ return False
64
+ if self._db:
65
+ cursor = self._db.execute(
66
+ "INSERT INTO queued_events (payload) VALUES (?)",
67
+ (json.dumps(event.to_dict()),),
68
+ )
69
+ self._db.commit()
70
+ event.db_id = cursor.lastrowid
71
+ self._queue.append(event)
72
+ return True
73
+
74
+ def dequeue_batch(self, size: int) -> list[Event]:
75
+ """Извлечь до `size` событий из начала очереди."""
76
+ with self._lock:
77
+ batch = []
78
+ for _ in range(min(size, len(self._queue))):
79
+ batch.append(self._queue.popleft())
80
+ return batch
81
+
82
+ def requeue_batch(self, events: list[Event]) -> None:
83
+ """Вернуть события в начало очереди (после неудачной отправки)."""
84
+ with self._lock:
85
+ for event in reversed(events):
86
+ self._queue.appendleft(event)
87
+
88
+ def delete_from_db(self, events: list[Event]) -> None:
89
+ """Удалить отправленные события из SQLite по db_id."""
90
+ if not self._db:
91
+ return
92
+ ids = [e.db_id for e in events if e.db_id is not None]
93
+ if not ids:
94
+ return
95
+ with self._lock:
96
+ self._db.execute(
97
+ f"DELETE FROM queued_events WHERE id IN ({','.join('?' * len(ids))})",
98
+ ids,
99
+ )
100
+ self._db.commit()
101
+
102
+ def __len__(self) -> int:
103
+ with self._lock:
104
+ return len(self._queue)
@@ -0,0 +1,35 @@
1
+ import logging
2
+
3
+ import httpx
4
+
5
+ from .models import Event
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class HttpTransport:
11
+ def __init__(self, api_key: str, endpoint: str, timeout: int = 10):
12
+ self._api_key = api_key
13
+ self._batch_url = endpoint.rstrip("/") + "/ingest/batch"
14
+ self._timeout = timeout
15
+
16
+ def send_batch(self, events: list[Event]) -> None:
17
+ """
18
+ Отправить батч событий на сервер.
19
+ Поднимает исключение при ошибке (для retry-логики в worker'е).
20
+ """
21
+ payload = {"events": [e.to_dict() for e in events]}
22
+
23
+ with httpx.Client(timeout=self._timeout) as client:
24
+ response = client.post(
25
+ self._batch_url,
26
+ json=payload,
27
+ headers={"X-API-Key": self._api_key},
28
+ )
29
+ response.raise_for_status()
30
+
31
+ data = response.json()
32
+ if data.get("status", {}).get("code") not in ("", None):
33
+ raise RuntimeError(
34
+ f"Сервер вернул ошибку: {data['status'].get('message')}"
35
+ )
@@ -0,0 +1,69 @@
1
+ import logging
2
+ import threading
3
+ import time
4
+
5
+ from .queue import EventQueue
6
+ from .transport import HttpTransport
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class BackgroundWorker(threading.Thread):
12
+ """
13
+ Daemon thread, который периодически отправляет события из очереди.
14
+ Запускается при создании клиента, останавливается при shutdown().
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ queue: EventQueue,
20
+ transport: HttpTransport,
21
+ flush_interval: int = 5,
22
+ batch_size: int = 50,
23
+ max_retries: int = 3,
24
+ ):
25
+ super().__init__(daemon=True, name="semetrics-worker")
26
+ self._queue = queue
27
+ self._transport = transport
28
+ self._flush_interval = flush_interval
29
+ self._batch_size = batch_size
30
+ self._max_retries = max_retries
31
+ self._stop_event = threading.Event()
32
+
33
+ def run(self) -> None:
34
+ while not self._stop_event.is_set():
35
+ self._stop_event.wait(timeout=self._flush_interval)
36
+ self._flush()
37
+
38
+ def flush_sync(self) -> None:
39
+ """Отправить всё что есть прямо сейчас (вызывается из основного потока)."""
40
+ while len(self._queue) > 0:
41
+ self._flush()
42
+
43
+ def stop(self) -> None:
44
+ """Остановить worker и отправить оставшиеся события."""
45
+ self._stop_event.set()
46
+ self.flush_sync()
47
+
48
+ def _flush(self) -> None:
49
+ batch = self._queue.dequeue_batch(self._batch_size)
50
+ if not batch:
51
+ return
52
+
53
+ for attempt in range(self._max_retries):
54
+ try:
55
+ self._transport.send_batch(batch)
56
+ self._queue.delete_from_db(batch)
57
+ logger.debug(f"Отправлено {len(batch)} событий.")
58
+ return
59
+ except Exception as exc:
60
+ wait = 2 ** attempt # 1s, 2s, 4s
61
+ logger.warning(
62
+ f"Ошибка отправки (попытка {attempt + 1}/{self._max_retries}): {exc}. "
63
+ f"Повтор через {wait}с."
64
+ )
65
+ if attempt < self._max_retries - 1:
66
+ time.sleep(wait)
67
+
68
+ # После всех попыток — логируем потерю батча
69
+ logger.error(f"Батч из {len(batch)} событий потерян после {self._max_retries} попыток.")
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: semetrics-sdk
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Semetrics — AI-first product analytics platform
5
+ License: MIT
6
+ Project-URL: Homepage, https://semetrics.ru
7
+ Project-URL: Repository, https://github.com/trombocit/semetrics-python
8
+ Keywords: analytics,tracking,semetrics,product-analytics
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: httpx>=0.28
18
+
19
+ # Semetrics Python SDK
20
+
21
+ Python SDK для отправки аналитических событий на платформу Semetrics.
22
+
23
+ ## Установка
24
+
25
+ ```bash
26
+ pip install semetrics-sdk
27
+ ```
28
+
29
+ ## Быстрый старт
30
+
31
+ ```python
32
+ from semetrics import Semetrics
33
+
34
+ # Инициализация (один раз при старте приложения)
35
+ semetrics = Semetrics(
36
+ api_key="sm_live_ваш_ключ",
37
+ endpoint="https://semetrics.ru/events",
38
+ )
39
+
40
+ # Отправка событий (не блокирует — очередь + фоновый поток)
41
+ semetrics.track(
42
+ event_name="user_signed_up",
43
+ user_id="user_123",
44
+ properties={"plan": "pro", "source": "organic"},
45
+ )
46
+
47
+ semetrics.track(
48
+ event_name="checkout_completed",
49
+ user_id="user_123",
50
+ properties={"amount": 1990, "currency": "RUB", "items": 3},
51
+ )
52
+
53
+ # При завершении программы — отправить все накопленные события
54
+ semetrics.shutdown()
55
+ ```
56
+
57
+ ## Использование через контекстный менеджер
58
+
59
+ ```python
60
+ with Semetrics(api_key="sm_live_...", endpoint="https://semetrics.ru/events") as sm:
61
+ sm.track("page_viewed", user_id="u1", properties={"page": "/home"})
62
+ # shutdown() вызывается автоматически при выходе из блока
63
+ ```
64
+
65
+ ## Параметры конструктора
66
+
67
+ | Параметр | По умолчанию | Описание |
68
+ |----------|-------------|----------|
69
+ | `api_key` | обязательный | API-ключ проекта (`sm_live_...`) |
70
+ | `endpoint` | `https://semetrics.ru/events` | URL сервиса |
71
+ | `flush_interval` | `5` | Интервал фонового сброса (секунды) |
72
+ | `batch_size` | `50` | Максимум событий в одном запросе |
73
+ | `max_queue_size` | `10_000` | Максимум событий в памяти |
74
+ | `max_retries` | `3` | Попыток при ошибке отправки |
75
+ | `request_timeout` | `10` | Таймаут HTTP запроса (секунды) |
76
+ | `persistence_path` | `None` | Путь к SQLite для персистентной очереди |
77
+
78
+ ## Персистентная очередь (опционально)
79
+
80
+ Для серверных приложений с требованием "ни одно событие не должно потеряться":
81
+
82
+ ```python
83
+ semetrics = Semetrics(
84
+ api_key="sm_live_...",
85
+ endpoint="https://semetrics.ru/events",
86
+ persistence_path="/var/lib/myapp/semetrics_queue.db",
87
+ )
88
+ ```
89
+
90
+ События сохраняются в SQLite и отправляются даже после перезапуска процесса.
91
+
92
+ ## Принудительный сброс
93
+
94
+ ```python
95
+ # Отправить всё накопленное прямо сейчас (блокирует до завершения)
96
+ semetrics.flush()
97
+ ```
98
+
99
+ ## Поля события
100
+
101
+ | Поле | Тип | Обязательное | Описание |
102
+ |------|-----|-------------|----------|
103
+ | `event_name` | str | ✅ | Название события |
104
+ | `user_id` | str | — | ID аутентифицированного пользователя |
105
+ | `anonymous_id` | str | — | ID анонимного пользователя |
106
+ | `session_id` | str | — | ID сессии |
107
+ | `properties` | dict | — | Произвольные свойства события |
108
+ | `client_ts` | datetime | — | Время события (по умолчанию — `now()`) |