connect-flow 0.1.1__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.
- connect_flow-0.1.1/.claude/settings.json +24 -0
- connect_flow-0.1.1/.gitignore +10 -0
- connect_flow-0.1.1/.gitlab-ci.yml +37 -0
- connect_flow-0.1.1/.pre-commit-config.yaml +39 -0
- connect_flow-0.1.1/.pre-commit-hooks.yaml +15 -0
- connect_flow-0.1.1/CHANGELOG.md +18 -0
- connect_flow-0.1.1/CLAUDE.md +127 -0
- connect_flow-0.1.1/LICENSE +21 -0
- connect_flow-0.1.1/PKG-INFO +716 -0
- connect_flow-0.1.1/README.md +689 -0
- connect_flow-0.1.1/docs/adr/0001-cross-environment-id-mapping.md +66 -0
- connect_flow-0.1.1/docs/adr/0002-default-region-fallback.md +37 -0
- connect_flow-0.1.1/docs/adr/0003-boto3-required-dependency.md +37 -0
- connect_flow-0.1.1/docs/adr/0004-name-based-addressing.md +64 -0
- connect_flow-0.1.1/docs/adr/0005-default-to-stdout.md +54 -0
- connect_flow-0.1.1/docs/adr/0006-list-describe-mode.md +44 -0
- connect_flow-0.1.1/docs/adr/0007-normalize-opaque-reference-resolution.md +66 -0
- connect_flow-0.1.1/docs/adr/0008-unified-instance-flow-addressing.md +83 -0
- connect_flow-0.1.1/docs/adr/0009-copy-verb.md +63 -0
- connect_flow-0.1.1/docs/adr/0010-diff-instance-addressing.md +40 -0
- connect_flow-0.1.1/docs/adr/0011-cli-module-split.md +52 -0
- connect_flow-0.1.1/docs/adr/0012-load-module-split.md +50 -0
- connect_flow-0.1.1/docs/adr/0013-copy-name-auto-resolution.md +47 -0
- connect_flow-0.1.1/docs/adr/0014-copy-preview-exit-code.md +35 -0
- connect_flow-0.1.1/docs/adr/0015-copy-writes-by-default.md +59 -0
- connect_flow-0.1.1/docs/adr/0016-functional-only-comparison.md +77 -0
- connect_flow-0.1.1/docs/adr/0017-emit-map-auto-naming-and-merge.md +64 -0
- connect_flow-0.1.1/docs/adr/0018-saved-qualifier-name-based-addressing.md +61 -0
- connect_flow-0.1.1/docs/adr/0019-emit-map-deterministic-output.md +62 -0
- connect_flow-0.1.1/docs/adr/0020-optional-graphviz-extra.md +36 -0
- connect_flow-0.1.1/docs/data-model.md +113 -0
- connect_flow-0.1.1/docs/design.md +185 -0
- connect_flow-0.1.1/docs/images/messy.after.jpg +0 -0
- connect_flow-0.1.1/docs/images/messy.before.jpg +0 -0
- connect_flow-0.1.1/docs/safety.md +214 -0
- connect_flow-0.1.1/pyproject.toml +50 -0
- connect_flow-0.1.1/scripts/release.py +143 -0
- connect_flow-0.1.1/src/connect_flow/__init__.py +1 -0
- connect_flow-0.1.1/src/connect_flow/__main__.py +4 -0
- connect_flow-0.1.1/src/connect_flow/args.py +647 -0
- connect_flow-0.1.1/src/connect_flow/arn.py +177 -0
- connect_flow-0.1.1/src/connect_flow/autoresolve.py +259 -0
- connect_flow-0.1.1/src/connect_flow/aws.py +414 -0
- connect_flow-0.1.1/src/connect_flow/cli.py +612 -0
- connect_flow-0.1.1/src/connect_flow/diff.py +262 -0
- connect_flow-0.1.1/src/connect_flow/digest.py +135 -0
- connect_flow-0.1.1/src/connect_flow/exits.py +27 -0
- connect_flow-0.1.1/src/connect_flow/flow.py +121 -0
- connect_flow-0.1.1/src/connect_flow/graph.py +147 -0
- connect_flow-0.1.1/src/connect_flow/layout.py +185 -0
- connect_flow-0.1.1/src/connect_flow/lint.py +89 -0
- connect_flow-0.1.1/src/connect_flow/load.py +151 -0
- connect_flow-0.1.1/src/connect_flow/mapping.py +343 -0
- connect_flow-0.1.1/src/connect_flow/metrics.py +94 -0
- connect_flow-0.1.1/src/connect_flow/pipeline.py +917 -0
- connect_flow-0.1.1/src/connect_flow/py.typed +0 -0
- connect_flow-0.1.1/src/connect_flow/remote_write.py +177 -0
- connect_flow-0.1.1/src/connect_flow/render.py +66 -0
- connect_flow-0.1.1/src/connect_flow/report.py +184 -0
- connect_flow-0.1.1/src/connect_flow/resolve.py +324 -0
- connect_flow-0.1.1/src/connect_flow/targets.py +167 -0
- connect_flow-0.1.1/src/connect_flow/writer.py +211 -0
- connect_flow-0.1.1/tests/conftest.py +53 -0
- connect_flow-0.1.1/tests/fixtures/README.md +21 -0
- connect_flow-0.1.1/tests/fixtures/annotations.json +47 -0
- connect_flow-0.1.1/tests/fixtures/compound.json +48 -0
- connect_flow-0.1.1/tests/fixtures/env_arns.json +38 -0
- connect_flow-0.1.1/tests/fixtures/env_map.json +5 -0
- connect_flow-0.1.1/tests/fixtures/first_match.json +61 -0
- connect_flow-0.1.1/tests/fixtures/mapped.json +38 -0
- connect_flow-0.1.1/tests/fixtures/messy.json +113 -0
- connect_flow-0.1.1/tests/fixtures/messy.pretty.json +113 -0
- connect_flow-0.1.1/tests/fixtures/no_entry_point.json +33 -0
- connect_flow-0.1.1/tests/fixtures/style_4space +34 -0
- connect_flow-0.1.1/tests/fixtures/style_expanded.json +45 -0
- connect_flow-0.1.1/tests/fixtures/tiny.json +45 -0
- connect_flow-0.1.1/tests/fixtures/unreachable.json +56 -0
- connect_flow-0.1.1/tests/test_arn.py +105 -0
- connect_flow-0.1.1/tests/test_autoresolve.py +260 -0
- connect_flow-0.1.1/tests/test_aws.py +400 -0
- connect_flow-0.1.1/tests/test_cli.py +245 -0
- connect_flow-0.1.1/tests/test_cli_diff.py +232 -0
- connect_flow-0.1.1/tests/test_cli_diff_normalize.py +177 -0
- connect_flow-0.1.1/tests/test_cli_pull_list.py +564 -0
- connect_flow-0.1.1/tests/test_cli_remote.py +387 -0
- connect_flow-0.1.1/tests/test_cli_translate.py +723 -0
- connect_flow-0.1.1/tests/test_corpus.py +64 -0
- connect_flow-0.1.1/tests/test_diff.py +320 -0
- connect_flow-0.1.1/tests/test_digest.py +128 -0
- connect_flow-0.1.1/tests/test_graph.py +88 -0
- connect_flow-0.1.1/tests/test_import_drift.py +95 -0
- connect_flow-0.1.1/tests/test_layout.py +83 -0
- connect_flow-0.1.1/tests/test_lint.py +93 -0
- connect_flow-0.1.1/tests/test_mapping.py +306 -0
- connect_flow-0.1.1/tests/test_metrics.py +71 -0
- connect_flow-0.1.1/tests/test_render.py +48 -0
- connect_flow-0.1.1/tests/test_targets.py +101 -0
- connect_flow-0.1.1/tests/test_writer.py +128 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Bash|Grep",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "graphify hook-guard search"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"matcher": "Read|Glob",
|
|
15
|
+
"hooks": [
|
|
16
|
+
{
|
|
17
|
+
"type": "command",
|
|
18
|
+
"command": "graphify hook-guard read"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
default:
|
|
2
|
+
image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
3
|
+
|
|
4
|
+
stages:
|
|
5
|
+
- test
|
|
6
|
+
- publish
|
|
7
|
+
|
|
8
|
+
test:
|
|
9
|
+
stage: test
|
|
10
|
+
script:
|
|
11
|
+
# pygraphviz's wheel needs several system shared libs (expat, glib,
|
|
12
|
+
# ...) that bookworm-slim omits; installing the graphviz apt package
|
|
13
|
+
# pulls its whole correct dependency tree instead of chasing each
|
|
14
|
+
# missing .so one at a time.
|
|
15
|
+
- apt-get update && apt-get install -y --no-install-recommends graphviz
|
|
16
|
+
- uv sync --all-extras
|
|
17
|
+
- uv run pytest
|
|
18
|
+
|
|
19
|
+
publish:
|
|
20
|
+
stage: publish
|
|
21
|
+
rules:
|
|
22
|
+
- if: $CI_COMMIT_TAG
|
|
23
|
+
# PyPI trusted publishing (no stored secret): this job requests a short-
|
|
24
|
+
# lived OIDC token scoped to `pypi`; twine >=6.0 auto-detects it via the
|
|
25
|
+
# PYPI_ID_TOKEN env var and exchanges it for a PyPI API token itself when
|
|
26
|
+
# no credentials are passed - see https://docs.pypi.org/trusted-publishers/
|
|
27
|
+
# using-a-publisher/. One-time setup: on pypi.org -> Your projects ->
|
|
28
|
+
# connect-flow -> Settings -> Publishing, add a GitLab publisher for
|
|
29
|
+
# namespace `mludvig`, project `connect-flow`, workflow filename
|
|
30
|
+
# `.gitlab-ci.yml` (a "pending" publisher can be added before the project
|
|
31
|
+
# exists on PyPI at all, for the very first release).
|
|
32
|
+
id_tokens:
|
|
33
|
+
PYPI_ID_TOKEN:
|
|
34
|
+
aud: pypi
|
|
35
|
+
script:
|
|
36
|
+
- uv build
|
|
37
|
+
- uvx twine upload dist/*
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v6.0.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: check-added-large-files
|
|
6
|
+
- id: check-case-conflict
|
|
7
|
+
- id: check-merge-conflict
|
|
8
|
+
- id: check-json
|
|
9
|
+
- id: check-yaml
|
|
10
|
+
- id: check-xml
|
|
11
|
+
- id: detect-aws-credentials
|
|
12
|
+
args: ["--allow-missing-credentials"]
|
|
13
|
+
- id: mixed-line-ending
|
|
14
|
+
args: ["--fix", "auto"]
|
|
15
|
+
- id: end-of-file-fixer
|
|
16
|
+
- id: trailing-whitespace
|
|
17
|
+
|
|
18
|
+
- repo: local
|
|
19
|
+
hooks:
|
|
20
|
+
- id: ruff-check
|
|
21
|
+
name: ruff check
|
|
22
|
+
entry: uv run ruff check --fix
|
|
23
|
+
language: system
|
|
24
|
+
types: [python]
|
|
25
|
+
pass_filenames: true
|
|
26
|
+
|
|
27
|
+
- id: ruff-format
|
|
28
|
+
name: ruff format
|
|
29
|
+
entry: uv run ruff format
|
|
30
|
+
language: system
|
|
31
|
+
types: [python]
|
|
32
|
+
pass_filenames: true
|
|
33
|
+
|
|
34
|
+
#- id: mypy
|
|
35
|
+
# name: mypy
|
|
36
|
+
# entry: uv run mypy src/
|
|
37
|
+
# language: system
|
|
38
|
+
# types: [python]
|
|
39
|
+
# pass_filenames: false
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Hook definitions published FOR OTHER REPOS to consume.
|
|
2
|
+
#
|
|
3
|
+
# The audience here is a repo that stores Amazon Connect contact flow JSON
|
|
4
|
+
# and wants it kept in the neutral layout.
|
|
5
|
+
#
|
|
6
|
+
# Consumers should narrow `files:` to wherever their flow JSON actually
|
|
7
|
+
# lives — the default below matches every .json in the repo.
|
|
8
|
+
|
|
9
|
+
- id: connect-flow
|
|
10
|
+
name: connect-flow
|
|
11
|
+
description: Auto-arrange Amazon Connect contact flow JSON without changing behaviour
|
|
12
|
+
entry: connect-flow
|
|
13
|
+
language: python
|
|
14
|
+
files: \.json$
|
|
15
|
+
args: [prettify, --check]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.1.1] - 2026-09-10
|
|
9
|
+
|
|
10
|
+
- `pygraphviz` is now an optional `[graphviz]` extra instead of a required
|
|
11
|
+
dependency - `lint`, `diff` and `digest` no longer need it installed.
|
|
12
|
+
- Added PyPI release metadata (classifiers, license files, project URLs).
|
|
13
|
+
- Added a GitLab CI pipeline that runs tests on every push and publishes to
|
|
14
|
+
PyPI via trusted publishing on tagged commits.
|
|
15
|
+
|
|
16
|
+
## [0.1.0] - 2026-09-03
|
|
17
|
+
|
|
18
|
+
Initial release.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Guidance for working on this repository.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
A standalone CLI that rewrites Amazon Connect contact flow JSON block
|
|
8
|
+
*positions* into a readable auto-layout, guaranteeing no behavioural change.
|
|
9
|
+
See `README.md` for usage and `docs/design.md`, `docs/data-model.md`,
|
|
10
|
+
`docs/safety.md` for the reasoning — read those before changing `writer.py`,
|
|
11
|
+
`digest.py`, or `graph.py`, since each encodes a specific, previously
|
|
12
|
+
measured or tested invariant, not an arbitrary choice.
|
|
13
|
+
|
|
14
|
+
## The one rule that matters most
|
|
15
|
+
|
|
16
|
+
`digest.py`'s semantic digest is the safety boundary. Any change to
|
|
17
|
+
`writer.py`, `flow.py`, or `digest.py` itself must preserve: digests are
|
|
18
|
+
invariant under any position/geometry change, and are sensitive to every
|
|
19
|
+
other field. If you're not sure whether a change preserves this, add a test
|
|
20
|
+
in `tests/test_digest.py` proving it before proceeding, not after.
|
|
21
|
+
|
|
22
|
+
## Never re-serialise a flow file
|
|
23
|
+
|
|
24
|
+
`writer.py` patches position literals via regex, in place, and must never
|
|
25
|
+
route through `json.dumps` on a whole file. See `docs/safety.md` for why —
|
|
26
|
+
in short, it destroys the property that a rewrite's diff is exactly the
|
|
27
|
+
coordinates that changed.
|
|
28
|
+
|
|
29
|
+
## Edge order into `dot` must never be re-sorted
|
|
30
|
+
|
|
31
|
+
`layout.py` feeds `graph.edges` to `pygraphviz` in the exact order
|
|
32
|
+
`graph.py` produced them (i.e., in designer port order), because
|
|
33
|
+
`ordering="out"` depends on insertion order as each node's port order. This
|
|
34
|
+
has regressed once already from an innocuous-looking `sorted(...)` — see the
|
|
35
|
+
comment in `layout.py` and `docs/design.md`'s "Edge order and weight" section
|
|
36
|
+
before touching this.
|
|
37
|
+
|
|
38
|
+
## Test data must stay synthetic
|
|
39
|
+
|
|
40
|
+
This repo is open source and must never contain real customer data,
|
|
41
|
+
identifiers, or references to any specific deployment. Everything under
|
|
42
|
+
`tests/fixtures/` is hand-authored from scratch with throwaway UUIDs — see
|
|
43
|
+
`tests/fixtures/README.md`. Do not add a fixture derived from, or copy any
|
|
44
|
+
fragment of, a real flow export. `tests/test_corpus.py` exists precisely so
|
|
45
|
+
that real-corpus validation can happen locally (via `$CONNECT_FLOW_CORPUS`)
|
|
46
|
+
without ever needing to commit that corpus.
|
|
47
|
+
|
|
48
|
+
This rule isn't limited to fixtures: help text, error messages, docstrings,
|
|
49
|
+
version/snapshot descriptions, and doc examples are all committed text too,
|
|
50
|
+
and all of them handle real ARNs, account ids, instance ids and resource
|
|
51
|
+
names at runtime. Every example anywhere in this repo uses the one
|
|
52
|
+
placeholder set (`11111111-1111-…` instance, `22222222-…`/`33333333-…`
|
|
53
|
+
resource, account `000000000000`) — never a value copied from a real
|
|
54
|
+
console session or CLI run, even a redacted-looking one.
|
|
55
|
+
|
|
56
|
+
## The CLI is six modules, not one
|
|
57
|
+
|
|
58
|
+
`cli.py` is the verb dispatch and the five verb runners only. The rest of
|
|
59
|
+
the CLI is layered beneath it, and imports flow one way — never back into
|
|
60
|
+
`cli.py`, which is what keeps the graph acyclic. `resolve.py` and `load.py`
|
|
61
|
+
are a parallel pair, not a chain — neither imports the other:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
args.py -> resolve.py -> remote_write.py -> pipeline.py -> cli.py
|
|
65
|
+
load.py ---------^
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- `args.py` — parser construction and cross-flag validation.
|
|
69
|
+
- `resolve.py` — strings to regions, clients, instance ids, resolved targets.
|
|
70
|
+
Never touches a flow's contents.
|
|
71
|
+
- `load.py` — an already-resolved target to a loaded `Flow`, wherever it
|
|
72
|
+
lives; the only code in the CLI layer that reads one. See
|
|
73
|
+
`docs/adr/0012-load-module-split.md` for why this is split from `resolve.py`
|
|
74
|
+
rather than folded into it.
|
|
75
|
+
- `remote_write.py` — confirm and upload; the only code that mutates a live
|
|
76
|
+
instance, kept isolated so it can be audited alone.
|
|
77
|
+
- `pipeline.py` — the shared transform-and-emit engine behind `prettify`,
|
|
78
|
+
`copy` and `normalize`.
|
|
79
|
+
|
|
80
|
+
New CLI code goes in the layer it belongs to, not in `cli.py` by default —
|
|
81
|
+
see `docs/adr/0011-cli-module-split.md` for why the file was split. Exit
|
|
82
|
+
codes come from `exits.py` directly in every module; `cli.py` only
|
|
83
|
+
re-exports them (along with `main_connect_flow`,
|
|
84
|
+
`build_connect_flow_parser` and `_classify_copy_endpoint`) so the console
|
|
85
|
+
script, `__main__.py` and the tests address one unchanged name.
|
|
86
|
+
|
|
87
|
+
## `boto3` stays contained to `aws.py`
|
|
88
|
+
|
|
89
|
+
`src/connect_flow/aws.py` is the *only* module allowed to import
|
|
90
|
+
`boto3` or `botocore`. `boto3` is a required dependency (see
|
|
91
|
+
`docs/adr/0003-boto3-required-dependency.md` for why the optional `[aws]`
|
|
92
|
+
extra was dropped), but the containment still matters: `aws.py`'s job is to
|
|
93
|
+
be the only place that ever sees a `botocore.ClientError`, translating every
|
|
94
|
+
AWS failure into `AwsError` before anything above it can observe a botocore
|
|
95
|
+
type — and to keep `resolve.py`, `load.py`, `cli.py`, `targets.py`, `arn.py`
|
|
96
|
+
free of credential/region resolution details that belong to boto3, not to
|
|
97
|
+
this tool's own logic. `resolve.py` is where region and client resolution
|
|
98
|
+
now lives, so it is the module most likely to tempt you — it calls
|
|
99
|
+
`aws.get_default_region()`/`aws.client()` and must keep doing so rather
|
|
100
|
+
than reaching past them. `load.py` constructs `aws.RemoteFlowClient`
|
|
101
|
+
instances from a factory it's handed, for the same reason — it must keep
|
|
102
|
+
going through `aws.py` rather than reaching past it. If
|
|
103
|
+
you're adding remote-target code anywhere else and find yourself reaching
|
|
104
|
+
for `boto3` directly, that's a sign the operation belongs in `aws.py`
|
|
105
|
+
instead.
|
|
106
|
+
|
|
107
|
+
## Commands
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
uv sync
|
|
111
|
+
uv run pytest
|
|
112
|
+
uv run connect-flow --version
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
No linter or type-checker is currently wired in; keep changes consistent
|
|
116
|
+
with the existing style (dataclasses, `from __future__ import annotations`,
|
|
117
|
+
docstrings that explain *why*, not what).
|
|
118
|
+
|
|
119
|
+
## graphify
|
|
120
|
+
|
|
121
|
+
This project has a knowledge graph at graphify-out/ with god nodes, community structure, and cross-file relationships.
|
|
122
|
+
|
|
123
|
+
Rules:
|
|
124
|
+
- For codebase questions, first run `graphify query "<question>"` when graphify-out/graph.json exists. Use `graphify path "<A>" "<B>"` for relationships and `graphify explain "<concept>"` for focused concepts. These return a scoped subgraph, usually much smaller than GRAPH_REPORT.md or raw grep output.
|
|
125
|
+
- If graphify-out/wiki/index.md exists, use it for broad navigation instead of raw source browsing.
|
|
126
|
+
- Read graphify-out/GRAPH_REPORT.md only for broad architecture review or when query/path/explain do not surface enough context.
|
|
127
|
+
- After modifying code, run `graphify update .` to keep the graph current (AST-only, no API cost).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 connect-flow contributors
|
|
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.
|