mxm-runtime 0.1.0__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.
@@ -0,0 +1,12 @@
1
+ """Runtime identity discovery and runtime context construction for MXM."""
2
+
3
+ from mxm.runtime.identity import build_runtime_identity
4
+ from mxm.runtime.validation import RuntimeIdentityError, validate_runtime_identity_shape
5
+ from mxm.types import RuntimeIdentity
6
+
7
+ __all__ = [
8
+ "RuntimeIdentity",
9
+ "RuntimeIdentityError",
10
+ "build_runtime_identity",
11
+ "validate_runtime_identity_shape",
12
+ ]
@@ -0,0 +1,57 @@
1
+ """Runtime identity attestation.
2
+
3
+ Attestation checks whether the empirical claims in a RuntimeIdentity match the
4
+ current running process.
5
+
6
+ This is distinct from shape validation. Shape validation checks whether an
7
+ identity object is well formed. Attestation checks whether selected claims are
8
+ true here.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from mxm.runtime.discovery import discover_machine, discover_substrate
14
+ from mxm.types import RuntimeIdentity
15
+
16
+
17
+ class RuntimeIdentityAttestationError(RuntimeError):
18
+ """Raised when a runtime identity is not true for this process."""
19
+
20
+
21
+ def attest_runtime_identity(identity: RuntimeIdentity) -> None:
22
+ """Attest that empirical RuntimeIdentity claims match local discovery.
23
+
24
+ Only locally discoverable empirical fields are attested:
25
+
26
+ - ``machine``
27
+ - ``substrate``
28
+
29
+ The following fields are not attested here because they are invocation or
30
+ deployment claims rather than directly discoverable local facts:
31
+
32
+ - ``app``
33
+ - ``environment``
34
+ - ``role``
35
+ """
36
+ discovered_machine = discover_machine()
37
+ discovered_substrate = discover_substrate()
38
+
39
+ errors: list[str] = []
40
+
41
+ if identity.machine != discovered_machine:
42
+ errors.append(
43
+ "machine claim does not match local discovery: "
44
+ f"identity.machine={identity.machine!r}, "
45
+ f"discovered_machine={discovered_machine!r}"
46
+ )
47
+
48
+ if identity.substrate != discovered_substrate:
49
+ errors.append(
50
+ "substrate claim does not match local discovery: "
51
+ f"identity.substrate={identity.substrate!r}, "
52
+ f"discovered_substrate={discovered_substrate!r}"
53
+ )
54
+
55
+ if errors:
56
+ message = "; ".join(errors)
57
+ raise RuntimeIdentityAttestationError(message)
mxm/runtime/build.py ADDED
@@ -0,0 +1,79 @@
1
+ """RuntimeContext construction for mxm-runtime.
2
+
3
+ This module owns materialisation of a RuntimeContext from an explicit
4
+ RuntimeIdentity and resolved MXM configuration.
5
+
6
+ It deliberately keeps package responsibilities separated:
7
+
8
+ - mxm-config loads, slices, and converts configuration.
9
+ - mxm-secrets constructs the configured SecretsApi from plain config data.
10
+ - mxm-runtime assembles the materialised RuntimeContext.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from pathlib import Path
16
+
17
+ from mxm.config import load_config, make_view, to_config_data
18
+ from mxm.runtime.context import RuntimeContext
19
+ from mxm.secrets import SecretsApi
20
+ from mxm.types import RuntimeIdentity
21
+
22
+
23
+ def build_runtime_context(
24
+ *,
25
+ identity: RuntimeIdentity,
26
+ store_root: Path | None = None,
27
+ ) -> RuntimeContext:
28
+ """Build a RuntimeContext for an explicit runtime identity.
29
+
30
+ Parameters
31
+ ----------
32
+ identity
33
+ Runtime identity used to resolve configuration and materialise runtime
34
+ services.
35
+ store_root
36
+ Optional configuration store root. If omitted, mxm-config uses its
37
+ default configuration store location.
38
+
39
+ Returns
40
+ -------
41
+ RuntimeContext
42
+ Materialised runtime context containing the supplied identity, resolved
43
+ configuration, and configured SecretsApi.
44
+
45
+ Raises
46
+ ------
47
+ FileNotFoundError
48
+ If required configuration files are missing.
49
+ KeyError
50
+ If selected configuration layers or required config sections are absent.
51
+ TypeError
52
+ If configuration sections have invalid structure.
53
+ ValueError
54
+ If secrets configuration fails mxm-secrets validation.
55
+ """
56
+ if store_root is None:
57
+ config = load_config(identity=identity)
58
+ else:
59
+ config = load_config(
60
+ identity=identity,
61
+ store_root=store_root,
62
+ )
63
+
64
+ secrets_config = make_view(
65
+ config,
66
+ "mxm_secrets",
67
+ readonly=True,
68
+ resolve=True,
69
+ )
70
+
71
+ secrets = SecretsApi.from_config_data(
72
+ to_config_data(secrets_config),
73
+ )
74
+
75
+ return RuntimeContext(
76
+ identity=identity,
77
+ config=config,
78
+ secrets=secrets,
79
+ )
mxm/runtime/cli.py ADDED
File without changes
mxm/runtime/context.py ADDED
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+ from mxm.config import MXMConfig
7
+ from mxm.secrets import SecretsApi
8
+ from mxm.types import RuntimeIdentity
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class RuntimePaths:
13
+ """Resolved filesystem locations available to this runtime."""
14
+
15
+ data_root: Path | None = None
16
+ artifact_root: Path | None = None
17
+ state_root: Path | None = None
18
+ log_root: Path | None = None
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class RuntimeMetadata:
23
+ """Materialised metadata about the current execution substrate."""
24
+
25
+ substrate: str
26
+ is_container: bool
27
+
28
+
29
+ @dataclass(frozen=True, slots=True)
30
+ class RuntimeContext:
31
+ """Materialised operational context for an MXM runtime."""
32
+
33
+ identity: RuntimeIdentity
34
+ config: MXMConfig
35
+ secrets: SecretsApi | None = None
36
+ paths: RuntimePaths | None = None
37
+ runtime: RuntimeMetadata | None = None
@@ -0,0 +1,68 @@
1
+ """Runtime discovery helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import socket
6
+ from pathlib import Path
7
+
8
+ DEFAULT_DOCKERENV_PATH = Path("/.dockerenv")
9
+
10
+
11
+ def discover_machine() -> str:
12
+ """Discover the current MXM machine selector.
13
+
14
+ The machine selector identifies the machine-specific configuration profile
15
+ that should be applied by MXM.
16
+
17
+ It is derived from operating-system host information but is not required to
18
+ equal the raw hostname reported by the operating system. The purpose of the
19
+ selector is configuration selection rather than unique host identification.
20
+
21
+ For v0.1, the selector is derived from the local hostname by taking the
22
+ unqualified hostname component.
23
+
24
+ Examples
25
+ --------
26
+ Raw hostname:
27
+
28
+ bridge.local
29
+
30
+ Produces:
31
+
32
+ bridge
33
+
34
+ Raw hostname:
35
+
36
+ monolith
37
+
38
+ Produces:
39
+
40
+ monolith
41
+
42
+ Returns
43
+ -------
44
+ str
45
+ MXM machine selector used for machine-specific configuration loading.
46
+ """
47
+ hostname = socket.gethostname()
48
+ return hostname.split(".", maxsplit=1)[0]
49
+
50
+
51
+ def discover_substrate(
52
+ *,
53
+ dockerenv_path: Path = DEFAULT_DOCKERENV_PATH,
54
+ ) -> str:
55
+ """Discover the current execution substrate.
56
+
57
+ Discovery order:
58
+
59
+ 1. Docker marker file.
60
+ 2. ``local-process`` fallback.
61
+
62
+ The Docker marker path is injectable because ``/.dockerenv`` is a
63
+ conventional probe rather than an MXM-owned runtime concept.
64
+ """
65
+ if dockerenv_path.exists():
66
+ return "docker-container"
67
+
68
+ return "local-process"
@@ -0,0 +1,59 @@
1
+ """Runtime identity construction.
2
+
3
+ This module provides the small public construction helper for MXM runtime
4
+ identity. It deliberately keeps discovery and validation at the boundary:
5
+ callers may pass explicit values, while missing machine/substrate values are
6
+ delegated to discovery helpers.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from mxm.runtime.discovery import discover_machine, discover_substrate
12
+ from mxm.runtime.validation import validate_runtime_identity_shape
13
+ from mxm.types import (
14
+ RuntimeIdentity,
15
+ )
16
+
17
+
18
+ def build_runtime_identity(
19
+ *,
20
+ app: str,
21
+ environment: str,
22
+ role: str,
23
+ machine: str | None = None,
24
+ substrate: str | None = None,
25
+ ) -> RuntimeIdentity:
26
+ """Build and validate a RuntimeIdentity.
27
+
28
+ Parameters
29
+ ----------
30
+ app:
31
+ MXM application or package identifier.
32
+ environment:
33
+ Operational environment, for example ``dev``, ``test``, or ``prod``.
34
+ role:
35
+ Runtime responsibility, for example ``research`` or ``marketdata``.
36
+ machine:
37
+ Physical or logical host identifier. If omitted, this is discovered.
38
+ substrate:
39
+ Execution substrate. If omitted, this is discovered.
40
+
41
+ Returns
42
+ -------
43
+ RuntimeIdentity
44
+ A validated runtime identity using the shared mxm-types vocabulary.
45
+ """
46
+ resolved_machine = machine if machine is not None else discover_machine()
47
+ resolved_substrate = substrate if substrate is not None else discover_substrate()
48
+
49
+ identity = RuntimeIdentity(
50
+ app=app,
51
+ environment=environment,
52
+ machine=resolved_machine,
53
+ substrate=resolved_substrate,
54
+ role=role,
55
+ )
56
+
57
+ validate_runtime_identity_shape(identity)
58
+
59
+ return identity
mxm/runtime/py.typed ADDED
File without changes
@@ -0,0 +1,32 @@
1
+ """Runtime identity validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from mxm.types import RuntimeIdentity
6
+
7
+
8
+ class RuntimeIdentityError(ValueError):
9
+ """Raised when a runtime identity is invalid."""
10
+
11
+
12
+ def validate_runtime_identity_shape(identity: RuntimeIdentity) -> None:
13
+ """Validate a RuntimeIdentity.
14
+
15
+ Validation is intentionally minimal for v0.1.0. Policy-specific allowed
16
+ values should be introduced only once the deployment model has stabilized.
17
+ """
18
+ fields = {
19
+ "app": identity.app,
20
+ "environment": identity.environment,
21
+ "machine": identity.machine,
22
+ "substrate": identity.substrate,
23
+ "role": identity.role,
24
+ }
25
+
26
+ missing = [name for name, value in fields.items() if not str(value).strip()]
27
+
28
+ if missing:
29
+ joined = ", ".join(missing)
30
+ raise RuntimeIdentityError(
31
+ f"RuntimeIdentity contains empty required field(s): {joined}"
32
+ )
@@ -0,0 +1,378 @@
1
+ Metadata-Version: 2.4
2
+ Name: mxm-runtime
3
+ Version: 0.1.0
4
+ Summary: Runtime identity discovery and runtime context construction for Money Ex Machina.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: runtime,context,identity,configuration,deployment,infrastructure,orchestration,prefect,mxm
8
+ Author: mxm
9
+ Author-email: contact@moneyexmachina.com
10
+ Requires-Python: >=3.13,<3.15
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Typing :: Typed
18
+ Requires-Dist: mxm-config (>=0.6.2,<0.7)
19
+ Requires-Dist: mxm-secrets (>=0.3.2,<0.4)
20
+ Requires-Dist: mxm-types (>=0.3.1,<0.4.0)
21
+ Requires-Dist: rich (>=14.2.0,<15.0.0)
22
+ Requires-Dist: typer (>=0.20.0,<0.21.0)
23
+ Project-URL: Homepage, https://github.com/moneyexmachina/mxm-runtime
24
+ Project-URL: Repository, https://github.com/moneyexmachina/mxm-runtime
25
+ Description-Content-Type: text/markdown
26
+
27
+ # mxm-runtime
28
+
29
+ ![Version](https://img.shields.io/github/v/release/moneyexmachina/mxm-runtime)
30
+ ![License](https://img.shields.io/github/license/moneyexmachina/mxm-runtime)
31
+ ![Python](https://img.shields.io/badge/python-3.13+-blue)
32
+ [![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
33
+
34
+ Runtime discovery, configuration-driven service construction, and RuntimeContext assembly for the Money Ex Machina ecosystem.
35
+
36
+ `mxm-runtime` is responsible for constructing the operational environment in which MXM applications execute.
37
+
38
+ It discovers runtime characteristics, loads and resolves configuration, constructs configured services, and assembles them into a single RuntimeContext.
39
+
40
+ ## Purpose
41
+
42
+ MXM applications require more than configuration files.
43
+
44
+ They require an operational environment:
45
+
46
+ ```text
47
+ Who am I?
48
+ Where am I running?
49
+ Which configuration applies?
50
+ Which services are available?
51
+ Where should data and artefacts live?
52
+ ```
53
+
54
+ `mxm-runtime` answers these questions.
55
+
56
+ It exists to separate:
57
+
58
+ ```text
59
+ application code
60
+ ```
61
+
62
+ from:
63
+
64
+ ```text
65
+ runtime discovery
66
+ configuration loading
67
+ service construction
68
+ deployment concerns
69
+ context assembly
70
+ ```
71
+
72
+ Applications should not:
73
+
74
+ - discover machine characteristics,
75
+ - determine deployment substrate,
76
+ - load configuration directly,
77
+ - construct service APIs,
78
+ - or reason about deployment topology.
79
+
80
+ Instead, applications receive a configured RuntimeContext:
81
+
82
+ ```python
83
+ context = build_runtime_context(
84
+ identity=runtime_identity,
85
+ )
86
+ ```
87
+
88
+ and consume the services and resources provided by that context.
89
+
90
+ ## Architecture
91
+
92
+ `mxm-runtime` acts as the runtime constructor layer of the MXM architecture.
93
+
94
+ ```text
95
+ RuntimeIdentity
96
+ ↓
97
+ mxm-config
98
+ ↓
99
+ Configuration Resolution
100
+ ↓
101
+ Service Construction
102
+ ↓
103
+ RuntimeContext
104
+ ↓
105
+ Application
106
+ ```
107
+
108
+ The package owns runtime construction.
109
+
110
+ It does not own configuration semantics or secret resolution semantics.
111
+
112
+ Those responsibilities belong to:
113
+
114
+ ```text
115
+ mxm-config
116
+ ```
117
+
118
+ and:
119
+
120
+ ```text
121
+ mxm-secrets
122
+ ```
123
+
124
+ respectively.
125
+
126
+ ## Core Concepts
127
+
128
+ ### RuntimeIdentity
129
+
130
+ Represents the operational identity of a running process.
131
+
132
+ Example:
133
+
134
+ ```text
135
+ app mxm-moneymachine
136
+ environment dev
137
+ machine bridge
138
+ substrate local-process
139
+ role marketdata
140
+ ```
141
+
142
+ Runtime identity determines which configuration layers are selected and which services are constructed.
143
+
144
+ ### Machine
145
+
146
+ A machine identifies a machine-specific configuration profile.
147
+
148
+ Examples:
149
+
150
+ ```text
151
+ bridge
152
+ monolith
153
+ wildling
154
+ scribe
155
+ ```
156
+
157
+ Machine values are derived from operating-system characteristics and are used to select machine-specific configuration.
158
+
159
+ They are configuration selectors rather than unique hardware identifiers.
160
+
161
+ ### Substrate
162
+
163
+ Represents the execution substrate.
164
+
165
+ Examples:
166
+
167
+ ```text
168
+ local-process
169
+ docker
170
+ ```
171
+
172
+ Substrate allows runtime construction to adapt to deployment environment differences.
173
+
174
+ ### RuntimeContext
175
+
176
+ Represents the fully constructed operational environment.
177
+
178
+ Current fields:
179
+
180
+ ```python
181
+ RuntimeContext(
182
+ identity=...,
183
+ config=...,
184
+ secrets=...,
185
+ )
186
+ ```
187
+
188
+ Future versions may additionally materialise:
189
+
190
+ ```text
191
+ paths
192
+ databases
193
+ reference data services
194
+ storage services
195
+ execution services
196
+ reporting services
197
+ ```
198
+
199
+ Applications are expected to consume runtime services through RuntimeContext.
200
+
201
+ ## Runtime Construction Flow
202
+
203
+ Runtime construction follows the sequence:
204
+
205
+ ```text
206
+ RuntimeIdentity
207
+ ↓
208
+ load_config(...)
209
+ ↓
210
+ Configuration Views
211
+ ↓
212
+ Service Construction
213
+ ↓
214
+ RuntimeContext
215
+ ```
216
+
217
+ For example:
218
+
219
+ ```python
220
+ context = build_runtime_context(
221
+ identity=identity,
222
+ )
223
+ ```
224
+
225
+ which currently materialises:
226
+
227
+ ```text
228
+ configuration
229
+ secret services
230
+ ```
231
+
232
+ and returns a configured RuntimeContext.
233
+
234
+ ## Runtime Discovery
235
+
236
+ `mxm-runtime` provides discovery utilities for determining runtime characteristics.
237
+
238
+ Examples:
239
+
240
+ ```python
241
+ machine = discover_machine()
242
+ substrate = discover_substrate()
243
+ ```
244
+
245
+ These functions derive MXM runtime selectors from operating-system facts.
246
+
247
+ The resulting values are suitable for configuration resolution and runtime construction.
248
+
249
+ ## Relationship To mxm-config
250
+
251
+ `mxm-config` owns:
252
+
253
+ ```text
254
+ configuration storage
255
+ configuration loading
256
+ configuration merging
257
+ configuration views
258
+ ```
259
+
260
+ `mxm-runtime` consumes configuration and constructs runtime services from it.
261
+
262
+ Example:
263
+
264
+ ```text
265
+ RuntimeIdentity
266
+ ↓
267
+ mxm-config
268
+ ↓
269
+ MXMConfig
270
+ ↓
271
+ RuntimeContext
272
+ ```
273
+
274
+ ## Relationship To mxm-secrets
275
+
276
+ `mxm-secrets` owns:
277
+
278
+ ```text
279
+ secret references
280
+ authorization
281
+ resolution
282
+ retrieval
283
+ ```
284
+
285
+ `mxm-runtime` constructs configured secret services and makes them available through RuntimeContext.
286
+
287
+ Applications are expected to consume:
288
+
289
+ ```python
290
+ context.secrets
291
+ ```
292
+
293
+ rather than constructing SecretsApi instances directly.
294
+
295
+ ## Installation
296
+
297
+ ```bash
298
+ pip install mxm-runtime
299
+ ```
300
+
301
+ ## Usage
302
+
303
+ Construct a RuntimeContext:
304
+
305
+ ```python
306
+ from mxm.runtime import build_runtime_context
307
+
308
+ context = build_runtime_context(
309
+ identity=identity,
310
+ )
311
+ ```
312
+
313
+ Access configured services:
314
+
315
+ ```python
316
+ api_key = context.secrets.get_secret(
317
+ "databento_api_key",
318
+ identity=context.identity,
319
+ )
320
+ ```
321
+
322
+ ## Design Principles
323
+
324
+ - **Explicit runtime identity**
325
+ Runtime identity is always represented explicitly.
326
+
327
+ - **Configuration-driven construction**
328
+ Runtime behaviour is determined through configuration rather than hardcoded wiring.
329
+
330
+ - **Separation of concerns**
331
+ Discovery, configuration, resolution, construction, and application logic remain separate.
332
+
333
+ - **Strict typing**
334
+ Fully Pyright-clean and PEP 561 compliant.
335
+
336
+ - **Minimal implicit behaviour**
337
+ Runtime construction is deterministic and inspectable.
338
+
339
+ - **Composable services**
340
+ Runtime services are assembled from independent packages.
341
+
342
+ ## Development
343
+
344
+ ```bash
345
+ poetry install
346
+
347
+ make check
348
+ ```
349
+
350
+ Run the RuntimeContext smoke test:
351
+
352
+ ```bash
353
+ poetry run python scripts/smoke_runtime_context.py
354
+ ```
355
+
356
+ ## Status
357
+
358
+ Current release status:
359
+
360
+ ```text
361
+ Runtime Identity Discovery Complete
362
+ RuntimeContext Construction Complete
363
+ Service Expansion In Progress
364
+ ```
365
+
366
+ Current RuntimeContext materialises:
367
+
368
+ ```text
369
+ configuration
370
+ secrets
371
+ ```
372
+
373
+ Additional services will be added incrementally.
374
+
375
+ ## License
376
+
377
+ MIT License. See [LICENSE](LICENSE).
378
+
@@ -0,0 +1,13 @@
1
+ mxm/runtime/__init__.py,sha256=Dlj02SLeQBLy-7njvXumhUJ1q6-9lEsr09nlBU6Jc2Q,394
2
+ mxm/runtime/attestation.py,sha256=OaUiyMr454Mu_agczucofcg-wtCZm2OmUPIejs-g2lI,1779
3
+ mxm/runtime/build.py,sha256=mOzNc-z1X4UQ_eqVfs5GxU_Px0TXxdK6GP8HTOJyvCU,2166
4
+ mxm/runtime/cli.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ mxm/runtime/context.py,sha256=qzR8JJytt7i85Cm_oSLreal5Vc4PXN0CC5KG8pEMgJ4,938
6
+ mxm/runtime/discovery.py,sha256=yPr-C7xO-i8iw9FBp7fKCboDBcpN_UWEe8JEnHZMFN0,1542
7
+ mxm/runtime/identity.py,sha256=7Kwx8-_-TAdmzHlY81hsAPhlD4sFa4uVjpHfmIxz5f4,1713
8
+ mxm/runtime/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ mxm/runtime/validation.py,sha256=53nokAbSfwlktZH3q1X9oiJnDCF_00yPo5IzfFN6thU,937
10
+ mxm_runtime-0.1.0.dist-info/METADATA,sha256=5NUqWt0xoK9kT066ll1QayEgiHMwgd9j1YkbkI8DyOE,7294
11
+ mxm_runtime-0.1.0.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
12
+ mxm_runtime-0.1.0.dist-info/licenses/LICENSE,sha256=4_iKgf-rYcWbqm27b61H2XDN_22u7YUfPbrLYJMKHjc,1106
13
+ mxm_runtime-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.4.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Money Ex Machina
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.