wirelog 0.1.0__py3-none-any.whl

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.
wirelog/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """WireLog analytics client — zero dependencies."""
2
+
3
+ from wirelog.client import WireLog
4
+
5
+ __all__ = ["WireLog"]
6
+ __version__ = "0.1.0"
wirelog/client.py ADDED
@@ -0,0 +1,138 @@
1
+ """WireLog analytics client. Zero external dependencies — stdlib only."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ import uuid
9
+ from typing import Any
10
+ from urllib.error import HTTPError
11
+ from urllib.request import Request, urlopen
12
+
13
+
14
+ class WireLogError(Exception):
15
+ """Raised when the WireLog API returns an error."""
16
+
17
+ def __init__(self, status: int, message: str) -> None:
18
+ super().__init__(f"WireLog API {status}: {message}")
19
+ self.status = status
20
+
21
+
22
+ class WireLog:
23
+ """WireLog analytics client.
24
+
25
+ Zero external dependencies. Uses only the Python standard library.
26
+
27
+ Args:
28
+ api_key: API key (pk_, sk_, or aat_). Falls back to WIRELOG_API_KEY env var.
29
+ host: API base URL. Falls back to WIRELOG_HOST env var or https://api.wirelog.ai.
30
+ timeout: HTTP timeout in seconds. Default 30.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ api_key: str | None = None,
36
+ host: str | None = None,
37
+ timeout: int = 30,
38
+ ) -> None:
39
+ self.api_key = api_key or os.environ.get("WIRELOG_API_KEY", "")
40
+ self.host = (
41
+ host or os.environ.get("WIRELOG_HOST", "https://api.wirelog.ai")
42
+ ).rstrip("/")
43
+ self.timeout = timeout
44
+
45
+ def track(
46
+ self,
47
+ event_type: str,
48
+ *,
49
+ user_id: str | None = None,
50
+ device_id: str | None = None,
51
+ session_id: str | None = None,
52
+ event_properties: dict[str, Any] | None = None,
53
+ user_properties: dict[str, Any] | None = None,
54
+ insert_id: str | None = None,
55
+ ) -> dict[str, Any]:
56
+ """Track a single event. Returns {"accepted": N}."""
57
+ body: dict[str, Any] = {"event_type": event_type}
58
+ if user_id is not None:
59
+ body["user_id"] = user_id
60
+ if device_id is not None:
61
+ body["device_id"] = device_id
62
+ if session_id is not None:
63
+ body["session_id"] = session_id
64
+ if event_properties is not None:
65
+ body["event_properties"] = event_properties
66
+ if user_properties is not None:
67
+ body["user_properties"] = user_properties
68
+ if insert_id is not None:
69
+ body["insert_id"] = insert_id
70
+ else:
71
+ body["insert_id"] = uuid.uuid4().hex
72
+ body["time"] = _iso_now()
73
+ return self._post("/track", body)
74
+
75
+ def track_batch(self, events: list[dict[str, Any]]) -> dict[str, Any]:
76
+ """Track multiple events. Returns {"accepted": N}."""
77
+ return self._post("/track", {"events": events})
78
+
79
+ def query(
80
+ self,
81
+ q: str,
82
+ *,
83
+ format: str = "llm",
84
+ limit: int = 100,
85
+ offset: int = 0,
86
+ ) -> Any:
87
+ """Run a pipe DSL query. Returns Markdown (default), JSON, or CSV."""
88
+ return self._post(
89
+ "/query",
90
+ {"q": q, "format": format, "limit": limit, "offset": offset},
91
+ )
92
+
93
+ def identify(
94
+ self,
95
+ user_id: str,
96
+ *,
97
+ device_id: str | None = None,
98
+ user_properties: dict[str, Any] | None = None,
99
+ user_property_ops: dict[str, Any] | None = None,
100
+ ) -> dict[str, Any]:
101
+ """Bind device to user and/or set profile properties."""
102
+ body: dict[str, Any] = {"user_id": user_id}
103
+ if device_id is not None:
104
+ body["device_id"] = device_id
105
+ if user_properties is not None:
106
+ body["user_properties"] = user_properties
107
+ if user_property_ops is not None:
108
+ body["user_property_ops"] = user_property_ops
109
+ return self._post("/identify", body)
110
+
111
+ def _post(self, path: str, body: dict[str, Any]) -> Any:
112
+ """Send a POST request to the WireLog API."""
113
+ url = f"{self.host}{path}"
114
+ data = json.dumps(body).encode("utf-8")
115
+ req = Request(
116
+ url,
117
+ data=data,
118
+ headers={
119
+ "Content-Type": "application/json",
120
+ "X-API-Key": self.api_key,
121
+ },
122
+ method="POST",
123
+ )
124
+ try:
125
+ with urlopen(req, timeout=self.timeout) as resp:
126
+ raw = resp.read()
127
+ content_type = resp.headers.get("Content-Type", "")
128
+ if "application/json" in content_type:
129
+ return json.loads(raw)
130
+ return raw.decode("utf-8")
131
+ except HTTPError as e:
132
+ msg = e.read().decode("utf-8", errors="replace")
133
+ raise WireLogError(e.code, msg) from e
134
+
135
+
136
+ def _iso_now() -> str:
137
+ """Current UTC time in ISO 8601 format."""
138
+ return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: wirelog
3
+ Version: 0.1.0
4
+ Summary: WireLog analytics client — zero dependencies
5
+ Project-URL: Homepage, https://wirelog.ai
6
+ Project-URL: Repository, https://github.com/wirelogai/wirelog-python
7
+ Project-URL: Documentation, https://docs.wirelog.ai
8
+ Author-email: WireLog <hello@wirelog.ai>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai-agents,analytics,events,tracking,wirelog
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+
25
+ # wirelog
26
+
27
+ [WireLog](https://wirelog.ai) analytics client for Python. **Zero dependencies** — stdlib only.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install wirelog
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ from wirelog import WireLog
39
+
40
+ wl = WireLog(api_key="sk_your_secret_key")
41
+
42
+ # Track an event
43
+ wl.track("signup", user_id="u_123", event_properties={"plan": "free"})
44
+
45
+ # Query analytics (returns Markdown by default)
46
+ result = wl.query("signup | last 7d | count by day")
47
+ print(result)
48
+
49
+ # Identify a user (bind device → user, set profile)
50
+ wl.identify("alice@acme.org", device_id="dev_abc", user_properties={"plan": "pro"})
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ ```python
56
+ wl = WireLog(
57
+ api_key="sk_...", # or set WIRELOG_API_KEY env var
58
+ host="https://api.wirelog.ai", # or set WIRELOG_HOST env var
59
+ timeout=30, # HTTP timeout in seconds
60
+ )
61
+ ```
62
+
63
+ ## API
64
+
65
+ ### `wl.track(event_type, *, user_id, device_id, session_id, event_properties, user_properties, insert_id)`
66
+
67
+ Track a single event. Auto-generates `insert_id` and `time` if not provided.
68
+
69
+ ### `wl.track_batch(events)`
70
+
71
+ Track multiple events in one request (up to 2000).
72
+
73
+ ### `wl.query(q, *, format="llm", limit=100, offset=0)`
74
+
75
+ Run a pipe DSL query. Format: `"llm"` (Markdown), `"json"`, or `"csv"`.
76
+
77
+ ### `wl.identify(user_id, *, device_id, user_properties, user_property_ops)`
78
+
79
+ Bind a device to a user and/or update profile properties.
80
+
81
+ ## Zero Dependencies
82
+
83
+ This library uses only the Python standard library (`urllib.request`, `json`, `time`, `uuid`, `os`). No `requests`, no `httpx`, no `urllib3`. It works out of the box on any Python 3.9+ installation.
84
+
85
+ ## Learn More
86
+
87
+ - [WireLog](https://wirelog.ai) — headless analytics for agents and LLMs
88
+ - [Query language docs](https://docs.wirelog.ai/query-language)
89
+ - [API reference](https://docs.wirelog.ai/reference/api)
@@ -0,0 +1,6 @@
1
+ wirelog/__init__.py,sha256=lV8DGI-ueFdmlfgrJ2NngapSiWXG0rs6vD8vudbQfiU,135
2
+ wirelog/client.py,sha256=5BlkJUnau1pAf6WevCsT6HR0ARs1vL2GoTon_9BIxbs,4595
3
+ wirelog-0.1.0.dist-info/METADATA,sha256=g4hqtzepgoZ6Zunl2UovpYPraDb8a7wY1lJ6kgcmDAg,2773
4
+ wirelog-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
5
+ wirelog-0.1.0.dist-info/licenses/LICENSE,sha256=Xp6_oelz4Jy_uERw5PFQdkeeAO9XdAfJCgjBeg0F1L0,1064
6
+ wirelog-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WireLog
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.