icmd-python 3.11.0__py3-none-any.whl → 3.14.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.
icmd/__init__.py CHANGED
@@ -22,6 +22,7 @@ from .core.exceptions import (
22
22
  ICMDResultsRefusedError,
23
23
  ICMDTokenRefreshError,
24
24
  ICMDValidationError,
25
+ ICMDVersionError,
25
26
  )
26
27
  from .core.model_classes import ContractFallbackWarning
27
28
  from .links import KIND_PATHS, link_for
@@ -105,6 +106,7 @@ __all__ = [
105
106
  "ICMDResultsRefusedError",
106
107
  "ICMDTokenRefreshError",
107
108
  "ICMDValidationError",
109
+ "ICMDVersionError",
108
110
  "LatinHypercubeSampling",
109
111
  "ModelInput",
110
112
  "ModelNode",
icmd/_vendor/assembler.py CHANGED
@@ -113,7 +113,7 @@ def assemble_model_node(
113
113
 
114
114
 
115
115
  def build_design_document(
116
- composition: Mapping[str, Any],
116
+ composition: Mapping[str, Any] | None,
117
117
  variables: Mapping[str, Any] | None = None,
118
118
  databases: Sequence[Mapping[str, Any]] | None = None,
119
119
  engine: str | None = None,
@@ -126,6 +126,12 @@ def build_design_document(
126
126
  all, so an empty one is left out rather than written present-and-falsy.
127
127
  Passing ``[]`` or ``""`` is therefore the same as passing nothing, and the
128
128
  advisory reports the deferring model either way.
129
+
130
+ ``composition`` follows the same rule, for the "none" composition mode a
131
+ design uses when its models do not need thermodynamics. The key must be
132
+ absent rather than empty: an empty mapping reads downstream as a
133
+ composition that happens to have no components, which is a different claim.
134
+ The envelope schema does not require the key.
129
135
  """
130
136
  design_space = deepcopy(dict(variables)) if variables is not None else {}
131
137
  design_space.setdefault("sampling_input", {"type": "grid"})
@@ -133,7 +139,9 @@ def build_design_document(
133
139
  design_space.setdefault("model_variables", [])
134
140
  design_space.setdefault("dataset_variables", [])
135
141
  design_space.setdefault("series_dataset_variables", [])
136
- document: dict[str, Any] = {"composition": deepcopy(dict(composition))}
142
+ document: dict[str, Any] = {}
143
+ if composition:
144
+ document["composition"] = deepcopy(dict(composition))
137
145
  if databases:
138
146
  document["databases"] = deepcopy(list(databases))
139
147
  if engine:
icmd/cli/__init__.py CHANGED
@@ -17,6 +17,7 @@ from icmd.core.exceptions import (
17
17
  ICMDConnectionError,
18
18
  ICMDRequestError,
19
19
  ICMDValidationError,
20
+ ICMDVersionError,
20
21
  )
21
22
  from icmd.core.session import normalize_domain
22
23
  from icmd.version import __version__
@@ -54,6 +55,7 @@ class _ICMDGroup(click.Group):
54
55
  ICMDValidationError,
55
56
  ICMDConnectionError,
56
57
  ICMDRequestError,
58
+ ICMDVersionError,
57
59
  ) as exc:
58
60
  name = type(exc).__name__.removeprefix("ICMD")
59
61
  # A throttled refresh names its own wait; publish it as a field so
icmd/client.py CHANGED
@@ -8,6 +8,7 @@ owner-group logic.
8
8
  from __future__ import annotations
9
9
 
10
10
  from typing import TYPE_CHECKING, Any
11
+ from urllib.parse import urlsplit
11
12
 
12
13
  from .apps import AccountApp, AgentApp, CommentsApp, StudioApp, SuiteApp, WorkspaceApp
13
14
  from .apps.base import _expect_dict
@@ -24,13 +25,66 @@ from .core.auth import (
24
25
  detect_auth_method,
25
26
  display_banner,
26
27
  )
27
- from .core.constants import PUBLIC_CONFIG
28
+ from .core.constants import MIN_ICMD_VERSION, PUBLIC_CONFIG
28
29
  from .core.contracts import ContractClient
29
30
  from .core.credentials import CredentialManager
30
- from .core.exceptions import ICMDRefreshTokenInvalidError, ICMDRequestError
31
+ from .core.exceptions import ICMDRefreshTokenInvalidError, ICMDRequestError, ICMDVersionError
31
32
  from .core.groups import GroupManager
32
33
  from .core.session import Session, normalize_domain
33
34
  from .links import link_for
35
+ from .version import __version__
36
+
37
+
38
+ def _version_key(value: object) -> tuple[int, ...] | None:
39
+ """Return *value* as comparable integers, or ``None`` when it is not a release number.
40
+
41
+ Trailing zeros are dropped so ``20.61.0`` and ``20.61.0.0`` compare equal.
42
+ ``None`` (empty, a dev build, garbage) makes the caller skip that check.
43
+ """
44
+ if not isinstance(value, str):
45
+ return None
46
+ parts = value.strip().split(".")
47
+ if not all(part.isdecimal() for part in parts):
48
+ return None
49
+ key = [int(part) for part in parts]
50
+ while key and key[-1] == 0:
51
+ key.pop()
52
+ return tuple(key)
53
+
54
+
55
+ def _check_compatibility(session: Session) -> None:
56
+ """Raise :class:`ICMDVersionError` when either side's minimum rules the other out.
57
+
58
+ The config is cached on the session, so the login banner reuses this fetch;
59
+ a failed one reads as ``{}`` and leaves connectivity to the login.
60
+ """
61
+ config = session.get_server_config()
62
+ domain = urlsplit(session.domain).netloc
63
+ site_version = config.get("ICMD_VERSION") or ""
64
+ paired = config.get("ICMD_PYTHON_VERSION") or ""
65
+ # Unauthenticated input shown as a copy-paste command: only a release number qualifies.
66
+ install = (
67
+ f'\nInstall the version released with it: pip install "icmd-python=={paired}"'
68
+ if _version_key(paired) is not None
69
+ else ""
70
+ )
71
+ installed = _version_key(__version__)
72
+ site = _version_key(site_version)
73
+
74
+ sdk_minimum = config.get("ICMD_PYTHON_MINIMUM")
75
+ minimum = _version_key(sdk_minimum)
76
+ if installed is not None and minimum is not None and installed < minimum:
77
+ runs = f"{domain} runs ICMD® {site_version}" if site is not None else domain
78
+ raise ICMDVersionError(
79
+ f"{runs}, which needs icmd-python {sdk_minimum} or newer; "
80
+ f"you have {__version__}.{install}"
81
+ )
82
+
83
+ if site is not None and site < (_version_key(MIN_ICMD_VERSION) or ()):
84
+ raise ICMDVersionError(
85
+ f"{domain} runs ICMD® {site_version}; icmd-python {__version__} needs "
86
+ f"ICMD® {MIN_ICMD_VERSION} or newer.{install}"
87
+ )
34
88
 
35
89
 
36
90
  class ICMD:
@@ -75,6 +129,10 @@ class ICMD:
75
129
  protocol.
76
130
  ICMDValidationError
77
131
  If *domain* is invalid.
132
+ ICMDVersionError
133
+ If the site serves a minimum SDK version above this one, or runs an
134
+ ICMD® release older than this SDK supports. Raised before any
135
+ login prompt; a site that serves no version is not checked.
78
136
  ICMDTokenRefreshError
79
137
  If a cached session could not be renewed for a transient reason —
80
138
  a throttled (:class:`ICMDRefreshRateLimitedError`), unreachable, or
@@ -101,6 +159,7 @@ class ICMD:
101
159
  self._authenticator = (
102
160
  NotebookAuthenticator() if _in_jupyter() else TerminalAuthenticator()
103
161
  )
162
+ _check_compatibility(self._session)
104
163
 
105
164
  if developer_token:
106
165
  self.auth_method: str = "DEVELOPER_TOKEN"
icmd/compose.py CHANGED
@@ -62,7 +62,7 @@ def _assemble_tuple(model: ModelInput) -> dict[str, Any]:
62
62
 
63
63
 
64
64
  def compose_input_dict(
65
- composition: Mapping[str, Any] | Composition,
65
+ composition: Mapping[str, Any] | Composition | None,
66
66
  models: Iterable[ModelInput | ModelNode],
67
67
  variables: Mapping[str, Any] | Sequence[DesignVariable] | DesignSpace | None = None,
68
68
  *,
@@ -110,9 +110,15 @@ def compose_input_dict(
110
110
 
111
111
  Parameters
112
112
  ----------
113
- composition : Mapping[str, Any] | Composition
113
+ composition : Mapping[str, Any] | Composition | None
114
114
  The design composition, as a wire mapping or a
115
- :class:`~icmd.types.Composition`.
115
+ :class:`~icmd.types.Composition`. ``None`` or an empty mapping emits no
116
+ ``composition`` key, for a design whose models do not use
117
+ thermodynamics -- the "none" composition mode, which the Fatigue studio
118
+ defaults to. It stays a *required* positional argument, so spell that
119
+ case ``compose_input_dict(None, models)``; giving it a default would
120
+ mean defaulting ``models`` as well, and reordering the parameters would
121
+ break every positional caller.
116
122
  models : Iterable[ModelInput | ModelNode]
117
123
  The workflow's model instances, in either spelling described above.
118
124
  variables : Mapping[str, Any] | Sequence[DesignVariable] | DesignSpace | None
icmd/core/advisory.py CHANGED
@@ -84,13 +84,17 @@ class ContractProvider(Protocol):
84
84
  """Return the ACL-filtered contract for *model_uuid*."""
85
85
  ...
86
86
 
87
+ def envelope(self) -> dict[str, Any]:
88
+ """Return the job-envelope JSON Schema the backend validates submissions against."""
89
+ ...
90
+
87
91
 
88
92
  class _ModelInput(TypedDict):
89
93
  """An assembled model node narrowed for advisory checking.
90
94
 
91
95
  ``name`` is the node's ``qt_model.key``, or its name when it carries no
92
96
  key. ``uuid`` is ``None`` for a node that does not carry its catalog uuid.
93
- Such a node still gets the envelope half of the check -- which reads the
97
+ Such a node still gets the globals half of the check -- which reads the
94
98
  document alone -- and forces the contract half to report itself skipped,
95
99
  because there is no uuid to fetch a contract with.
96
100
  """
@@ -315,19 +319,20 @@ def _validator_for(schema_key: str) -> Any:
315
319
  return _construct_validator(schema_key)
316
320
 
317
321
 
322
+ def _schema_validator(schema: dict[str, Any]) -> Any:
323
+ """Return the cached validator for *schema*, without re-checking its meta-schema."""
324
+ return _validator_for(
325
+ json.dumps(schema, ensure_ascii=False, separators=(",", ":"), sort_keys=True)
326
+ )
327
+
328
+
318
329
  def _validation_errors(
319
330
  schema: dict[str, Any],
320
331
  model_input: dict[str, Any],
321
332
  location: tuple[str | int, ...],
322
333
  ) -> list[Any]:
323
334
  """Validate one model input without repeatedly checking its meta-schema."""
324
- schema_key = json.dumps(
325
- schema,
326
- ensure_ascii=False,
327
- separators=(",", ":"),
328
- sort_keys=True,
329
- )
330
- validator = _validator_for(schema_key)
335
+ validator = _schema_validator(schema)
331
336
  return sorted(
332
337
  validator.iter_errors(model_input),
333
338
  key=lambda error: _json_path((*location, *error.absolute_path)),
@@ -356,6 +361,92 @@ def _skipped(reason: str) -> list[AdvisoryFinding]:
356
361
  return [_finding("", None, "$", f"check skipped: {reason}", "skipped")]
357
362
 
358
363
 
364
+ def _named_at_submit(error: Any) -> bool:
365
+ """Whether *error* is the missing name of a key-composed model node.
366
+
367
+ The backend names a catalog node from its key at submit binding, before
368
+ its envelope gate, so a catalog node's missing name is not a refusal. A
369
+ trained-model node composed by key without a name is suppressed too, yet
370
+ refused at submit: the backend does not name it, and telling its uuid
371
+ from a catalog one would take a lookup this check does not make.
372
+ """
373
+ return (
374
+ error.validator == "required"
375
+ and error.message == "'name' is a required property"
376
+ and bool(error.absolute_path)
377
+ and error.absolute_path[-1] == "qt_model"
378
+ and isinstance(error.instance, dict)
379
+ and bool(error.instance.get("key"))
380
+ )
381
+
382
+
383
+ def _envelope_error_findings(error: Any, definitions: dict[str, Any]) -> list[AdvisoryFinding]:
384
+ """Report one envelope error against the union branch the document selects.
385
+
386
+ Mirrors qt-designer's ``validate_envelope``: a discriminated union is read
387
+ through the branch its tag names, and ``X | None`` through ``X``, so a
388
+ sampling input without ``type`` reads as ``required`` at the sampling input
389
+ rather than "not valid under any of the given schemas".
390
+ """
391
+ location = _json_path(tuple(error.absolute_path))
392
+ branches = error.schema.get(error.validator) if error.validator in ("anyOf", "oneOf") else None
393
+ if not branches:
394
+ return [_finding("", None, location, error.message, str(error.validator))]
395
+ if "discriminator" in error.schema:
396
+ tag_name = error.schema["discriminator"]["propertyName"]
397
+ if not isinstance(error.instance, dict):
398
+ return [] # The union's own ``type: object`` reports it.
399
+ if tag_name not in error.instance:
400
+ message = f"{tag_name!r} is a required property"
401
+ return [_finding("", None, location, message, "required")]
402
+ tags = [
403
+ definitions[branch["$ref"].removeprefix("#/$defs/")]["properties"][tag_name]["const"]
404
+ for branch in branches
405
+ ]
406
+ tag = error.instance[tag_name]
407
+ if tag not in tags:
408
+ return [
409
+ _finding("", None, location, f"{tag_name} {tag!r} is not one of {tags}", "enum")
410
+ ]
411
+ selected = tags.index(tag)
412
+ else:
413
+ non_null = [index for index, branch in enumerate(branches) if branch != {"type": "null"}]
414
+ if error.instance is None or len(non_null) != 1:
415
+ return [_finding("", None, location, error.message, str(error.validator))]
416
+ selected = non_null[0]
417
+ return [
418
+ finding
419
+ for sub_error in error.context or []
420
+ if sub_error.relative_schema_path[0] == selected
421
+ for finding in _envelope_error_findings(sub_error, definitions)
422
+ ]
423
+
424
+
425
+ def _envelope_findings(
426
+ input_dict: dict[str, Any],
427
+ contracts: ContractProvider,
428
+ ) -> list[AdvisoryFinding]:
429
+ """Validate the whole document against the served job envelope.
430
+
431
+ An envelope that cannot be fetched or applied is one ``skipped`` finding,
432
+ never an empty list: an unchecked envelope must not read as a valid one.
433
+ """
434
+ envelope = getattr(contracts, "envelope", None)
435
+ if envelope is None:
436
+ return _skipped("job envelope: this contract provider has no envelope()")
437
+ try:
438
+ schema = envelope()
439
+ definitions = schema.get("$defs", {})
440
+ return [
441
+ finding
442
+ for error in _schema_validator(schema).iter_errors(input_dict)
443
+ if not _named_at_submit(error)
444
+ for finding in _envelope_error_findings(error, definitions)
445
+ ]
446
+ except Exception as exc:
447
+ return _skipped(f"job envelope: {str(exc).strip() or type(exc).__name__}")
448
+
449
+
359
450
  def _canonical_global(key: str) -> str | None:
360
451
  """Name the global a ``use_global`` sitting under *key* resolves against.
361
452
 
@@ -564,30 +655,33 @@ def check_input_dict(
564
655
  input_dict: dict[str, Any],
565
656
  contracts: ContractProvider,
566
657
  ) -> list[AdvisoryFinding]:
567
- """Check assembled per-model inputs and return non-blocking findings.
568
-
569
- Two independent halves, reported in that order. The envelope check reads
570
- the document alone: a model deferring to a global the job never defines is
571
- reported here rather than failing worker-side once the job is claimed, and
572
- needs no contract to say so. Contract validation is the other half, and if
573
- any model's contract cannot be fetched it contributes one ``"check
574
- skipped"`` finding instead of its own, so partial contract output cannot be
658
+ """Check an assembled job input and return non-blocking findings.
659
+
660
+ Three independent parts, reported in this order. The job envelope comes
661
+ first: the whole document against the schema the backend serves, or one
662
+ ``"check skipped"`` finding when that schema cannot be fetched. The globals
663
+ check reads the document alone: a model deferring to a global the job never
664
+ defines is reported here rather than failing worker-side once the job is
665
+ claimed. Contract validation is last, and if any model's contract cannot be
666
+ fetched it contributes one ``"check skipped"`` finding instead of its own,
667
+ placed ahead of the globals findings, so partial contract output cannot be
575
668
  mistaken for a complete check.
576
669
 
577
- The envelope findings survive that skip -- a pre-contract or unreachable
670
+ No part's skip suppresses another's findings -- an old or unreachable
578
671
  backend is where a pre-submit warning is worth most -- so an incomplete
579
672
  check is recognized by a ``kind == "skipped"`` finding, never by an empty
580
- result. A skip puts that finding first.
673
+ result.
581
674
  """
675
+ envelope_findings = _envelope_findings(input_dict, contracts)
582
676
  try:
583
677
  models = _model_inputs(input_dict)
584
678
  global_findings = [
585
679
  finding for model in models for finding in _global_findings(model, input_dict)
586
680
  ]
587
681
  except Exception as exc:
588
- return _skipped(str(exc).strip() or type(exc).__name__)
682
+ return [*envelope_findings, *_skipped(str(exc).strip() or type(exc).__name__)]
589
683
 
590
684
  contract_findings, reason = _contract_findings(models, contracts)
591
685
  if reason is not None:
592
- return [*_skipped(reason), *global_findings]
593
- return [*global_findings, *contract_findings]
686
+ return [*envelope_findings, *_skipped(reason), *global_findings]
687
+ return [*envelope_findings, *global_findings, *contract_findings]
icmd/core/auth.py CHANGED
@@ -495,14 +495,11 @@ def enhance_auth_error(
495
495
  def display_banner(session: Session, authenticator: Authenticator) -> None:
496
496
  """Display the server config's banner message, if there is one.
497
497
 
498
- An authenticator may set ``displays_messages = False`` to opt out; that
499
- suppresses the server-config fetch as well as the display. Authenticators
500
- that do not declare the optional flag display the banner.
498
+ An authenticator may set ``displays_messages = False`` to opt out of the
499
+ display. Authenticators that do not declare the optional flag display the
500
+ banner.
501
501
  """
502
502
  try:
503
- # Skips the fetch, not merely the display: this round-trip is made
504
- # solely to render the banner, so an authenticator that discards
505
- # messages would spend a request per process on output nobody sees.
506
503
  # Defaulting to True preserves banner display for authenticators that
507
504
  # do not declare the flag. Inside the try because a declared property
508
505
  # may raise something other than AttributeError, which getattr's
icmd/core/constants.py CHANGED
@@ -61,3 +61,12 @@ compare its ``contract_sha256`` to decide whether a refresh is warranted.
61
61
 
62
62
  # Public
63
63
  PUBLIC_CONFIG = "public/config"
64
+
65
+ # Compatibility
66
+ MIN_ICMD_VERSION = "20.49.0"
67
+ """Oldest ICMD® release this SDK supports; a site below it is refused on connect.
68
+
69
+ 20.49.0 is where the backend binds model nodes by key, which is the only form
70
+ this SDK composes. Raise it in the change that drops a fallback for an older
71
+ backend.
72
+ """
icmd/core/contracts.py CHANGED
@@ -67,8 +67,9 @@ class ContractClient:
67
67
  The cache key includes the current owner group, resolved on every call
68
68
  because callers may change groups after constructing
69
69
  :class:`~icmd.client.ICMD`. Generated classes hang off the entry, so no
70
- class outlives the group and the scenario set it was licensed by. Both
71
- caches are memory-only and :meth:`clear` drops them together.
70
+ class outlives the group and the scenario set it was licensed by. All three
71
+ -- contracts, their classes and the job envelope schema (one per client,
72
+ not group-scoped) -- are memory-only and :meth:`clear` drops them together.
72
73
  """
73
74
 
74
75
  def __init__(
@@ -84,6 +85,7 @@ class ContractClient:
84
85
  self._ttl = ttl
85
86
  self._clock = clock
86
87
  self._cache: dict[tuple[str, Hashable, str], _CacheEntry] = {}
88
+ self._envelope: tuple[dict[str, Any], float] | None = None
87
89
 
88
90
  def _cache_key(self, model_uuid: str) -> tuple[str, Hashable, str]:
89
91
  """Build a key from domain, the live effective group, and model UUID."""
@@ -193,6 +195,45 @@ class ContractClient:
193
195
  """Return a model contract, re-fetching once the cached copy expires."""
194
196
  return deepcopy(self._entry(model_uuid).contract)
195
197
 
198
+ def envelope(self) -> dict[str, Any]:
199
+ """Return the job-envelope JSON Schema, re-fetching once the cached copy expires.
200
+
201
+ The schema comes from ``studio/input-dicts/schema/`` and is the one the
202
+ backend validates every submitted job document against. It is not
203
+ owner-group scoped, so one copy serves the whole client, on the same TTL
204
+ as contracts.
205
+
206
+ Raises
207
+ ------
208
+ ICMDRequestError
209
+ If the request fails or the body carries no schema object.
210
+ """
211
+ now = self._clock()
212
+ if self._envelope is not None and now - self._envelope[1] < self._ttl:
213
+ return deepcopy(self._envelope[0])
214
+ response = self._session.api_request("GET", "studio/input-dicts/schema/")
215
+ if response.status_code == HTTPStatus.NOT_FOUND:
216
+ raise ICMDRequestError(
217
+ "This ICMD® backend does not serve the job envelope schema (it predates it).",
218
+ status_code=response.status_code,
219
+ )
220
+ check_response(response, "fetch job envelope schema")
221
+ try:
222
+ body = response.json()
223
+ except ValueError as exc:
224
+ raise ICMDRequestError(
225
+ "Expected a JSON job envelope schema.",
226
+ status_code=response.status_code,
227
+ ) from exc
228
+ schema = body.get("schema") if isinstance(body, dict) else None
229
+ if not isinstance(schema, dict):
230
+ raise ICMDRequestError(
231
+ "Expected a job envelope schema object.",
232
+ status_code=response.status_code,
233
+ )
234
+ self._envelope = (schema, now)
235
+ return deepcopy(schema)
236
+
196
237
  def model_class(self, model_uuid: str, scenario: str | None = None) -> type[BaseModel]:
197
238
  """Return the pydantic input class one model scenario describes.
198
239
 
@@ -253,5 +294,6 @@ class ContractClient:
253
294
  return check_input_dict(input_dict, self)
254
295
 
255
296
  def clear(self) -> None:
256
- """Clear cached contracts and the classes built from them."""
297
+ """Clear cached contracts, the classes built from them, and the envelope schema."""
257
298
  self._cache.clear()
299
+ self._envelope = None
icmd/core/exceptions.py CHANGED
@@ -49,6 +49,15 @@ class ICMDConnectionError(Exception):
49
49
  """Raised when connection to ICMD® fails."""
50
50
 
51
51
 
52
+ class ICMDVersionError(Exception):
53
+ """Raised when this SDK and the ICMD® site it connects to cannot work together.
54
+
55
+ Either side may refuse: the site serves the oldest SDK it supports, and the
56
+ SDK declares the oldest ICMD® release it supports. The message names the
57
+ version to install when the site says which icmd-python it was released with.
58
+ """
59
+
60
+
52
61
  class ICMDValidationError(Exception):
53
62
  """Raised when input validation fails."""
54
63
 
icmd/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "3.11.0"
1
+ __version__ = "3.14.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: icmd-python
3
- Version: 3.11.0
3
+ Version: 3.14.0
4
4
  Summary: Python client app for ICMD®
5
5
  Author: QuesTek Innovations LLC
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,12 +1,12 @@
1
- icmd/__init__.py,sha256=-2i4tJ69UAb547_LPnAVrglNc1ZRRTce5Ju2BoKzaDc,3109
2
- icmd/client.py,sha256=0mHPuWCvfv-urlTRqOvjmaBwFNt09XR1nmr-4QSRk3Q,23098
3
- icmd/compose.py,sha256=-xkfVsqvNejYnaxvddlidNpzLE1tZ59Qdj8DZq1yENQ,7089
1
+ icmd/__init__.py,sha256=TME0_M-oG3U7-rrdQ4cLzcp7JvgBeP0BhjjiDOshbaY,3155
2
+ icmd/client.py,sha256=F9qwRrC1cfEFTw0FZgI7PWXX0J_B-bzv0CXs2R_R5EM,25606
3
+ icmd/compose.py,sha256=kl_-yYiVRfglpyNn782iuzg54-_55Rf3XJnkqSY_Zq0,7563
4
4
  icmd/links.py,sha256=I8v8_xhI6lbUwrMjIUcYYcqKUi2PIPm6zQ7mXYP9A5E,6827
5
5
  icmd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  icmd/types.py,sha256=af26gLTblS33vhlYdHLSPKY7Rm1shJDDOpzauCeOxAE,81323
7
- icmd/version.py,sha256=cpptu6yAKjujkKCECVRXYkw3SmUpyBiTvJvB3Mmcu5s,23
7
+ icmd/version.py,sha256=xtcy_5Xf21-Fg12NJ-BUaHz9wwW-QCIv0Ni0UXGxy0s,23
8
8
  icmd/_vendor/__init__.py,sha256=VFA4EQd2l76sxgkHPUpgzZavFF6kFdpq9v71AZsPIdQ,579
9
- icmd/_vendor/assembler.py,sha256=2KpvpIARfcM2H1mE3ajKT6ukaEnE5cKztSySpsSq8ZA,5776
9
+ icmd/_vendor/assembler.py,sha256=3mOONZyT_J2WOVs06xofoxv0nSjBgSfPFV3nj-sUZGw,6176
10
10
  icmd/apps/__init__.py,sha256=Bg6rR2Zt38OHdBBwGKBzSqbsDW8chVM9KwjZ4IGODXE,712
11
11
  icmd/apps/account.py,sha256=BWDO0Zysv0ygLVhbeIk82BjsvuDVb_vBdu6jqBPw3tc,14557
12
12
  icmd/apps/agent.py,sha256=EasQ0ZHbBbqXauAcoSym-eK2TOFDbdd2XGJPnKYBx6k,8467
@@ -23,7 +23,7 @@ icmd/apps/suite.py,sha256=1XT8aKPn15W-bwBdOz-JV3Wrmr_JOKyrMEC6--ljj9A,3907
23
23
  icmd/apps/transfer.py,sha256=UVVEOmYynLzRtdTO6dbUBm6om6oHgmnspuKjACp0arE,36736
24
24
  icmd/apps/types.py,sha256=jOzykkXWj9e_lk805_ABCC-sWBwIpJGN3HEb2AyQ9vw,1290
25
25
  icmd/apps/workspace.py,sha256=BXCOLQo5BMu-okW2Ink8L_FPhU8r7OWy2jDsvilU3Oo,3055
26
- icmd/cli/__init__.py,sha256=mBpAlW1BCKglPzdDffTc4uLdr_2Ri7qnVC2I-Q9FjEA,7395
26
+ icmd/cli/__init__.py,sha256=KiXdv_n5SOPqBxtTFQSnfqMf-T1ScxuOrbdkB_GUHp8,7447
27
27
  icmd/cli/account.py,sha256=equTAGWR3NCCMYEPTw4IZKHEtBEdGD8F4Pp8cfiSNoU,13859
28
28
  icmd/cli/auth.py,sha256=UFpEf-lLlkbphivgBfd9NxAEqHcCsPmEl-DXet-NStg,3317
29
29
  icmd/cli/auth_status.py,sha256=6wkI3Q0c-t8DBOfuPaN_xXEUcL9HLZm1jX4lQ76X3Ag,13505
@@ -35,19 +35,19 @@ icmd/cli/links.py,sha256=7CMhKUJ-qKQmk2I-KCdn6oF06cdCq0v5jQDGU7gEJ30,14039
35
35
  icmd/cli/output.py,sha256=-TKZ8fJYCUhh7qHRNAZyFYFaXcMDqm4NDR2qFfErg5A,1190
36
36
  icmd/cli/registry.py,sha256=MEkqcu2b6ZjfmE2L2L062PxTjOTjlVEyYSycTv_tq30,17577
37
37
  icmd/core/__init__.py,sha256=45q0vlUao61rG6ugufGTRtoDeBTDcscU5dLAMluDSBA,225
38
- icmd/core/advisory.py,sha256=9zB2Ew5RKtwwDKTYF3nPYSuv5SnHCEthriYfTr6Tzko,22707
39
- icmd/core/auth.py,sha256=bG6eLbSkmyDWLyprx7NJMDwLTGsOIrsRdBZ3f6vz1vk,29480
40
- icmd/core/constants.py,sha256=P3a01sDt5WmGYqkp5hu4UqWbMgfp50o6rBV3q9jU4yA,2339
41
- icmd/core/contracts.py,sha256=uHybwSxBHV4l6M4ho4ECRKitXm4lMbBroFhGxim0V0I,10690
38
+ icmd/core/advisory.py,sha256=gQqaPUfSNhUQFG61SPqaZQhd_7AfzPgw8lNXhwulZhc,26994
39
+ icmd/core/auth.py,sha256=WRY2BStFYQqSnk__oEQTrAvbvVCHQdm_G2YYOcHL_jY,29207
40
+ icmd/core/constants.py,sha256=sKSy5oRqUKztlesyjirMEGrA9FmR4Kz-uPrDE8rv33A,2638
41
+ icmd/core/contracts.py,sha256=QXMpxiPBltaXFfGCJx4Y2d7tkRyjWAIBragSg_NucXE,12575
42
42
  icmd/core/credentials.py,sha256=39vyoFH1_MrzD9TpTh4FVPhWbrY5X35zGCS22KeTi_8,46835
43
43
  icmd/core/display.py,sha256=RkgJhr2Bfw3-ZjGvE100ddD6oyAAUUAJfBAy6DvX5ik,13060
44
- icmd/core/exceptions.py,sha256=Lv9zPMqZqZW8O-QqZ8AThF2u9xcGv-yfHWk1Vl6FKeA,12663
44
+ icmd/core/exceptions.py,sha256=c88w5neJU7ohm5iEboA29jFaUfZ8EfrxmiO9rornTTw,13035
45
45
  icmd/core/groups.py,sha256=BEwpBEe60l_8HpnsIvLPC3EhLSXnv-qyo-OZIyP0sWI,7585
46
46
  icmd/core/model_classes.py,sha256=uuhJwh_AZ69eOp8SoWYo0MM5rNwA_a-vkxhrIwFasds,40372
47
47
  icmd/core/session.py,sha256=I3X0z2ZZm9H68NLnOOs4vS6sn4THWLNjvgUcBDJB5i8,60902
48
- icmd_python-3.11.0.dist-info/licenses/LICENSE,sha256=644lGvCmDFa5hQl6xdhaYk3o-1oWqPXAIiiQlvqeZdE,1028
49
- icmd_python-3.11.0.dist-info/METADATA,sha256=47PYNTfR1-9e39BnnlAHFJQSOOfoWcQy6r2OHGcs59c,5358
50
- icmd_python-3.11.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
51
- icmd_python-3.11.0.dist-info/entry_points.txt,sha256=CFX537PlyyPQL3Q_3TKUFeiiDSkOAboqaLEy9AIEwVU,39
52
- icmd_python-3.11.0.dist-info/top_level.txt,sha256=olzM6Z_QEwg7RrFVOnkgO0I2Ps2m7vhSqYQZYeiawcw,5
53
- icmd_python-3.11.0.dist-info/RECORD,,
48
+ icmd_python-3.14.0.dist-info/licenses/LICENSE,sha256=644lGvCmDFa5hQl6xdhaYk3o-1oWqPXAIiiQlvqeZdE,1028
49
+ icmd_python-3.14.0.dist-info/METADATA,sha256=-ZmtyDRQTcleRU64XI03xSfBVISxKNI7D6BSQkeks9Q,5358
50
+ icmd_python-3.14.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
51
+ icmd_python-3.14.0.dist-info/entry_points.txt,sha256=CFX537PlyyPQL3Q_3TKUFeiiDSkOAboqaLEy9AIEwVU,39
52
+ icmd_python-3.14.0.dist-info/top_level.txt,sha256=olzM6Z_QEwg7RrFVOnkgO0I2Ps2m7vhSqYQZYeiawcw,5
53
+ icmd_python-3.14.0.dist-info/RECORD,,