nfse-br 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.
Files changed (76) hide show
  1. nfse_br-0.1.0/.gitattributes +1 -0
  2. nfse_br-0.1.0/.github/workflows/ci.yml +133 -0
  3. nfse_br-0.1.0/.github/workflows/release.yml +184 -0
  4. nfse_br-0.1.0/.gitignore +41 -0
  5. nfse_br-0.1.0/.python-version +1 -0
  6. nfse_br-0.1.0/CHANGELOG.md +47 -0
  7. nfse_br-0.1.0/CONTRIBUTING.md +37 -0
  8. nfse_br-0.1.0/LICENSE +21 -0
  9. nfse_br-0.1.0/PKG-INFO +314 -0
  10. nfse_br-0.1.0/README.md +290 -0
  11. nfse_br-0.1.0/SECURITY.md +23 -0
  12. nfse_br-0.1.0/contracts/restricted/DPS_SCHEMA_CONTRACT.md +65 -0
  13. nfse_br-0.1.0/contracts/restricted/DPS_UNSIGNED_BUILDER.md +93 -0
  14. nfse_br-0.1.0/contracts/restricted/DPS_XMLDSIG_PROFILE.md +224 -0
  15. nfse_br-0.1.0/contracts/restricted/DPS_XSD_VALIDATOR.md +89 -0
  16. nfse_br-0.1.0/contracts/restricted/README.md +41 -0
  17. nfse_br-0.1.0/contracts/restricted/dps-schema-contract.json +4232 -0
  18. nfse_br-0.1.0/contracts/restricted/dps-xmldsig-profile.json +121 -0
  19. nfse_br-0.1.0/contracts/restricted/manifest.json +42 -0
  20. nfse_br-0.1.0/docs/RELEASE.md +94 -0
  21. nfse_br-0.1.0/examples/__init__.py +1 -0
  22. nfse_br-0.1.0/examples/build_unsigned_dps.py +77 -0
  23. nfse_br-0.1.0/pyproject.toml +81 -0
  24. nfse_br-0.1.0/scripts/__init__.py +1 -0
  25. nfse_br-0.1.0/scripts/check_coverage.py +466 -0
  26. nfse_br-0.1.0/scripts/f0_freeze_dps_schema_contract.py +55 -0
  27. nfse_br-0.1.0/scripts/f0_freeze_restricted.py +50 -0
  28. nfse_br-0.1.0/scripts/smoke_base_wheel.py +297 -0
  29. nfse_br-0.1.0/scripts/validate_generated_dps.py +274 -0
  30. nfse_br-0.1.0/scripts/validate_official_dps_xsd.py +188 -0
  31. nfse_br-0.1.0/src/nfse_br/__init__.py +7 -0
  32. nfse_br-0.1.0/src/nfse_br/__main__.py +5 -0
  33. nfse_br-0.1.0/src/nfse_br/_f0/__init__.py +3 -0
  34. nfse_br-0.1.0/src/nfse_br/_f0/dps_schema_contract.py +1050 -0
  35. nfse_br-0.1.0/src/nfse_br/_f0/restricted_contract.py +872 -0
  36. nfse_br-0.1.0/src/nfse_br/_xmlsig/__init__.py +1 -0
  37. nfse_br-0.1.0/src/nfse_br/_xmlsig/preflight.py +180 -0
  38. nfse_br-0.1.0/src/nfse_br/cli.py +234 -0
  39. nfse_br-0.1.0/src/nfse_br/domain/__init__.py +16 -0
  40. nfse_br-0.1.0/src/nfse_br/domain/cnpj.py +58 -0
  41. nfse_br-0.1.0/src/nfse_br/domain/competence.py +52 -0
  42. nfse_br-0.1.0/src/nfse_br/domain/cpf.py +46 -0
  43. nfse_br-0.1.0/src/nfse_br/domain/environment.py +31 -0
  44. nfse_br-0.1.0/src/nfse_br/domain/errors.py +5 -0
  45. nfse_br-0.1.0/src/nfse_br/domain/federal_tax_id.py +70 -0
  46. nfse_br-0.1.0/src/nfse_br/domain/municipality.py +31 -0
  47. nfse_br-0.1.0/src/nfse_br/dps/__init__.py +7 -0
  48. nfse_br-0.1.0/src/nfse_br/dps/builder.py +352 -0
  49. nfse_br-0.1.0/src/nfse_br/dps/identity.py +136 -0
  50. nfse_br-0.1.0/src/nfse_br/dps/number.py +32 -0
  51. nfse_br-0.1.0/src/nfse_br/dps/series.py +37 -0
  52. nfse_br-0.1.0/src/nfse_br/py.typed +1 -0
  53. nfse_br-0.1.0/src/nfse_br/xsd/__init__.py +16 -0
  54. nfse_br-0.1.0/src/nfse_br/xsd/validator.py +356 -0
  55. nfse_br-0.1.0/tests/domain/test_cnpj.py +143 -0
  56. nfse_br-0.1.0/tests/domain/test_competence.py +89 -0
  57. nfse_br-0.1.0/tests/domain/test_cpf.py +119 -0
  58. nfse_br-0.1.0/tests/domain/test_environment.py +41 -0
  59. nfse_br-0.1.0/tests/domain/test_federal_tax_id.py +157 -0
  60. nfse_br-0.1.0/tests/domain/test_municipality.py +63 -0
  61. nfse_br-0.1.0/tests/dps/test_builder.py +890 -0
  62. nfse_br-0.1.0/tests/dps/test_identity.py +180 -0
  63. nfse_br-0.1.0/tests/dps/test_number.py +73 -0
  64. nfse_br-0.1.0/tests/dps/test_series.py +88 -0
  65. nfse_br-0.1.0/tests/examples/test_build_unsigned_dps.py +190 -0
  66. nfse_br-0.1.0/tests/f0/test_dps_schema_contract.py +1229 -0
  67. nfse_br-0.1.0/tests/f0/test_manifest_binding.py +185 -0
  68. nfse_br-0.1.0/tests/f0/test_restricted_contract.py +1015 -0
  69. nfse_br-0.1.0/tests/test_cli.py +685 -0
  70. nfse_br-0.1.0/tests/test_package.py +12 -0
  71. nfse_br-0.1.0/tests/tooling/test_check_coverage.py +534 -0
  72. nfse_br-0.1.0/tests/tooling/test_validate_generated_dps.py +96 -0
  73. nfse_br-0.1.0/tests/xmlsig/test_preflight.py +439 -0
  74. nfse_br-0.1.0/tests/xmlsig/test_profile_evidence.py +96 -0
  75. nfse_br-0.1.0/tests/xsd/test_validator.py +576 -0
  76. nfse_br-0.1.0/uv.lock +423 -0
@@ -0,0 +1 @@
1
+ contracts/restricted/*.json text eol=lf
@@ -0,0 +1,133 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - main
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ quality:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ python-version:
19
+ - "3.12"
20
+ - "3.13"
21
+ env:
22
+ UV_PYTHON: ${{ matrix.python-version }}
23
+ steps:
24
+ - name: Check out repository
25
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+
27
+ - name: Set up Python
28
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+
32
+ - name: Set up uv
33
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
34
+ with:
35
+ version: "0.12.13"
36
+ enable-cache: true
37
+
38
+ - name: Install dependencies
39
+ run: uv sync --frozen --extra xsd
40
+
41
+ - name: Lint
42
+ run: uv run --frozen --extra xsd ruff check .
43
+
44
+ - name: Check formatting
45
+ run: uv run --frozen --extra xsd ruff format --check .
46
+
47
+ - name: Type-check
48
+ run: uv run --frozen --extra xsd mypy src tests scripts examples
49
+
50
+ - name: Test
51
+ run: |
52
+ mkdir -p build/coverage
53
+ uv run --frozen --extra xsd pytest \
54
+ --cov=src/nfse_br \
55
+ --cov-branch \
56
+ --cov-report=term-missing \
57
+ --cov-report=json:build/coverage/coverage.json \
58
+ --cov-fail-under=80
59
+
60
+ - name: Enforce coverage gates
61
+ run: >-
62
+ uv run --frozen --extra xsd python scripts/check_coverage.py
63
+ build/coverage/coverage.json
64
+
65
+ - name: Build distributions
66
+ run: uv build
67
+
68
+ - name: Smoke-test base wheel without XSD dependencies
69
+ run: python scripts/smoke_base_wheel.py dist/nfse_br-0.1.0-py3-none-any.whl
70
+
71
+ windows-compatibility:
72
+ name: windows-compatibility (${{ matrix.python-version }})
73
+ runs-on: windows-2022
74
+ strategy:
75
+ fail-fast: false
76
+ matrix:
77
+ python-version:
78
+ - "3.12"
79
+ - "3.13"
80
+ env:
81
+ UV_PYTHON: ${{ matrix.python-version }}
82
+ steps:
83
+ - name: Check out repository
84
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
85
+
86
+ - name: Set up Python
87
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
88
+ with:
89
+ python-version: ${{ matrix.python-version }}
90
+
91
+ - name: Set up uv
92
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
93
+ with:
94
+ version: "0.12.13"
95
+ enable-cache: true
96
+
97
+ - name: Confirm Windows runner
98
+ run: python -c "import os; assert os.name == 'nt'; print(os.name)"
99
+
100
+ - name: Install dependencies
101
+ run: uv sync --frozen --extra xsd
102
+
103
+ - name: Lint
104
+ run: uv run --frozen --extra xsd ruff check .
105
+
106
+ - name: Check formatting
107
+ run: uv run --frozen --extra xsd ruff format --check .
108
+
109
+ - name: Type-check
110
+ run: uv run --frozen --extra xsd mypy src tests scripts examples
111
+
112
+ - name: Prepare coverage directory
113
+ shell: pwsh
114
+ run: New-Item -ItemType Directory -Force build/coverage | Out-Null
115
+
116
+ - name: Test
117
+ shell: pwsh
118
+ run: |
119
+ uv run --frozen --extra xsd pytest `
120
+ --cov=src/nfse_br `
121
+ --cov-branch `
122
+ --cov-report=term-missing `
123
+ --cov-report=json:build/coverage/coverage.json `
124
+ --cov-fail-under=80
125
+
126
+ - name: Enforce coverage gates
127
+ run: uv run --frozen --extra xsd python scripts/check_coverage.py build/coverage/coverage.json
128
+
129
+ - name: Build distributions
130
+ run: uv build
131
+
132
+ - name: Smoke-test base wheel without XSD dependencies
133
+ run: python scripts/smoke_base_wheel.py dist/nfse_br-0.1.0-py3-none-any.whl
@@ -0,0 +1,184 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ permissions: {}
9
+
10
+ concurrency:
11
+ group: release-${{ github.ref }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ build:
16
+ runs-on: ubuntu-24.04
17
+ permissions:
18
+ contents: read
19
+ steps:
20
+ - name: Check out repository
21
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
+ with:
23
+ fetch-depth: 0
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
27
+ with:
28
+ python-version: "3.12"
29
+
30
+ - name: Set up uv
31
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
32
+ with:
33
+ version: "0.12.13"
34
+ enable-cache: true
35
+
36
+ - name: Validate release identity
37
+ shell: bash
38
+ run: |
39
+ set -euo pipefail
40
+
41
+ test "$GITHUB_REPOSITORY" = "cassao29/nfse-br"
42
+ test "$GITHUB_REF_TYPE" = "tag"
43
+ git fetch origin main --no-tags
44
+ test "$(git rev-parse 'origin/main^{commit}')" = "$GITHUB_SHA"
45
+
46
+ python - <<'PY'
47
+ from __future__ import annotations
48
+
49
+ import ast
50
+ import os
51
+ import re
52
+ import tomllib
53
+ from pathlib import Path
54
+
55
+ tag = os.environ["GITHUB_REF_NAME"]
56
+ match = re.fullmatch(r"v([0-9]+)\.([0-9]+)\.([0-9]+)", tag)
57
+ if match is None:
58
+ raise SystemExit("release tag must match vMAJOR.MINOR.PATCH")
59
+ version = tag[1:]
60
+
61
+ with Path("pyproject.toml").open("rb") as stream:
62
+ project_version = tomllib.load(stream)["project"]["version"]
63
+
64
+ module = ast.parse(
65
+ Path("src/nfse_br/__init__.py").read_text(encoding="utf-8")
66
+ )
67
+ package_versions = []
68
+ for node in module.body:
69
+ if (
70
+ isinstance(node, (ast.Assign, ast.AnnAssign))
71
+ and isinstance(node.value, ast.Constant)
72
+ and isinstance(node.value.value, str)
73
+ ):
74
+ targets = (
75
+ node.targets if isinstance(node, ast.Assign) else [node.target]
76
+ )
77
+ if any(
78
+ isinstance(target, ast.Name)
79
+ and target.id == "__version__"
80
+ for target in targets
81
+ ):
82
+ package_versions.append(node.value.value)
83
+
84
+ if package_versions != [version] or project_version != version:
85
+ raise SystemExit("tag, project version, and package version differ")
86
+
87
+ github_env = Path(os.environ["GITHUB_ENV"])
88
+ with github_env.open("a", encoding="utf-8") as stream:
89
+ stream.write(f"VERSION={version}\n")
90
+ PY
91
+
92
+ - name: Install frozen environment
93
+ run: uv sync --frozen
94
+
95
+ - name: Build distributions
96
+ shell: bash
97
+ run: |
98
+ set -euo pipefail
99
+ uv build
100
+
101
+ # uv 0.12.13 creates this ignored marker in a new output directory.
102
+ test "$(cat dist/.gitignore)" = "*"
103
+ rm -- dist/.gitignore
104
+
105
+ - name: Inspect distributions
106
+ shell: bash
107
+ run: |
108
+ set -euo pipefail
109
+ wheel="dist/nfse_br-${VERSION}-py3-none-any.whl"
110
+ sdist="dist/nfse_br-${VERSION}.tar.gz"
111
+
112
+ test -f "$wheel"
113
+ test ! -L "$wheel"
114
+ test -f "$sdist"
115
+ test ! -L "$sdist"
116
+
117
+ shopt -s nullglob dotglob
118
+ files=(dist/*)
119
+ test "${#files[@]}" -eq 2
120
+ test "${files[0]}" = "$sdist" || test "${files[0]}" = "$wheel"
121
+ test "${files[1]}" = "$sdist" || test "${files[1]}" = "$wheel"
122
+
123
+ sha256sum -- "$wheel" "$sdist"
124
+
125
+ - name: Smoke-test base wheel
126
+ shell: bash
127
+ run: >-
128
+ python scripts/smoke_base_wheel.py
129
+ "dist/nfse_br-${VERSION}-py3-none-any.whl"
130
+
131
+ - name: Upload distributions
132
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
133
+ with:
134
+ name: python-package-distributions
135
+ path: dist/
136
+ if-no-files-found: error
137
+ retention-days: 7
138
+
139
+ publish-pypi:
140
+ needs: build
141
+ runs-on: ubuntu-24.04
142
+ environment: pypi
143
+ permissions:
144
+ actions: read
145
+ id-token: write
146
+ steps:
147
+ - name: Download distributions
148
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
149
+ with:
150
+ name: python-package-distributions
151
+ path: dist/
152
+
153
+ - name: Inspect downloaded distributions
154
+ shell: bash
155
+ run: |
156
+ set -euo pipefail
157
+
158
+ if [[ ! "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
159
+ echo "release tag must match vMAJOR.MINOR.PATCH" >&2
160
+ exit 1
161
+ fi
162
+ version="${GITHUB_REF_NAME#v}"
163
+ wheel="dist/nfse_br-${version}-py3-none-any.whl"
164
+ sdist="dist/nfse_br-${version}.tar.gz"
165
+
166
+ test -f "$wheel"
167
+ test ! -L "$wheel"
168
+ test -f "$sdist"
169
+ test ! -L "$sdist"
170
+
171
+ shopt -s nullglob dotglob
172
+ files=(dist/*)
173
+ test "${#files[@]}" -eq 2
174
+ test "${files[0]}" = "$sdist" || test "${files[0]}" = "$wheel"
175
+ test "${files[1]}" = "$sdist" || test "${files[1]}" = "$wheel"
176
+
177
+ sha256sum -- "$wheel" "$sdist"
178
+
179
+ - name: Publish distributions to PyPI
180
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
181
+ with:
182
+ packages-dir: dist/
183
+ attestations: true
184
+ skip-existing: false
@@ -0,0 +1,41 @@
1
+ # Python bytecode and caches
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Virtual environments
7
+ .venv/
8
+ venv/
9
+
10
+ # Test and coverage artifacts
11
+ .pytest_cache/
12
+ .coverage
13
+ .coverage.*
14
+ coverage.xml
15
+ htmlcov/
16
+
17
+ # Type-checker and linter caches
18
+ .mypy_cache/
19
+ .ruff_cache/
20
+
21
+ # Build artifacts
22
+ build/
23
+ dist/
24
+ *.egg-info/
25
+
26
+ # IDE and editor files
27
+ .idea/
28
+ .vscode/
29
+ *.swp
30
+
31
+ # Local environment and credentials
32
+ .env
33
+ .env.*
34
+ !.env.example
35
+ *.pfx
36
+ *.p12
37
+ *.pem
38
+ *.key
39
+
40
+ # Local official-contract evidence bytes
41
+ .f0/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ ### Added
6
+
7
+ - Typed primitives for NFS-e environment, federal tax identifiers,
8
+ municipality codes, competence dates, DPS series, DPS numbers, and DPS
9
+ identity.
10
+ - Explicit, opt-in CPF and numeric/alphanumeric CNPJ check-digit validation.
11
+ - Frozen provenance for the official restricted-environment evidence and a
12
+ derived restricted DPS schema contract.
13
+ - Local validation against the pinned restricted XSD bundle through the
14
+ optional `xsd` extra.
15
+ - A deterministic unsigned DPS builder for a deliberately limited restricted
16
+ subset.
17
+ - A private structural signature preflight that rejects ambiguous targets;
18
+ this is not signing or cryptographic verification.
19
+ - The `nfse-br check-unsigned` CLI with machine-readable results and bounded
20
+ file reads.
21
+ - Linux and Windows Server 2022 CI for Python 3.12 and 3.13.
22
+ - A tested executable quickstart for generating a synthetic unsigned DPS from
23
+ a checkout.
24
+
25
+ ### Security / hardening
26
+
27
+ - XML parsing denies DTDs, entities, external resolution, unexpected roots,
28
+ and inputs above the documented size limit.
29
+ - Signature preflight requires one unambiguous direct `infDPS`, rejects
30
+ competing identifiers and pre-existing signatures, and reconstructs its
31
+ identity from the document fields.
32
+ - CLI diagnostics use controlled JSON error codes without echoing fiscal
33
+ payloads or caller-provided paths.
34
+ - Base-wheel smoke tests verify that generation and non-XSD operations remain
35
+ usable without `lxml`.
36
+
37
+ ### Known limitations
38
+
39
+ - Issuance, authorization, transmission, HTTP/SEFIN integration, certificate
40
+ handling, and private-key handling are not implemented.
41
+ - The applicable XMLDSig algorithm profile remains unresolved; signing and
42
+ cryptographic verification are not implemented.
43
+ - The builder covers only the documented restricted-environment subset, not a
44
+ complete fiscal model.
45
+ - The production environment is unsupported.
46
+ - End-to-end validation with the pinned official bundle is a local release
47
+ gate and is not executed in CI by design.
@@ -0,0 +1,37 @@
1
+ # Contributing
2
+
3
+ This project supports Python 3.12 and 3.13 and uses
4
+ [`uv`](https://docs.astral.sh/uv/) for dependency and environment management.
5
+
6
+ Use GitHub Flow: branch from an up-to-date `main`, keep pull requests small,
7
+ and use Conventional Commit messages. Do not commit downloaded official
8
+ ZIP/XSD/XLSX/PDF artifacts, generated XML, coverage reports, `.f0/` contents,
9
+ certificates, keys, or fiscal data.
10
+
11
+ Install the frozen development environment and run the same core checks used
12
+ by CI:
13
+
14
+ ```console
15
+ uv sync --frozen --extra xsd
16
+ uv run --frozen --extra xsd ruff check .
17
+ uv run --frozen --extra xsd ruff format --check .
18
+ uv run --frozen --extra xsd mypy src tests scripts examples
19
+ uv run --frozen --extra xsd coverage erase
20
+ uv run --frozen --extra xsd python -c "from pathlib import Path; Path('build/coverage').mkdir(parents=True, exist_ok=True)"
21
+ uv run --frozen --extra xsd pytest \
22
+ --cov=src/nfse_br --cov-branch --cov-report=term-missing \
23
+ --cov-report=json:build/coverage/coverage.json --cov-fail-under=80
24
+ uv run --frozen --extra xsd python scripts/check_coverage.py \
25
+ build/coverage/coverage.json
26
+ uv build
27
+ uv run --frozen --extra xsd python scripts/smoke_base_wheel.py \
28
+ dist/nfse_br-0.1.0-py3-none-any.whl
29
+ ```
30
+
31
+ Run the suite under both supported Python versions. Pull requests are checked
32
+ on Linux and Windows Server 2022; platform-specific skips must be narrow and
33
+ justified. Tests are offline unless a documented local integration procedure
34
+ explicitly says otherwise.
35
+
36
+ Never weaken evidence pins, parser policy, coverage thresholds, or branch
37
+ protection merely to make a change pass.
nfse_br-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cássio Santos
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.