pytest-resilience-agent 0.2.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.
Files changed (64) hide show
  1. pytest_resilience_agent-0.2.0/.github/dependabot.yml +25 -0
  2. pytest_resilience_agent-0.2.0/.github/workflows/ci.yml +44 -0
  3. pytest_resilience_agent-0.2.0/.github/workflows/codeql.yml +38 -0
  4. pytest_resilience_agent-0.2.0/.github/workflows/dependabot-auto-merge.yml +29 -0
  5. pytest_resilience_agent-0.2.0/.github/workflows/publish.yml +33 -0
  6. pytest_resilience_agent-0.2.0/.github/workflows/scorecard.yml +42 -0
  7. pytest_resilience_agent-0.2.0/.gitignore +40 -0
  8. pytest_resilience_agent-0.2.0/.pre-commit-config.yaml +17 -0
  9. pytest_resilience_agent-0.2.0/CHANGELOG.md +113 -0
  10. pytest_resilience_agent-0.2.0/CONTRIBUTING.md +40 -0
  11. pytest_resilience_agent-0.2.0/LICENSE +21 -0
  12. pytest_resilience_agent-0.2.0/PKG-INFO +278 -0
  13. pytest_resilience_agent-0.2.0/README.md +243 -0
  14. pytest_resilience_agent-0.2.0/SECURITY.md +24 -0
  15. pytest_resilience_agent-0.2.0/assets/demo.gif +0 -0
  16. pytest_resilience_agent-0.2.0/demo/__init__.py +0 -0
  17. pytest_resilience_agent-0.2.0/demo/example_agent_tests/__init__.py +0 -0
  18. pytest_resilience_agent-0.2.0/demo/example_agent_tests/test_summarise_resilience.py +85 -0
  19. pytest_resilience_agent-0.2.0/demo/mock_lark.py +92 -0
  20. pytest_resilience_agent-0.2.0/demo/mock_truefoundry.py +99 -0
  21. pytest_resilience_agent-0.2.0/demo/run_demo.py +191 -0
  22. pytest_resilience_agent-0.2.0/demo/run_full_loop.py +207 -0
  23. pytest_resilience_agent-0.2.0/demo/sample_agent/__init__.py +0 -0
  24. pytest_resilience_agent-0.2.0/demo/sample_agent/app.py +119 -0
  25. pytest_resilience_agent-0.2.0/demo/scenarios/chaos_patterns.json +41 -0
  26. pytest_resilience_agent-0.2.0/docs/adr/0001-direction-reversal.md +55 -0
  27. pytest_resilience_agent-0.2.0/docs/adr/0002-respx-as-injection-layer.md +44 -0
  28. pytest_resilience_agent-0.2.0/docs/adr/0003-rule-based-generator.md +42 -0
  29. pytest_resilience_agent-0.2.0/docs/devpost_submission.md +116 -0
  30. pytest_resilience_agent-0.2.0/docs/screenshot/demo_full_loop.png +0 -0
  31. pytest_resilience_agent-0.2.0/docs/screenshot/render_demo_screenshot.py +109 -0
  32. pytest_resilience_agent-0.2.0/pyproject.toml +70 -0
  33. pytest_resilience_agent-0.2.0/scripts/smoke_live_integrations.py +128 -0
  34. pytest_resilience_agent-0.2.0/src/pytest_resilience_agent/__init__.py +8 -0
  35. pytest_resilience_agent-0.2.0/src/pytest_resilience_agent/chaos.py +174 -0
  36. pytest_resilience_agent-0.2.0/src/pytest_resilience_agent/cli.py +199 -0
  37. pytest_resilience_agent-0.2.0/src/pytest_resilience_agent/gateway.py +75 -0
  38. pytest_resilience_agent-0.2.0/src/pytest_resilience_agent/generator.py +118 -0
  39. pytest_resilience_agent-0.2.0/src/pytest_resilience_agent/lark_mcp.py +131 -0
  40. pytest_resilience_agent-0.2.0/src/pytest_resilience_agent/plugin.py +179 -0
  41. pytest_resilience_agent-0.2.0/src/pytest_resilience_agent/scenarios.py +471 -0
  42. pytest_resilience_agent-0.2.0/tests/__init__.py +0 -0
  43. pytest_resilience_agent-0.2.0/tests/conftest.py +5 -0
  44. pytest_resilience_agent-0.2.0/tests/test_chaos_scenarios.py +198 -0
  45. pytest_resilience_agent-0.2.0/tests/test_marker_and_fixtures.py +63 -0
  46. pytest_resilience_agent-0.2.0/tests/test_multi_turn_chaos.py +150 -0
  47. pytest_resilience_agent-0.2.0/tests/test_timeline_and_report.py +68 -0
  48. pytest_resilience_agent-0.2.0/videos/CREDITS.md +26 -0
  49. pytest_resilience_agent-0.2.0/videos/concat.txt +21 -0
  50. pytest_resilience_agent-0.2.0/videos/pytest-resilience-agent-demo.mp4 +0 -0
  51. pytest_resilience_agent-0.2.0/videos/slides/beat-01-title.png +0 -0
  52. pytest_resilience_agent-0.2.0/videos/slides/beat-02-problem.png +0 -0
  53. pytest_resilience_agent-0.2.0/videos/slides/beat-03-gap.png +0 -0
  54. pytest_resilience_agent-0.2.0/videos/slides/beat-04-lark.png +0 -0
  55. pytest_resilience_agent-0.2.0/videos/slides/beat-05-generator.png +0 -0
  56. pytest_resilience_agent-0.2.0/videos/slides/beat-06-pytest.png +0 -0
  57. pytest_resilience_agent-0.2.0/videos/slides/beat-07-loop.png +0 -0
  58. pytest_resilience_agent-0.2.0/videos/slides/beat-08-arch.png +0 -0
  59. pytest_resilience_agent-0.2.0/videos/slides/beat-09-built.png +0 -0
  60. pytest_resilience_agent-0.2.0/videos/slides/beat-10-tagout.png +0 -0
  61. pytest_resilience_agent-0.2.0/videos/slides/make_slides.py +653 -0
  62. pytest_resilience_agent-0.2.0/videos/subtitles.ass +24 -0
  63. pytest_resilience_agent-0.2.0/videos/subtitles.srt +64 -0
  64. pytest_resilience_agent-0.2.0/videos/technology_dreams.mp3 +0 -0
@@ -0,0 +1,25 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: "/"
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ time: "06:00"
9
+ timezone: "Europe/London"
10
+ open-pull-requests-limit: 5
11
+ commit-message:
12
+ prefix: "deps"
13
+ include: scope
14
+
15
+ - package-ecosystem: github-actions
16
+ directory: "/"
17
+ schedule:
18
+ interval: weekly
19
+ day: monday
20
+ time: "06:00"
21
+ timezone: "Europe/London"
22
+ open-pull-requests-limit: 3
23
+ commit-message:
24
+ prefix: "ci"
25
+ include: scope
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ lint:
14
+ name: Lint (ruff)
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
18
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
19
+ with:
20
+ python-version: "3.12"
21
+ - name: Install ruff
22
+ run: pip install "ruff==0.15.13"
23
+ - name: Ruff check
24
+ run: ruff check .
25
+ - name: Ruff format check
26
+ run: ruff format --check .
27
+
28
+ unit-tests:
29
+ name: Unit tests (Python ${{ matrix.python-version }})
30
+ runs-on: ubuntu-latest
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ python-version: ["3.11", "3.12", "3.13"]
35
+ steps:
36
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
37
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
38
+ with:
39
+ python-version: ${{ matrix.python-version }}
40
+ cache: pip
41
+ - name: Install package with dev extras
42
+ run: pip install -e ".[dev]"
43
+ - name: Run tests (skip integration and slow markers)
44
+ run: pytest tests -m "not integration and not slow"
@@ -0,0 +1,38 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: "30 5 * * 1"
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ analyze:
16
+ name: Analyze (Python)
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ actions: read
20
+ contents: read
21
+ security-events: write
22
+
23
+ steps:
24
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
25
+
26
+ - name: Initialize CodeQL
27
+ uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
28
+ with:
29
+ languages: python
30
+ queries: security-and-quality
31
+
32
+ - name: Autobuild
33
+ uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
34
+
35
+ - name: Perform CodeQL Analysis
36
+ uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
37
+ with:
38
+ category: "/language:python"
@@ -0,0 +1,29 @@
1
+ name: Dependabot auto-merge
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ auto-merge:
12
+ if: github.actor == 'dependabot[bot]'
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ contents: write
16
+ pull-requests: write
17
+ steps:
18
+ - name: Read PR metadata
19
+ id: meta
20
+ uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2
21
+ with:
22
+ github-token: ${{ secrets.GITHUB_TOKEN }}
23
+
24
+ - name: Enable auto-merge for patch + minor
25
+ if: steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor'
26
+ run: gh pr merge --auto --squash "$PR_URL"
27
+ env:
28
+ PR_URL: ${{ github.event.pull_request.html_url }}
29
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,33 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment:
14
+ name: pypi
15
+ url: https://pypi.org/p/pytest-resilience-agent
16
+ permissions:
17
+ id-token: write
18
+ contents: read
19
+ steps:
20
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
24
+ with:
25
+ python-version: "3.12"
26
+
27
+ - name: Build sdist and wheel
28
+ run: |
29
+ python -m pip install --upgrade pip build
30
+ python -m build
31
+
32
+ - name: Publish to PyPI
33
+ uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
@@ -0,0 +1,42 @@
1
+ name: Scorecard supply-chain security
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ schedule:
7
+ - cron: "30 1 * * 6"
8
+
9
+ permissions: read-all
10
+
11
+ jobs:
12
+ analysis:
13
+ name: Scorecard analysis
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ security-events: write
17
+ id-token: write
18
+
19
+ steps:
20
+ - name: "Checkout code"
21
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22
+ with:
23
+ persist-credentials: false
24
+
25
+ - name: "Run analysis"
26
+ uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
27
+ with:
28
+ results_file: results.sarif
29
+ results_format: sarif
30
+ publish_results: true
31
+
32
+ - name: "Upload artifact"
33
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
34
+ with:
35
+ name: SARIF file
36
+ path: results.sarif
37
+ retention-days: 5
38
+
39
+ - name: "Upload to code-scanning"
40
+ uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
41
+ with:
42
+ sarif_file: results.sarif
@@ -0,0 +1,40 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ .Python
8
+ build/
9
+ dist/
10
+ *.egg-info/
11
+ *.egg
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ env/
17
+
18
+ # Editor
19
+ .vscode/
20
+ .idea/
21
+ *.swp
22
+ .DS_Store
23
+
24
+ # pytest
25
+ .pytest_cache/
26
+ .coverage
27
+ htmlcov/
28
+
29
+ # Local env files
30
+ .env
31
+ .env.local
32
+
33
+ # Build artefacts
34
+ *.whl
35
+ *.tar.gz
36
+ .secrets/
37
+
38
+ # Slide regeneration artefacts
39
+ videos/slides/*.orig.*
40
+ videos/*.orig.*
@@ -0,0 +1,17 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.7.4
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/pre-commit/pre-commit-hooks
10
+ rev: v5.0.0
11
+ hooks:
12
+ - id: end-of-file-fixer
13
+ - id: trailing-whitespace
14
+ - id: check-yaml
15
+ - id: check-toml
16
+ - id: check-merge-conflict
17
+ - id: check-added-large-files
@@ -0,0 +1,113 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Planned
11
+ - Semantic assertion hooks (composability with eval frameworks)
12
+ - LLM-driven scenario classifier behind the existing `pick_scenarios` interface
13
+ - Scenario composition primitives (e.g. `rate_limit` then `partial_outage`)
14
+
15
+ ## [0.2.0] - 2026-06-11
16
+
17
+ ### Added
18
+ - Multi-turn conversation chaos. The `resilience` marker now accepts
19
+ `turns=[[...], [...], ...]`, one scenario set per conversation turn, advanced
20
+ with `chaos.next_turn()`. Each turn is an independent chaos window: advancing
21
+ reverts the current turn's scenarios and applies the next turn's with fresh
22
+ call counters, so chaos can appear and clear mid-conversation (turn 1 clean,
23
+ turn 2 brownout, turn 3 recovered). `turns=` and `scenarios=` are mutually
24
+ exclusive; combining them, or advancing past the last turn, raises a clear
25
+ usage error. Each turn boundary emits a `chaos.turn.N` OpenTelemetry span.
26
+ - New `malformed_json` chaos scenario: the gateway returns HTTP 200 with an HTML error body instead of JSON, mirroring a proxy or CDN that swallows the upstream failure and serves its own page. Agent code that calls `response.json()` without guarding against decode errors surfaces this as an unhandled exception rather than a graceful fallback. Brings the built-in scenario count to ten.
27
+
28
+ ## [0.1.0] - 2026-05-27
29
+
30
+ Initial release. Built for the DevNetwork [AI + ML] Hackathon 2026, Lark and
31
+ TrueFoundry sponsor tracks.
32
+
33
+ ### Added
34
+
35
+ **Plugin**
36
+ - `pytest_resilience_agent.plugin` registers the `resilience` pytest marker
37
+ with strict-marker support
38
+ - `ai_gateway` fixture: returns an `AIGatewayClient` configured from
39
+ `--resilience-gateway-url` CLI option or `TFY_GATEWAY_URL` env var
40
+ - `chaos` fixture: applies named scenarios from the marker for the test
41
+ duration, with automatic cleanup
42
+ - `--resilience-record PATH` option writes a JSON timeline of every chaos
43
+ event to disk at session finish
44
+ - `pytest_runtest_logreport` hook attaches a "chaos events" section to each
45
+ test report so judges and engineers see what was injected next to the
46
+ test outcome
47
+
48
+ **Chaos scenarios**
49
+ - `llm_timeout`: gateway sleeps past the request timeout
50
+ - `llm_5xx`: gateway returns 502 for the first N calls, then succeeds
51
+ - `rate_limit`: gateway returns 429 with `Retry-After`
52
+ - `mcp_error`: Lark MCP server raises a JSON-RPC error envelope
53
+ - `partial_outage`: first call 503, retry succeeds
54
+ - `cost_exceeded`: gateway returns 402 quota_exceeded
55
+ - `wrong_model_returned`: gateway silently routes to an unintended model
56
+ - `stream_stall`: gateway returns 200 with empty content (stream drop)
57
+ - `network_blip`: ConnectError on first N calls, recovery after
58
+
59
+ **Generator**
60
+ - `pytest_resilience_agent.generator.generate_test` writes a runnable
61
+ pytest file for any failure text, with chaos scenarios picked from a
62
+ deterministic regex rule set (ADR 0003)
63
+ - 9 regex rules cover the common failure-text patterns: 429, 502, 503,
64
+ 504, 402, connection errors, empty/stream, MCP, model mismatch
65
+
66
+ **CLI**
67
+ - `pytest-resilience-agent` console script with five subcommands:
68
+ - `scenarios` lists the registered chaos scenarios
69
+ - `discover` lists failing tests via Lark MCP
70
+ - `generate` synthesises resilience tests from Lark failures
71
+ - `run` executes the generated tests through pytest
72
+ - `report` pushes resolution status back to Lark
73
+
74
+ **Demo entry points**
75
+ - `demo/run_demo.py`: drives the sample FastAPI agent through every chaos
76
+ scenario with a Rich-table summary
77
+ - `demo/run_full_loop.py`: full end-to-end loop - spins up mock Lark
78
+ server in a background thread, pulls failures, generates resilience
79
+ tests, runs pytest, reports resolutions back to Lark
80
+ - `demo/mock_truefoundry.py`: in-process mock of the TrueFoundry AI
81
+ Gateway with a Gemini → Claude → local fallback chain
82
+ - `demo/mock_lark.py`: in-process mock of the Lark MCP server with seeded
83
+ failing-test data
84
+ - `demo/sample_agent/app.py`: sample FastAPI agent that summarises
85
+ customer emails through the gateway with retry logic
86
+
87
+ **Tests and quality**
88
+ - 16 passing tests covering plugin registration, fixture wiring, every
89
+ chaos scenario, the timeline export, and the report hook
90
+ - 5 example end-user tests in `demo/example_agent_tests/` showing the
91
+ four patterns we expect adopters to copy
92
+ - pre-commit configuration (ruff format + lint, trailing-whitespace,
93
+ YAML/TOML/large-file checks)
94
+ - 3 ADRs in `docs/adr/`:
95
+ - 0001: resilience-first direction, not eval-first
96
+ - 0002: respx as the chaos injection layer
97
+ - 0003: rule-based scenario picker for v0.1, LLM-driven option for v0.2
98
+
99
+ **Observability**
100
+ - OpenTelemetry spans on every chaos scenario apply / revert, ready for
101
+ OTLP export to Cloud Trace or any compatible backend
102
+
103
+ ### Known limitations
104
+
105
+ - Semantic-level paraphrased failures are out of scope (use phoenix2pytest
106
+ or DeepEval for those)
107
+ - Multi-turn conversation chaos is on the v0.2 roadmap
108
+ - Distributed-system chaos (network partitions across services) is out of
109
+ scope for v0.1; the HTTP-layer approach covers gateway, model, MCP, and
110
+ rate limiter in one mechanism
111
+
112
+ [Unreleased]: https://github.com/golikovichev/pytest-resilience-agent/compare/v0.1.0...HEAD
113
+ [0.1.0]: https://github.com/golikovichev/pytest-resilience-agent/releases/tag/v0.1.0
@@ -0,0 +1,40 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in pytest-resilience-agent. This is a small alpha project shipped during the DevNetwork AI+ML Hackathon, so the contribution flow is light.
4
+
5
+ ## Reporting a bug
6
+
7
+ Open an issue with:
8
+
9
+ - What you ran (pytest invocation, marker selection, Python version)
10
+ - What you expected the resilience scenario to do
11
+ - What happened instead (timeline output on failure helps)
12
+ - A minimal scenario or fixture snippet that reproduces it (strip any real API keys or gateway tokens first)
13
+
14
+ ## Suggesting a feature
15
+
16
+ Open an issue first so we can talk through the use case before you write code. The project scope is intentionally narrow: pytest-driven resilience testing for LLM applications via Lark MCP plus TrueFoundry AI Gateway, with rule-based assertions and explicit chaos markers. Feature requests that pull it elsewhere will get a polite redirect.
17
+
18
+ ## Submitting a pull request
19
+
20
+ 1. Fork the repo and create a branch from `main`.
21
+ 2. Make your changes. Keep the diff focused on one thing.
22
+ 3. Add or update tests in `tests/`. The CI runs `pytest -v` on Python 3.11, 3.12, and 3.13.
23
+ 4. Run the tests locally before pushing:
24
+ ```bash
25
+ pip install -e ".[dev]"
26
+ pytest tests -m "not integration"
27
+ ```
28
+ 5. New scenarios, fixtures, or markers need at least one happy-path test and one chaos-injection test that exercises the timeline output.
29
+ 6. Open the PR with a short description of what changed and why.
30
+
31
+ ## Code style
32
+
33
+ - Python 3.11+. Type hints on public functions.
34
+ - Function and variable names in English, snake_case (e.g., `inject_timeout`, `assert_recovery`).
35
+ - One responsibility per function. If a function grows past 30-40 lines, split it.
36
+ - Ruff handles lint and formatting. Run `ruff check . && ruff format .` before opening a PR.
37
+
38
+ ## Security
39
+
40
+ If you find something that could leak gateway tokens, MCP server credentials, or PII from a real LLM transcript, please report privately. See `SECURITY.md` for the disclosure channel.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mikhail Golikov
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.