otelq 0.2.1__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.
- otelq-0.2.1.dist-info/METADATA +280 -0
- otelq-0.2.1.dist-info/RECORD +6 -0
- otelq-0.2.1.dist-info/WHEEL +4 -0
- otelq-0.2.1.dist-info/entry_points.txt +2 -0
- otelq-0.2.1.dist-info/licenses/LICENSE +21 -0
- otelq.py +1949 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: otelq
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Feed your agentic development setup with Open Telemetry — query OTLP traces/logs/metrics captured by a local dev Collector.
|
|
5
|
+
Project-URL: Homepage, https://github.com/robertgartman/otelq
|
|
6
|
+
Project-URL: Repository, https://github.com/robertgartman/otelq
|
|
7
|
+
Project-URL: Issues, https://github.com/robertgartman/otelq/issues
|
|
8
|
+
Author: Robert Gartman
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,duckdb,observability,opentelemetry,otlp,telemetry
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: duckdb==1.5.3
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# otelq
|
|
26
|
+
|
|
27
|
+
[](https://github.com/robertgartman/otelq/actions/workflows/ci.yml)
|
|
28
|
+
[](LICENSE)
|
|
29
|
+
|
|
30
|
+
**Give your AI coding agent eyes on your app's traces, logs, and metrics.**
|
|
31
|
+
|
|
32
|
+
otelq is a tiny command-line tool that turns the OpenTelemetry signals your application already emits into answers — straight from the terminal, in the same loop your AI agent codes in. Run your code, then have your agent ask *"did the request error?"*, *"what was slow?"*, *"show me trace X"* and get a structured answer back. No Jaeger, no Grafana, no SigNoz, no server, no UI.
|
|
33
|
+
|
|
34
|
+
## Why otelq
|
|
35
|
+
|
|
36
|
+
- **Built for AI coding agents.** Feed close-the-loop verification with real traces, logs, and metrics from any OpenTelemetry-compliant app: make a change, run it, and let the agent confirm from telemetry that it actually worked.
|
|
37
|
+
- **Lightweight, fast, token-efficient.** A single-file CLI invoked on demand — structured `json`/`csv`/`table` output an agent can parse, not dashboards to scrape, no MCPs crunching your tokens. No always-on services burning resources or context.
|
|
38
|
+
- **Zero heavy infrastructure.** A stock [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) writes signals to plain JSONL files; otelq reads them in-process with DuckDB. Nothing to deploy, nothing to run between queries. A one-shot bundled demo gets you querying real signals in seconds.
|
|
39
|
+
- **Fully local, fully isolated.** Telemetry never leaves your machine — it lives in a directory you own and read directly. Nothing is shipped to a backend, a vendor, or the cloud.
|
|
40
|
+
|
|
41
|
+
## Take it for a test run
|
|
42
|
+
|
|
43
|
+
See it work in under a minute — no app to instrument. Clone the otelq repo and run the demo: it starts the Collector (in Docker) and pushes a few seconds of synthetic traces, metrics, and logs through it with [telemetrygen](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/cmd/telemetrygen), the official OpenTelemetry load generator.
|
|
44
|
+
|
|
45
|
+
When done, you have telemetry data that otelq can parse and query. The demo instructions below runs an initial summary query.
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
git clone https://github.com/robertgartman/otelq
|
|
49
|
+
cd otelq
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**With [`just`](https://github.com/casey/just)** — a small command runner (`brew install just`, `cargo install just`, or see its repo):
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
just otel-demo # Collector + generators, then waits for the flush
|
|
56
|
+
just otel-down # stop and clean up
|
|
57
|
+
|
|
58
|
+
printf '%s\n' "=== Demo queries ===" \
|
|
59
|
+
"just otelq summary" \
|
|
60
|
+
"just otelq errors" \
|
|
61
|
+
"just otelq slow --top 10" \
|
|
62
|
+
"just otelq trace <trace_id>" \
|
|
63
|
+
"just otelq logs --level ERROR --grep 'timeout'" \
|
|
64
|
+
"just otelq metric <name>" \
|
|
65
|
+
"just otelq sql 'select * from traces limit 5'" \
|
|
66
|
+
"== Running Summary =="
|
|
67
|
+
just otelq summary # summary based metrics stored under telemetry folder
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Or with plain Docker Compose** — no command runner needed:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
# start the Collector (no published host ports) and run the generators
|
|
74
|
+
docker compose -f compose.yaml -f compose.demo.yaml --profile otel up -d
|
|
75
|
+
docker compose -f compose.yaml -f compose.demo.yaml --profile demo up
|
|
76
|
+
sleep 7 # let the Collector flush its 5s batch
|
|
77
|
+
|
|
78
|
+
docker compose -f compose.yaml -f compose.demo.yaml --profile otel --profile demo down
|
|
79
|
+
|
|
80
|
+
printf '%s\n' "=== Demo queries ===" \
|
|
81
|
+
"uv run otelq.py otelq summary" \
|
|
82
|
+
"uv run otelq.py otelq errors" \
|
|
83
|
+
"uv run otelq.py otelq slow --top 10" \
|
|
84
|
+
"uv run otelq.py otelq trace <trace_id>" \
|
|
85
|
+
"uv run otelq.py otelq logs --level ERROR --grep 'timeout'" \
|
|
86
|
+
"uv run otelq.py otelq metric <name>" \
|
|
87
|
+
"uv run otelq.py otelq sql 'select * from traces limit 5'" \
|
|
88
|
+
"== Running Summary =="
|
|
89
|
+
|
|
90
|
+
uv run otelq.py summary # uv runs the single-file CLI — no install
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Both paths need [Docker](https://www.docker.com/) and [uv](https://docs.astral.sh/uv/); the `just` path additionally needs [`just`](https://github.com/casey/just). The demo generators live **only in this repo** as a testing aid — they are **never** part of integrating otelq into your own project.
|
|
94
|
+
|
|
95
|
+
## Architecture
|
|
96
|
+
|
|
97
|
+
At runtime, every component lives on your machine:
|
|
98
|
+
|
|
99
|
+
```mermaid
|
|
100
|
+
flowchart TB
|
|
101
|
+
subgraph host["Local host — nothing leaves your machine"]
|
|
102
|
+
apps["Applications generating OpenTelemetry<br/>(your services, tests, scripts)"]
|
|
103
|
+
collector["OpenTelemetry Collector<br/>· Docker container ·"]
|
|
104
|
+
skill["otelq skill"]
|
|
105
|
+
otelq["otelq · host CLI"]
|
|
106
|
+
|
|
107
|
+
subgraph project["Your project"]
|
|
108
|
+
signals["telemetry/<br/>traces.jsonl · logs.jsonl · metrics.jsonl"]
|
|
109
|
+
cache["telemetry/.otelq-cache/<br/>parquet query cache"]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
apps -->|"OTLP · gRPC :4317 / HTTP :4318"| collector
|
|
114
|
+
collector -->|"writes JSONL · bind mount"| signals
|
|
115
|
+
skill -->|invokes| otelq
|
|
116
|
+
otelq -->|reads| signals
|
|
117
|
+
otelq -->|"reads / writes"| cache
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Your application(s) send OpenTelemetry over OTLP to a Collector running in Docker. The Collector writes each signal as plain JSONL into a `telemetry/` directory bind-mounted from your project. otelq runs on the host — invoked directly or by the `otelq` skill — and reads those `.jsonl` files in-process with DuckDB, keeping an incremental parquet cache under `telemetry/.otelq-cache/` for fast repeat queries.
|
|
121
|
+
|
|
122
|
+
The bind-mounted directory is the entire contract: the Collector writes `traces.jsonl`, `logs.jsonl`, and `metrics.jsonl`; otelq reads those same files. There is no network coupling between the Collector and the CLI — the shared directory is the API.
|
|
123
|
+
|
|
124
|
+
### Using otelq in your project, with your OTEL Collector
|
|
125
|
+
|
|
126
|
+
otelq is a pure *consumer* of the telemetry directory — it never owns or runs a Collector. In any real setup the Collector belongs to **your** project: it is the one your application already sends OTLP to. You connect otelq by **teeing that Collector's output to a directory otelq can read** — add otelq's `file` exporters to the Collector so it also writes `traces.jsonl` / `logs.jsonl` / `metrics.jsonl`, then point otelq at that directory. otelq never starts, stops, or cleans that Collector; it only reads the files and owns its `.otelq-cache/` subtree.
|
|
127
|
+
|
|
128
|
+
The _direction_ of integration matters: you work **from the otelq repo** and integrate otelq **into your target project** (identified by its absolute path, e.g. `/Users/me/dev/my-service`) — not the other way around. You invoke *your* coding agent onto a `target-project-setup` skill in *this* repo.
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
# otelq runs straight from PyPI via uvx — no clone, no install:
|
|
132
|
+
alias otelq="uvx otelq"
|
|
133
|
+
|
|
134
|
+
otelq collector-config # prints the exporters + pipeline wiring to add
|
|
135
|
+
# ...paste the fragment into your project's Collector config, bind-mount its ./telemetry, restart...
|
|
136
|
+
otelq --dir /Users/me/dev/my-service/telemetry doctor # verify your wiring satisfies the contract
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`collector-config` is generated from otelq's pinned constants, so it never drifts from the contract; `doctor` checks a telemetry directory against it. The `file` exporter requires the `*-contrib` Collector image. The **target-project-setup** skill automates all of this and asks for the target project's path; see below. When exercising your own app is inconvenient, the skill can also confirm the wiring end-to-end with a throwaway [`telemetrygen`](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/cmd/telemetrygen) probe — committed, run against your Collector over its own network, then reverted — flagging first if the teed pipeline also feeds a real backend.
|
|
140
|
+
|
|
141
|
+
> **No Collector yet?** otelq bundles one purely so you can try the tool without instrumenting anything — see [Take it for a test run](#take-it-for-a-test-run). That bundled stack (and the Compose files and optional `just` recipes that manage it) is a **demo and local-dev aid, not a deployment model**: in real use the Collector lives in your project, and otelq just reads what it writes.
|
|
142
|
+
|
|
143
|
+
### Your project's production environment
|
|
144
|
+
|
|
145
|
+
otelq is a **local development** tool — nothing about it ships to production. The OpenTelemetry Collector, however, remains a perfectly valid (though not strictly necessary) component of your production stack: the same Collector your application sends OTLP to locally can run in production too, fronting your real observability backend.
|
|
146
|
+
|
|
147
|
+
The thing that must **not** carry over is otelq's wiring. When otelq is integrated into your project it adds a `file`-exporter pipeline that writes `traces.jsonl` / `logs.jsonl` / `metrics.jsonl` to a local `telemetry/` directory — that is exactly what otelq reads, and exactly what you do **not** want in production, where you ship telemetry to a remote service rather than storing it on a box.
|
|
148
|
+
|
|
149
|
+
So if you keep the Collector in production, make the configuration this project introduced into your Docker Compose **environment-conditional**:
|
|
150
|
+
|
|
151
|
+
- **Local / dev** — the `file` exporters and the bind-mounted `telemetry/` directory are active, so otelq can query the signals on your machine.
|
|
152
|
+
- **Production** — that local-storage path is switched off and the same pipelines instead point at production-grade, OTLP-compliant collectors or backends (your APM/observability vendor, a managed OTLP endpoint, etc.), shipping telemetry to the remote service instead of writing JSONL to disk.
|
|
153
|
+
|
|
154
|
+
Concretely, that means parameterizing the pieces otelq added — gating the `file` exporters and the `telemetry/` bind mount behind a profile or environment variable, and selecting the production exporter set when deploying — so a single Compose definition flips cleanly between *"store telemetry locally for otelq"* and *"ship telemetry to a remote, production-compliant collector."*
|
|
155
|
+
|
|
156
|
+
## Install / run options
|
|
157
|
+
|
|
158
|
+
**(a) Zero-install via PyPI (recommended)** — run otelq straight from [PyPI](https://pypi.org/project/otelq/) with `uvx`; no clone, no install. This is what the skill-based AI workflow uses:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
uvx otelq summary # pin a version with: uvx otelq@0.1.0 summary
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**(b) From the repo or a local clone** — `otelq.py` is a [PEP 723](https://peps.python.org/pep-0723/) single-file script, so `uv` can run it directly:
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
uv run otelq.py summary
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
## Commands
|
|
173
|
+
|
|
174
|
+
This is a dump from running `uv run otelq.py --help` within the project root:
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
|
|
178
|
+
usage: otelq [-h] [--dir DIR] [--format {table,json,csv}] [--all] [--no-cache] [--since SINCE]
|
|
179
|
+
{summary,sql,errors,slow,trace,logs,metric,collector-config,doctor,troubleshoot,help} ...
|
|
180
|
+
|
|
181
|
+
Query OTLP telemetry captured by the dev OTel Collector.
|
|
182
|
+
|
|
183
|
+
positional arguments:
|
|
184
|
+
{summary,sql,errors,slow,trace,logs,metric,collector-config,doctor,troubleshoot,help}
|
|
185
|
+
summary counts and time span per signal
|
|
186
|
+
sql run an ad-hoc SQL query
|
|
187
|
+
errors error spans and ERROR/FATAL logs
|
|
188
|
+
slow slowest spans
|
|
189
|
+
trace all spans of one trace as a tree
|
|
190
|
+
logs filtered log records
|
|
191
|
+
metric time series for one metric
|
|
192
|
+
collector-config print the file-export fragment to add to an existing Collector
|
|
193
|
+
doctor check that --dir satisfies the telemetry contract
|
|
194
|
+
troubleshoot print the capture → query loop and common fixes
|
|
195
|
+
help show help for otelq or a command
|
|
196
|
+
|
|
197
|
+
options:
|
|
198
|
+
-h, --help show this help message and exit
|
|
199
|
+
--dir DIR telemetry folder (default: /Users/robert/misc-dev/otelq/telemetry)
|
|
200
|
+
--format {table,json,csv}
|
|
201
|
+
--all widen the query to the full raw history (cold scan)
|
|
202
|
+
--no-cache bypass the parquet cache entirely (pure cold scan)
|
|
203
|
+
--since SINCE restrict to a trailing time window: Nm/Nh/Nd (e.g. 10m, 2h, 1d)
|
|
204
|
+
|
|
205
|
+
argument order:
|
|
206
|
+
--dir / --format / --all / --no-cache / --since are GLOBAL flags and
|
|
207
|
+
must come BEFORE the subcommand: otelq --since 10m --format json errors
|
|
208
|
+
(not: otelq errors --since 10m). Per-command flags (--top, --service,
|
|
209
|
+
--level, --grep) go AFTER the subcommand. Prefer --format json so output
|
|
210
|
+
is parsed, not scraped.
|
|
211
|
+
|
|
212
|
+
time window (filters by each record's own event-time):
|
|
213
|
+
(default) a recent window (the cache's hot window)
|
|
214
|
+
--since Nm|Nh|Nd only the trailing window, e.g. 10m, 2h, 1d
|
|
215
|
+
--all the full captured history (no window)
|
|
216
|
+
`trace` ignores the window — a trace id is looked up across all history.
|
|
217
|
+
|
|
218
|
+
sql views (for `otelq sql "<query>"`):
|
|
219
|
+
traces timestamp, duration (ms), trace_id, span_id, parent_span_id,
|
|
220
|
+
service_name, span_name, span_kind,
|
|
221
|
+
status_code (0=unset,1=ok,2=error), status_message
|
|
222
|
+
logs timestamp, trace_id, service_name, severity_text, body
|
|
223
|
+
metrics timestamp, service_name, metric_name, metric_type, value,
|
|
224
|
+
metric_unit (metric_type: gauge|sum|histogram|exp_histogram;
|
|
225
|
+
value = the value of gauge/sum, the sum of histogram/exp)
|
|
226
|
+
per-type metric relations (metrics unions whichever are present):
|
|
227
|
+
metrics_gauge, metrics_sum value
|
|
228
|
+
metrics_histogram, metrics_exp_histogram count, sum, min, max
|
|
229
|
+
(+ bucket_counts/explicit_bounds, or scale/zero_count/…)
|
|
230
|
+
(the OTel Summary metric type is unsupported by the reader extension)
|
|
231
|
+
|
|
232
|
+
Run `otelq troubleshoot` for the capture → query loop and common fixes.
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Run the full, authoritative command behavior.
|
|
236
|
+
|
|
237
|
+
## DuckDB pin note
|
|
238
|
+
|
|
239
|
+
The DuckDB runtime dependency is pinned exactly. This is deliberate. otelq reads OTLP JSONL via the community [`duckdb-otlp`](https://github.com/smithclay/duckdb-otlp) extension, which is built per DuckDB version — a floating DuckDB would silently fail to load the extension. CI runs an extension-probe step that loads the extension against the pinned version so the pin and the published extension stay in lockstep. See [`context/adr/ADR-003`](context/adr/ADR-003-duckdb-otlp-extension-pin-governance.md) for the decision and trade-offs.
|
|
240
|
+
|
|
241
|
+
## Agentic engineering
|
|
242
|
+
|
|
243
|
+
This repo is built to be developed with AI engineering:
|
|
244
|
+
|
|
245
|
+
- **[`AGENTS.md`](AGENTS.md)** — start here. The entry point for agents working in this repo.
|
|
246
|
+
- **[`context/CONTEXT.md`](context/CONTEXT.md)** — the documentation system (PRD / SPEC / ADR / CONTRACT routing rules).
|
|
247
|
+
- **[`.agents/skills/otelq`](.agents/skills/otelq/SKILL.md)** — the otelq skill: capture OTEL signals from the dev Collector and query them with otelq. A `.claude` shim (`.claude/skills/otelq`) mirrors it for Claude Code.
|
|
248
|
+
- **[`.agents/skills/target-project-setup`](.agents/skills/target-project-setup/SKILL.md)** — the target-project-setup skill: run from this repo to wire otelq's file-export pipeline into *another* project's existing Collector (the integrated setup above). It asks for the target project's absolute path and verifies the result with `otelq doctor`.
|
|
249
|
+
|
|
250
|
+
## Contributing
|
|
251
|
+
|
|
252
|
+
```sh
|
|
253
|
+
just lint # ruff
|
|
254
|
+
just otelq-test # pytest suite
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the full setup, the
|
|
258
|
+
maintainer branch/PR/merge workflow for this public repo, and the PR checklist.
|
|
259
|
+
Participation is governed by the
|
|
260
|
+
[`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md); report vulnerabilities per
|
|
261
|
+
[`SECURITY.md`](SECURITY.md). Issues and pull requests welcome at
|
|
262
|
+
[github.com/robertgartman/otelq](https://github.com/robertgartman/otelq).
|
|
263
|
+
|
|
264
|
+
## Acknowledgements
|
|
265
|
+
|
|
266
|
+
otelq stands on the shoulders of two outstanding open-source projects:
|
|
267
|
+
|
|
268
|
+
- **[DuckDB](https://duckdb.org/)** — the in-process analytical database that makes
|
|
269
|
+
otelq's fast, dependency-light querying possible. Heartfelt thanks to the DuckDB
|
|
270
|
+
team and its contributors for building such a remarkable engine.
|
|
271
|
+
- **[`duckdb-otlp`](https://github.com/smithclay/duckdb-otlp)** — the community
|
|
272
|
+
extension that teaches DuckDB to read OTLP telemetry. Thanks to
|
|
273
|
+
[Clay Smith](https://github.com/smithclay) and the duckdb-otlp contributors for the
|
|
274
|
+
work that otelq builds directly upon.
|
|
275
|
+
|
|
276
|
+
This project would not exist without their craftsmanship. 🦆
|
|
277
|
+
|
|
278
|
+
## License
|
|
279
|
+
|
|
280
|
+
MIT © 2026 Robert Gartman. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
otelq.py,sha256=BClgPGXOU8ZKcDUzLZU2QKHjFhc2Dfqhy6HS8lGRqtw,78437
|
|
2
|
+
otelq-0.2.1.dist-info/METADATA,sha256=9xH71ClGDVcWmevh17hqHdxDoQ2CPH9V0UAxTZEqTlo,17073
|
|
3
|
+
otelq-0.2.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
4
|
+
otelq-0.2.1.dist-info/entry_points.txt,sha256=5SBRubA88VTyn0UJf-Me-QvW1qRdAjiA9QtnCyWpyUA,37
|
|
5
|
+
otelq-0.2.1.dist-info/licenses/LICENSE,sha256=N69IT7osAdySXrGmjK9Bmj7XKD-PI2vL7TwOTRHxpEw,1071
|
|
6
|
+
otelq-0.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robert Gartman
|
|
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.
|