azure-vm-sdk 0.6.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 (48) hide show
  1. azure_vm_sdk-0.6.0/.editorconfig +23 -0
  2. azure_vm_sdk-0.6.0/.gitattributes +33 -0
  3. azure_vm_sdk-0.6.0/.github/workflows/ci.yml +97 -0
  4. azure_vm_sdk-0.6.0/.github/workflows/release.yml +66 -0
  5. azure_vm_sdk-0.6.0/.gitignore +22 -0
  6. azure_vm_sdk-0.6.0/.importlinter +33 -0
  7. azure_vm_sdk-0.6.0/.pre-commit-config.yaml +87 -0
  8. azure_vm_sdk-0.6.0/.python-version +1 -0
  9. azure_vm_sdk-0.6.0/CLAUDE.md +109 -0
  10. azure_vm_sdk-0.6.0/LICENSE +21 -0
  11. azure_vm_sdk-0.6.0/PKG-INFO +265 -0
  12. azure_vm_sdk-0.6.0/README.md +240 -0
  13. azure_vm_sdk-0.6.0/docs/superpowers/plans/2026-05-13-azure-vm-sdk-plan.md +2277 -0
  14. azure_vm_sdk-0.6.0/docs/superpowers/plans/2026-06-12-nsg-open-ports.md +157 -0
  15. azure_vm_sdk-0.6.0/docs/superpowers/specs/2026-05-13-azure-vm-sdk-design.md +322 -0
  16. azure_vm_sdk-0.6.0/pyproject.toml +185 -0
  17. azure_vm_sdk-0.6.0/src/azure_vm/__init__.py +32 -0
  18. azure_vm_sdk-0.6.0/src/azure_vm/_backend.py +131 -0
  19. azure_vm_sdk-0.6.0/src/azure_vm/_discovery.py +50 -0
  20. azure_vm_sdk-0.6.0/src/azure_vm/_templates.py +238 -0
  21. azure_vm_sdk-0.6.0/src/azure_vm/_workspace.py +175 -0
  22. azure_vm_sdk-0.6.0/src/azure_vm/client.py +302 -0
  23. azure_vm_sdk-0.6.0/src/azure_vm/devtools/__init__.py +6 -0
  24. azure_vm_sdk-0.6.0/src/azure_vm/devtools/code_eval.py +665 -0
  25. azure_vm_sdk-0.6.0/src/azure_vm/devtools/package_report.py +236 -0
  26. azure_vm_sdk-0.6.0/src/azure_vm/devtools/quality.py +36 -0
  27. azure_vm_sdk-0.6.0/src/azure_vm/e2e.py +250 -0
  28. azure_vm_sdk-0.6.0/src/azure_vm/exceptions.py +62 -0
  29. azure_vm_sdk-0.6.0/src/azure_vm/models.py +169 -0
  30. azure_vm_sdk-0.6.0/src/azure_vm/testing.py +5 -0
  31. azure_vm_sdk-0.6.0/src/azure_vm/vm.py +286 -0
  32. azure_vm_sdk-0.6.0/tests/__init__.py +0 -0
  33. azure_vm_sdk-0.6.0/tests/conftest.py +9 -0
  34. azure_vm_sdk-0.6.0/tests/integration/__init__.py +0 -0
  35. azure_vm_sdk-0.6.0/tests/integration/test_integration.py +107 -0
  36. azure_vm_sdk-0.6.0/tests/unit/__init__.py +0 -0
  37. azure_vm_sdk-0.6.0/tests/unit/devtools/__init__.py +0 -0
  38. azure_vm_sdk-0.6.0/tests/unit/devtools/test_code_eval.py +917 -0
  39. azure_vm_sdk-0.6.0/tests/unit/devtools/test_package_report.py +328 -0
  40. azure_vm_sdk-0.6.0/tests/unit/devtools/test_quality.py +102 -0
  41. azure_vm_sdk-0.6.0/tests/unit/test_backend.py +146 -0
  42. azure_vm_sdk-0.6.0/tests/unit/test_client.py +929 -0
  43. azure_vm_sdk-0.6.0/tests/unit/test_exceptions.py +48 -0
  44. azure_vm_sdk-0.6.0/tests/unit/test_models.py +76 -0
  45. azure_vm_sdk-0.6.0/tests/unit/test_open_ports.py +72 -0
  46. azure_vm_sdk-0.6.0/tests/unit/test_vm.py +565 -0
  47. azure_vm_sdk-0.6.0/tests/unit/test_workspace_refresh.py +95 -0
  48. azure_vm_sdk-0.6.0/uv.lock +1004 -0
@@ -0,0 +1,23 @@
1
+ # https://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ charset = utf-8
6
+ end_of_line = lf
7
+ insert_final_newline = true
8
+ trim_trailing_whitespace = true
9
+ indent_style = space
10
+ indent_size = 4
11
+
12
+ [*.{yml,yaml}]
13
+ indent_size = 2
14
+
15
+ [*.{toml,json,ini,cfg}]
16
+ indent_size = 2
17
+
18
+ [*.md]
19
+ # Trailing whitespace is meaningful in Markdown (hard line breaks).
20
+ trim_trailing_whitespace = false
21
+
22
+ [Makefile]
23
+ indent_style = tab
@@ -0,0 +1,33 @@
1
+ # Normalize line endings. This SDK is consumed from wherever the caller happens
2
+ # to be, so a CRLF leaked in from a Windows editor breaks things in ways that
3
+ # are hard to spot in review.
4
+ * text=auto eol=lf
5
+
6
+ *.py text diff=python
7
+ *.toml text
8
+ *.yml text
9
+ *.yaml text
10
+ *.json text
11
+ *.md text diff=markdown
12
+ *.txt text
13
+ *.ini text
14
+ *.cfg text
15
+
16
+ # Binary assets: never normalize, never try to diff.
17
+ *.png binary
18
+ *.jpg binary
19
+ *.jpeg binary
20
+ *.gif binary
21
+ *.ico binary
22
+ *.whl binary
23
+ *.gz binary
24
+ *.zip binary
25
+
26
+ # Generated at runtime.
27
+ .coverage linguist-generated=true
28
+ coverage.xml linguist-generated=true
29
+ # `linguist-generated` keeps this out of the language stats and collapses it in
30
+ # the GitHub diff view, which is right for a generated file. `-diff` is
31
+ # deliberately NOT set: a lockfile pins exact versions and hashes, so a change
32
+ # to it is exactly the kind of thing review should be able to see.
33
+ uv.lock linguist-generated=true
@@ -0,0 +1,97 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ # ---------------------------------------------------------------------------
18
+ # Lint, format, types, security.
19
+ #
20
+ # Runs the same hooks as local development through pre-commit, so the two can
21
+ # not drift. This is the first CI this repository has had: it previously had
22
+ # no .github/workflows at all, so nothing was checked on push.
23
+ # ---------------------------------------------------------------------------
24
+ pre-commit:
25
+ name: pre-commit (ruff, basedpyright, bandit, import-linter)
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ # ruff's target-version is py311 and basedpyright is pointed at the
33
+ # 3.11 venv, so linting on the declared floor is the honest choice.
34
+ python-version: "3.11"
35
+
36
+ - uses: astral-sh/setup-uv@v5
37
+ with:
38
+ enable-cache: true
39
+ cache-dependency-glob: uv.lock
40
+
41
+ - name: Install the project
42
+ run: uv sync --frozen
43
+
44
+ - name: Install pre-commit
45
+ run: python -m pip install --upgrade pip pre-commit
46
+
47
+ - name: Cache pre-commit environments
48
+ uses: actions/cache@v4
49
+ with:
50
+ path: ~/.cache/pre-commit
51
+ key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
52
+
53
+ - name: Run pre-commit
54
+ run: pre-commit run --all-files --show-diff-on-failure --color=always
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Tests + coverage gate.
58
+ #
59
+ # pyproject.toml sets `addopts = "-m 'not integration'"`, so the tests that
60
+ # need real Azure credentials and OpenTofu stay out of this job. The coverage
61
+ # threshold lives in [tool.coverage.report] and is deliberately not repeated
62
+ # as a --cov-fail-under flag here: stating it in two places is how the two
63
+ # come to disagree, with the command-line flag silently winning.
64
+ # ---------------------------------------------------------------------------
65
+ test:
66
+ name: pytest (py${{ matrix.python-version }})
67
+ runs-on: ubuntu-latest
68
+ strategy:
69
+ fail-fast: false
70
+ matrix:
71
+ # 3.11 is the declared floor (requires-python >=3.11).
72
+ python-version: ["3.11", "3.12", "3.13"]
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+
76
+ - uses: actions/setup-python@v5
77
+ with:
78
+ python-version: ${{ matrix.python-version }}
79
+
80
+ - uses: astral-sh/setup-uv@v5
81
+ with:
82
+ enable-cache: true
83
+ cache-dependency-glob: uv.lock
84
+
85
+ - name: Install project
86
+ run: uv sync --frozen
87
+
88
+ - name: Run tests with coverage
89
+ run: uv run --frozen pytest --cov-report=xml
90
+
91
+ - name: Upload coverage artifact
92
+ if: always()
93
+ uses: actions/upload-artifact@v4
94
+ with:
95
+ name: coverage-${{ matrix.python-version }}
96
+ path: coverage.xml
97
+ if-no-files-found: ignore
@@ -0,0 +1,66 @@
1
+ name: Release
2
+
3
+ # A PyPI version can be yanked but never reused, so this only runs on a tag, and
4
+ # the tag is checked against the version the build actually produced before
5
+ # anything is uploaded.
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ publish:
15
+ name: Build and publish azure-vm-sdk
16
+ runs-on: ubuntu-latest
17
+ # Must match the environment registered as this project's trusted publisher
18
+ # on PyPI, along with owner, repository and this filename.
19
+ environment: pypi
20
+ permissions:
21
+ # The whole credential: PyPI trusts a short-lived OIDC token minted here,
22
+ # so no API token is stored in the repository or in a secret.
23
+ id-token: write
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.11"
30
+
31
+ - uses: astral-sh/setup-uv@v5
32
+ with:
33
+ enable-cache: true
34
+ cache-dependency-glob: uv.lock
35
+
36
+ - name: Build azure-vm-sdk
37
+ run: uv build --out-dir dist
38
+
39
+ # The check that matters: uploading 0.7.0 under the tag v0.6.0 would burn
40
+ # the 0.7.0 version number permanently.
41
+ - name: Check the tag matches the built version
42
+ run: |
43
+ tag="${GITHUB_REF_NAME#v}"
44
+ # Globs every wheel rather than "<name>-*.whl". The wheel is named after
45
+ # the distribution, not the module, and for azure-vm-sdk the two differ
46
+ # (`azure_vm_sdk` against `azure_vm`), so a module-based glob matched
47
+ # nothing and failed the build. One package per repository, so one wheel.
48
+ wheels=(dist/*.whl)
49
+ if [ "${#wheels[@]}" -ne 1 ]; then
50
+ echo "expected exactly one wheel in dist/, found ${#wheels[@]}" >&2
51
+ ls -la dist/ >&2
52
+ exit 1
53
+ fi
54
+ artifact="$(basename "${wheels[0]}")"
55
+ case "$artifact" in
56
+ *"-$tag-"*) echo "tag $GITHUB_REF_NAME matches $artifact" ;;
57
+ *)
58
+ echo "tag $GITHUB_REF_NAME does not match the built $artifact" >&2
59
+ exit 1
60
+ ;;
61
+ esac
62
+
63
+ - name: Publish to PyPI
64
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
65
+ with:
66
+ packages-dir: dist/
@@ -0,0 +1,22 @@
1
+ __pycache__/
2
+ .pytest_cache/
3
+ .venv/
4
+ *.pyc
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ *.svg
9
+ .pydeps/
10
+ .basedpyright/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+ .grimp_cache/
14
+
15
+ # Coverage reports. `uv.lock` is deliberately NOT ignored any more: this
16
+ # repository tracks it so CI can run `uv sync --frozen`, like the sibling
17
+ # projects. Leaving it ignored meant a clean checkout had no lockfile at all and
18
+ # `--frozen` could not be used.
19
+ .coverage
20
+ .coverage.*
21
+ coverage.xml
22
+ htmlcov/
@@ -0,0 +1,33 @@
1
+ [importlinter]
2
+ root_package = azure_vm
3
+
4
+ [importlinter:contract:models_and_exceptions_are_independent]
5
+ name = models and exceptions must not depend on other internal packages
6
+ type = forbidden
7
+ source_modules =
8
+ azure_vm.models
9
+ azure_vm.exceptions
10
+ forbidden_modules =
11
+ azure_vm._backend
12
+ azure_vm.vm
13
+ azure_vm.client
14
+
15
+ [importlinter:contract:backend_is_independent]
16
+ name = _backend must not depend on higher-level packages
17
+ type = forbidden
18
+ source_modules =
19
+ azure_vm._backend
20
+ forbidden_modules =
21
+ azure_vm.vm
22
+ azure_vm.client
23
+
24
+ [importlinter:contract:internal_modules_are_independent]
25
+ name = internal modules must not depend on public API layer
26
+ type = forbidden
27
+ source_modules =
28
+ azure_vm._templates
29
+ azure_vm._workspace
30
+ azure_vm._discovery
31
+ forbidden_modules =
32
+ azure_vm.client
33
+ azure_vm.vm
@@ -0,0 +1,87 @@
1
+ # Pre-commit hooks for azure-vm-sdk.
2
+ #
3
+ # uv run pre-commit install # enable on commit
4
+ # uv run pre-commit run --all-files # run everything once
5
+ #
6
+ # Version policy: the ruff revision below MUST stay in sync with the `ruff`
7
+ # pin in pyproject.toml. A newer ruff in pre-commit than in the dev environment
8
+ # makes the hook report violations that `uv run ruff check` does not.
9
+ #
10
+ # No default_language_version is set on purpose: pinning it to a specific
11
+ # python3.x makes pre-commit fail outright on a machine that does not have that
12
+ # interpreter on PATH, and the one hook that needs the project's interpreter
13
+ # (basedpyright) runs through `uv run --frozen` against the project venv anyway.
14
+
15
+ fail_fast: false
16
+
17
+ repos:
18
+ # ---------------------------------------------------------------------------
19
+ # Ruff - lint + format
20
+ # ---------------------------------------------------------------------------
21
+ - repo: https://github.com/astral-sh/ruff-pre-commit
22
+ rev: v0.16.6
23
+ hooks:
24
+ - id: ruff
25
+ name: ruff (lint)
26
+ args: [--fix, --exit-non-zero-on-fix]
27
+ - id: ruff-format
28
+ name: ruff (format)
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Type checking
32
+ #
33
+ # Runs through the project environment rather than a pre-commit mirror:
34
+ # basedpyright has to resolve `azure_vm` itself, and [tool.basedpyright]
35
+ # carries the include list and venv settings that make the pass meaningful.
36
+ # ---------------------------------------------------------------------------
37
+ - repo: local
38
+ hooks:
39
+ - id: basedpyright
40
+ name: basedpyright
41
+ entry: uv run --frozen basedpyright
42
+ language: system
43
+ pass_filenames: false
44
+ types: [python]
45
+
46
+ # The architectural contracts CI checks, run locally too.
47
+ - id: import-linter
48
+ name: import-linter
49
+ entry: uv run --frozen lint-imports --config .importlinter --no-cache
50
+ language: system
51
+ pass_filenames: false
52
+ files: ^src/
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Security
56
+ #
57
+ # This SDK drives an Azure VM over SSH and through OpenTofu, so subprocess
58
+ # and paramiko are its subject matter rather than a smell. B602 (shell=True)
59
+ # is deliberately not skipped anywhere: no call here uses it, and that must
60
+ # stay true.
61
+ # ---------------------------------------------------------------------------
62
+ - repo: https://github.com/PyCQA/bandit
63
+ rev: "1.9.4"
64
+ hooks:
65
+ - id: bandit
66
+ args: ["-c", "pyproject.toml"]
67
+ additional_dependencies: ["bandit[toml]"]
68
+ # pre-commit passes explicit filenames, which bypasses bandit's own
69
+ # exclude_dirs - so the test tree has to be excluded here as well.
70
+ exclude: ^tests/
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Generic file hygiene
74
+ # ---------------------------------------------------------------------------
75
+ - repo: https://github.com/pre-commit/pre-commit-hooks
76
+ rev: v6.0.0
77
+ hooks:
78
+ - id: check-toml
79
+ - id: check-yaml
80
+ - id: check-merge-conflict
81
+ - id: check-added-large-files
82
+ args: [--maxkb=1024]
83
+ - id: end-of-file-fixer
84
+ - id: trailing-whitespace
85
+ exclude: \.md$
86
+ - id: mixed-line-ending
87
+ args: [--fix=lf]
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,109 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Overview
6
+
7
+ A Pythonic SDK for managing Azure VMs through OpenTofu: a higher-level API over
8
+ a rendered OpenTofu workspace plus SSH access to the resulting machines. It is
9
+ consumed by `nanolab` through a git pin, so the public API in `azure_vm.__all__`
10
+ is a contract with that project.
11
+
12
+ ## Setup
13
+
14
+ ```bash
15
+ uv sync
16
+ ```
17
+
18
+ Requires [uv](https://docs.astral.sh/uv/). Azure credentials and OpenTofu are
19
+ NOT required for the unit tests.
20
+
21
+ The toolchain (ruff, basedpyright, bandit, import-linter, pytest, pre-commit)
22
+ lives in `[dependency-groups].dev`, which a plain `uv sync` installs. It must
23
+ stay there and not move to `[project.optional-dependencies]`: extras are skipped
24
+ by `uv run`, so the basedpyright pre-commit hook — which runs `uv run --frozen
25
+ basedpyright` — would fail in CI with "Failed to spawn: basedpyright".
26
+
27
+ ## Commands
28
+
29
+ ```bash
30
+ # Tests (the integration marker is deselected by addopts)
31
+ uv run pytest
32
+ uv run pytest tests/unit/test_vm.py::test_name -v
33
+
34
+ # Integration tests - need real Azure credentials and OpenTofu
35
+ uv run pytest -m integration -v
36
+
37
+ # Lint, format, types, security, contracts - the same hooks CI runs
38
+ uv run ruff check .
39
+ uv run ruff format .
40
+ uv run basedpyright
41
+ uv run bandit -c pyproject.toml -r src
42
+ uv run lint-imports --config .importlinter --no-cache
43
+ uv run pre-commit run --all-files
44
+ ```
45
+
46
+ `azure-vm-quality` runs ruff, basedpyright, bandit and import-linter together
47
+ and exits non-zero if any of them fails.
48
+
49
+ ## Architecture
50
+
51
+ `src/azure_vm/` contains the package:
52
+
53
+ - `_backend.py` — the `TofuBackend` protocol and the subprocess implementation
54
+ that shells out to `tofu`. All process execution goes through a backend, which
55
+ is what makes the SDK testable without Azure.
56
+ - `_discovery.py` — locating the OpenTofu binary and the workspace root.
57
+ - `_templates.py` — rendering the `.tf` files for a VM.
58
+ - `_workspace.py` — the on-disk OpenTofu workspace: rendering, `init`, `apply`,
59
+ output parsing.
60
+ - `models.py` — dataclasses (`VmConfig`, `VmInfo`, `VmSize`, `ImageInfo`,
61
+ `VmState`, `VmArchitecture`, `CommandResult`, …).
62
+ - `exceptions.py` — typed hierarchy rooted at `AzureVmError`, plus
63
+ `SshConnectionError` and the rest, so callers catch a specific failure rather
64
+ than an opaque one.
65
+ - `vm.py` — `AzureVM`: the per-VM handle (info, start/stop/restart, delete,
66
+ clone, `exec`, `exec_structured`, `transfer`, `wait_for_ip`, `wait_ready`).
67
+ SSH goes through paramiko and commands are shell-quoted with `shlex.join`.
68
+ - `client.py` — `AzureClient`: cluster-level operations (launch, launch_many,
69
+ get_vm, ensure_running, list, find, list_sizes, purge).
70
+ - `testing.py` — `FakeTofuBackend` for consumers writing their own tests.
71
+ - `e2e.py` — the `azure-vm-e2e` console script: an end-to-end lifecycle
72
+ harness. Not imported by the library, and omitted from coverage.
73
+ - `devtools/` — repository tooling exposed as console scripts:
74
+ `azure-vm-quality`, `azure-vm-package-report` (import graph metrics via
75
+ `grimp`), `azure-vm-eval` (AST and coupling checks).
76
+
77
+ The architectural contracts live in `.importlinter`: `models` and `exceptions`
78
+ must not depend on the rest of the package, and the layers must not be imported
79
+ in the wrong direction. CI runs them, and so does pre-commit.
80
+
81
+ ## Callers must not be broken
82
+
83
+ `nanolab` consumes this package through a git pin, so a change to a name in
84
+ `__all__`, to a signature, or to a returned model's attributes is a breaking
85
+ change for it even though nothing in this repository fails. Check that project
86
+ before renaming or removing a public symbol.
87
+
88
+ ## Tooling
89
+
90
+ The same stack as the sibling projects.
91
+
92
+ | Concern | Tool | Config |
93
+ | --- | --- | --- |
94
+ | Lint + format | ruff (88 cols) | `[tool.ruff]` |
95
+ | Types | basedpyright (standard) | `[tool.basedpyright]` |
96
+ | Security | bandit | `[tool.bandit]` |
97
+ | Import layers | import-linter | `.importlinter` |
98
+ | Coverage | pytest-cov | `[tool.coverage.report]`, `fail_under = 80` |
99
+
100
+ Notes that are easy to get wrong:
101
+
102
+ - `SLF` (flake8-self) is part of the rule set here, as it was before the
103
+ migration; the per-file-ignores record where private access is deliberate.
104
+ - This package supports Python 3.11, so `reportImplicitOverride` is off
105
+ (`typing.override` needs 3.12).
106
+ - `uv.lock` is tracked, so CI can run `uv sync --frozen`. It used to be
107
+ gitignored, which meant a clean checkout had no lockfile at all.
108
+ - `src/azure_vm/e2e.py` is omitted from coverage: it only runs against real
109
+ Azure.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 miciav
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.