icmd-python 3.3.0__py3-none-any.whl → 3.3.4__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/apps/jobs.py CHANGED
@@ -29,7 +29,7 @@ if TYPE_CHECKING:
29
29
  from .types import JobLogResponse
30
30
 
31
31
 
32
- _TERMINAL_STATUSES = frozenset({"successful", "failed", "partial", "cancelled"})
32
+ _TERMINAL_STATUSES = frozenset({"successful", "failed", "partial", "cancelled", "aborted"})
33
33
 
34
34
  #: Consecutive transient failures :meth:`JobResource.poll` absorbs before
35
35
  #: giving up. A training job outlives the 15-minute access token several
@@ -223,11 +223,12 @@ class JobResource(ReadOnlyResource):
223
223
  The design-level CALPHAD engine, written to
224
224
  ``input_dict["engine"]`` — qt-designer reads it from there and
225
225
  substitutes it into every model input spelled ``use_global``.
226
- One of ``"thermocalc"``, ``"pandat"``, ``"panengine"``,
227
- ``"pycalphad"``, ``"prisms"``; anything else is rejected by the
228
- backend with ``400 invalid_engine``. Optional when
226
+ One of ``"thermocalc"``, ``"pandat"``, ``"pycalphad"``; anything
227
+ else is rejected by the backend with ``400 invalid_engine``. Optional when
229
228
  *input_dict* already carries an ``engine``; supplying both is an
230
- error unless they agree, and supplying neither is an error too.
229
+ error unless they agree. Omit both when no model defers to the
230
+ job-level engine; if one does, the backend refuses the job with
231
+ ``400 missing_engine``.
231
232
  folder : str
232
233
  UUID of the job folder this job belongs to.
233
234
  name : str | None
@@ -265,7 +266,7 @@ class JobResource(ReadOnlyResource):
265
266
  ------
266
267
  ICMDValidationError
267
268
  If *engine* and a non-empty ``input_dict["engine"]`` disagree, if
268
- neither supplies an engine, if ``input_dict["engine"]`` holds
269
+ ``input_dict["engine"]`` holds
269
270
  something other than a string, or if a model named by name alone
270
271
  matches more than one catalog model.
271
272
  """
@@ -285,12 +286,10 @@ class JobResource(ReadOnlyResource):
285
286
  "Pass one or the other, or make them agree."
286
287
  )
287
288
  resolved = engine or document_engine
288
- if resolved is None:
289
- raise ICMDValidationError(
290
- "No engine given: pass engine= or set input_dict['engine'] "
291
- "(thermocalc, pandat, panengine, pycalphad, prisms)."
292
- )
293
- inputs["engine"] = resolved
289
+ if resolved is not None:
290
+ inputs["engine"] = resolved
291
+ else:
292
+ inputs.pop("engine", None)
294
293
  # Before the advisory, which needs a uuid per model to fetch a contract,
295
294
  # and regardless of ``check`` -- completing the reference is what makes
296
295
  # the document submittable, not a check on it.
@@ -432,9 +431,10 @@ class JobResource(ReadOnlyResource):
432
431
  Returns
433
432
  -------
434
433
  str | None
435
- The terminal status reached -- ``"failed"`` and ``"cancelled"``
436
- are terminal too, so a caller that needs the job to have *worked*
437
- must check this. ``None`` only when polling was interrupted.
434
+ The terminal status reached -- ``"failed"``, ``"partial"``,
435
+ ``"cancelled"`` and ``"aborted"`` are terminal too, so a caller that
436
+ needs the job to have *worked* must check this. ``None`` only when
437
+ polling was interrupted.
438
438
 
439
439
  Raises
440
440
  ------
@@ -646,7 +646,7 @@ class JobResource(ReadOnlyResource):
646
646
  # -----------------------------------------------------------------
647
647
 
648
648
  def resubmit(self, uuid: str) -> dict:
649
- """Re-run a completed/failed/cancelled job as-is (``POST {uuid}/resubmit``).
649
+ """Re-run a successful/failed/partial/cancelled/aborted job (``POST {uuid}/resubmit``).
650
650
 
651
651
  Takes no inputs, because the endpoint accepts none: it declares no request
652
652
  body and re-resolves the job's *stored* ``input_dict`` against the owning
icmd/cli/commands.py CHANGED
@@ -1148,8 +1148,8 @@ def _make_submit_cmd(path: str) -> click.Command:
1148
1148
  default=None,
1149
1149
  help=(
1150
1150
  "Design-level CALPHAD engine written to input_dict['engine'] "
1151
- "(thermocalc, pandat, panengine, pycalphad, prisms). Omit when "
1152
- "the input_dict already carries one."
1151
+ "(thermocalc, pandat, pycalphad). Omit when the input_dict "
1152
+ "already carries one or no model defers to the job-level engine."
1153
1153
  ),
1154
1154
  )
1155
1155
  @click.option("--folder", required=True, help="UUID of the destination job folder.")
icmd/compose.py CHANGED
@@ -93,11 +93,11 @@ def compose_input_dict(
93
93
 
94
94
  ``databases`` and ``engine`` are the design-level values, written at the top
95
95
  of the document rather than on any one model: a sequence of wire mappings or
96
- :class:`~icmd.types.Database` objects, and one of the catalog engine names
97
- (``thermocalc``, ``pandat``, ``panengine``, ``pycalphad``, ``prisms``). They
98
- are what a model input spelled ``use_global`` resolves against. Omitting
99
- either emits no key for it, which is what leaves the value unset -- an empty
100
- list or an empty string reads downstream as no value at all.
96
+ :class:`~icmd.types.Database` objects, and one of the CALPHAD engine names
97
+ ``thermocalc``, ``pandat``, ``pycalphad``. They are what a model input
98
+ spelled ``use_global`` resolves against. Omitting either emits no key for
99
+ it, which is what leaves the value unset -- an empty list or an empty string
100
+ reads downstream as no value at all.
101
101
 
102
102
  :class:`~icmd.types.FromVariable` and :class:`~icmd.types.FromModel` hold
103
103
  the object they reference, and resolve here to the variable's name and the
icmd/core/session.py CHANGED
@@ -622,6 +622,16 @@ class BearerAuth(AuthBase):
622
622
  return r
623
623
 
624
624
 
625
+ def _no_auth(r: requests.PreparedRequest) -> requests.PreparedRequest:
626
+ """Send one request without the session's bearer.
627
+
628
+ Passed as a per-request ``auth=``, which displaces ``Session.auth``;
629
+ ``auth=None`` would not, since ``requests`` falls back to the session's.
630
+ """
631
+ r.headers.pop("Authorization", None)
632
+ return r
633
+
634
+
625
635
  # ---------------------------------------------------------------------------
626
636
  # Session
627
637
  # ---------------------------------------------------------------------------
@@ -716,6 +726,11 @@ class Session:
716
726
  PUBLIC_CONFIG,
717
727
  }
718
728
 
729
+ #: ``AllowAny`` endpoints that never carry the bearer: DRF rejects a stale one
730
+ #: before the view runs. The refresh POST opts out itself in
731
+ #: :meth:`refresh_access_token`; everything else, logout included, keeps it.
732
+ _BEARERLESS_ENDPOINTS: ClassVar[frozenset[str]] = frozenset({LOGIN, PUBLIC_CONFIG})
733
+
719
734
  def __init__(self, domain: str) -> None:
720
735
  """Initialise the session with a validated domain.
721
736
 
@@ -910,6 +925,7 @@ class Session:
910
925
  f"{self.api_root}/{AUTH_REFRESH}/",
911
926
  json={"refresh_token": self._refresh_token},
912
927
  timeout=self._REFRESH_TIMEOUT,
928
+ auth=_no_auth,
913
929
  )
914
930
  except requests.RequestException as exc:
915
931
  # Nothing came back, so nothing implicates the stored token.
@@ -1026,6 +1042,9 @@ class Session:
1026
1042
 
1027
1043
  if "timeout" not in kwargs:
1028
1044
  kwargs["timeout"] = self._timeout
1045
+ if endpoint in self._BEARERLESS_ENDPOINTS:
1046
+ # A default, so a caller's own ``auth=`` still wins.
1047
+ kwargs.setdefault("auth", _no_auth)
1029
1048
 
1030
1049
  renewal_failure = self._renew_expired_token(endpoint)
1031
1050
 
icmd/types.py CHANGED
@@ -22,6 +22,7 @@ import csv
22
22
  import math
23
23
  from collections.abc import Mapping, Sequence
24
24
  from copy import deepcopy
25
+ from decimal import Decimal
25
26
  from io import StringIO
26
27
  from typing import TYPE_CHECKING, Any, Protocol, Self, runtime_checkable
27
28
 
@@ -458,6 +459,9 @@ class DatasetCompositionalVariable(_Envelope):
458
459
  per element -- nothing downstream reads a dataset reference on the
459
460
  composition itself. :meth:`DatasetSource.composition` mints the composition
460
461
  and its variables together, which is the only way they stay consistent.
462
+ Constructing one here normalizes nothing and qt-designer samples on the
463
+ composition's own unit rather than the one labelled here, so fraction values
464
+ are unsupported: express them in percent through that method instead.
461
465
 
462
466
  The component is keyed ``name`` on the wire: that is the key the sampler
463
467
  matches against the composition's components.
@@ -550,6 +554,49 @@ _BALANCE = "bal"
550
554
  #: 9958180, lines 106-109).
551
555
  _PERCENT_UNITS = frozenset({"mass_percent", "mole_percent"})
552
556
 
557
+ #: The unit a fraction-labelled dataset's cells are in once expressed in
558
+ #: percent. A dataset may be labelled in fractions and a job may not, so these
559
+ #: are read off the dataset rather than added to :data:`_PERCENT_UNITS`.
560
+ _FRACTION_TO_PERCENT = {"mass_fraction": "mass_percent", "mole_fraction": "mole_percent"}
561
+
562
+
563
+ class _CompositionUnitUnset:
564
+ """The type of :data:`_COMPOSITION_UNIT_UNSET`, so it renders as prose.
565
+
566
+ ``inspect.signature`` renders a default by ``repr``, so a bare ``object()``
567
+ puts a memory address in :meth:`DatasetSource.composition`'s signature --
568
+ in ``help()`` and in every IDE tooltip on a public method.
569
+ """
570
+
571
+ def __repr__(self) -> str:
572
+ """Return what the signature shows where no unit was passed."""
573
+ return "<from the dataset>"
574
+
575
+
576
+ #: :meth:`DatasetSource.composition`'s "not given" default, and nothing else's.
577
+ #: Not ``None`` (:data:`_UNSET`): a plain parameter keeps no record of what the
578
+ #: caller passed, the way a Pydantic field's ``model_fields_set`` does, so
579
+ #: ``composition_unit=None`` is a value passed -- refused rather than resolved
580
+ #: from the dataset.
581
+ _COMPOSITION_UNIT_UNSET: Any = _CompositionUnitUnset()
582
+
583
+
584
+ def _as_percent(values: list[_Number | str]) -> list[_Number | str]:
585
+ """Return one composition column's fraction cells in percent, exactly.
586
+
587
+ ``Decimal(str(value)) * 100`` rather than ``value * 100``: the web app
588
+ multiplies in BigNumber, so ``0.07`` becomes ``7`` there and must not become
589
+ ``7.000000000000001`` here. Neither side scales the decimal the CSV cell was
590
+ written with -- both round it through a binary float first (``Number(value)``
591
+ there, :func:`_composition_values` here, and :func:`_as_numbers` before it
592
+ where the whole column parses) -- so what this buys is that the two agree,
593
+ cell for cell. ``bal`` markers are the strings here, and pass through
594
+ untouched.
595
+ """
596
+ return [
597
+ value if isinstance(value, str) else float(Decimal(str(value)) * 100) for value in values
598
+ ]
599
+
553
600
 
554
601
  def _composition_values(column: str, values: list[_Number | str]) -> list[_Number | str]:
555
602
  """Return one composition column as numbers, keeping its ``bal`` markers.
@@ -564,15 +611,75 @@ def _composition_values(column: str, values: list[_Number | str]) -> list[_Numbe
564
611
  coerced.append(_BALANCE)
565
612
  continue
566
613
  try:
567
- coerced.append(float(value))
614
+ number = float(value)
568
615
  except ValueError:
569
616
  raise ValueError(
570
617
  f"Composition column {column!r} holds {value!r}, which is neither a "
571
618
  f"number nor the {_BALANCE!r} balance marker."
572
619
  ) from None
620
+ # ``float`` reads ``nan`` and ``inf`` and ``Decimal`` scales them, so
621
+ # unguarded the balance element's remainder of 100 comes out ``nan`` and
622
+ # the cell reaches the wire as JSON no strict parser accepts.
623
+ if not math.isfinite(number):
624
+ raise ValueError(
625
+ f"Composition column {column!r} holds {value!r}, which is not a finite "
626
+ "number. Drop the row, or fill the cell in."
627
+ )
628
+ coerced.append(number)
573
629
  return coerced
574
630
 
575
631
 
632
+ def _resolve_composition_unit(
633
+ dataset_name: str, declared_unit: str | None, passed_unit: str
634
+ ) -> tuple[str, str | None]:
635
+ """Return the percent unit a composition carries, and the one its cells are in.
636
+
637
+ The second is the percent unit a fraction-labelled dataset's cells reach
638
+ once scaled, and ``None`` for every other label, including none -- which is
639
+ what tells the caller whether to scale them at all.
640
+
641
+ ``passed_unit`` left at :data:`_COMPOSITION_UNIT_UNSET` resolves from the
642
+ dataset: a fraction label through its matching percent unit, a percent label
643
+ as it stands, ``mass_percent`` only where the dataset declares nothing at
644
+ all. A label this SDK cannot read is refused with the dataset blamed -- an
645
+ empty one included, since a dataset carrying an empty label has declared a
646
+ value -- where a unit passed is refused with the caller blamed.
647
+ """
648
+ scaled_unit = _FRACTION_TO_PERCENT.get(declared_unit or "")
649
+ from_dataset = passed_unit is _COMPOSITION_UNIT_UNSET
650
+ composition_unit = passed_unit
651
+ if from_dataset:
652
+ # A fraction label resolves through its scaled percent unit; every
653
+ # other label stands and is checked below, as a passed one is.
654
+ declared = scaled_unit or declared_unit
655
+ composition_unit = "mass_percent" if declared is None else declared
656
+ if composition_unit not in _PERCENT_UNITS:
657
+ if from_dataset:
658
+ raise ValueError(
659
+ f"Dataset {dataset_name!r} declares composition_unit "
660
+ f"{declared_unit!r}, which is neither one of "
661
+ f"{sorted(_PERCENT_UNITS)} nor a fraction of one, so its cells cannot "
662
+ "be read. Pass the unit they are in: "
663
+ "composition(name, composition_unit=...)."
664
+ )
665
+ raise ValueError(
666
+ f"composition_unit must be one of {sorted(_PERCENT_UNITS)}; got "
667
+ f"{composition_unit!r}. The balance element is filled in as what is left "
668
+ "of 100, which is a wrong number in any other unit."
669
+ )
670
+ # Both units are now one of the two in ``_PERCENT_UNITS``, so they differ
671
+ # only in basis. Scaling the cells cannot change what they measure, so a
672
+ # caller naming the other basis has the dataset wrong.
673
+ if scaled_unit is not None and composition_unit != scaled_unit:
674
+ raise ValueError(
675
+ f"Dataset {dataset_name!r} is labelled {declared_unit!r}, so its "
676
+ f"cells are {scaled_unit!r} once expressed in percent; "
677
+ f"composition_unit={composition_unit!r} is the other basis. Pass "
678
+ f"{scaled_unit!r}, or leave composition_unit unset."
679
+ )
680
+ return composition_unit, scaled_unit
681
+
682
+
576
683
  class DatasetSource:
577
684
  """One uploaded dataset, read once, minting variables from its columns.
578
685
 
@@ -598,6 +705,13 @@ class DatasetSource:
598
705
  Its name, carried onto every variable minted here.
599
706
  columns : Mapping[str, list[int | float | str]]
600
707
  The parsed columns, by column name.
708
+ composition_unit : str | None
709
+ The unit the dataset itself declares its composition columns are in, as
710
+ :meth:`load` reads it off the dataset's metadata. :meth:`composition`
711
+ scales fraction cells on the strength of it, and resolves the
712
+ composition's own unit from it when none is passed. Omit it on a
713
+ hand-built source, which then behaves as a dataset carrying no unit
714
+ does.
601
715
  """
602
716
 
603
717
  def __init__(
@@ -605,16 +719,29 @@ class DatasetSource:
605
719
  dataset_uuid: str,
606
720
  dataset_name: str,
607
721
  columns: Mapping[str, list[_Number | str]],
722
+ *,
723
+ composition_unit: str | None = None,
608
724
  ) -> None:
609
725
  self.uuid = dataset_uuid
610
726
  self.name = dataset_name
611
727
  self._columns = dict(columns)
728
+ self._composition_unit = composition_unit
612
729
 
613
730
  @classmethod
614
731
  def load(cls, client: "ICMD", dataset_uuid: str) -> Self:
615
732
  """Fetch one dataset and return the source minting its variables."""
616
733
  body = client.workspace.dataset.get(dataset_uuid, inject_group=True)
617
- return cls(dataset_uuid, body["name"], _parse_columns(body["csv_data"]))
734
+ # This server always sends ``metadata`` as a dict; the inner ``dataset``
735
+ # block is the one that is really null, on a dataset saved without a
736
+ # unit. The outer guard is insurance for a tenant on an older backend.
737
+ metadata = body.get("metadata") or {}
738
+ declared_unit = (metadata.get("dataset") or {}).get("composition_unit")
739
+ return cls(
740
+ dataset_uuid,
741
+ body["name"],
742
+ _parse_columns(body["csv_data"]),
743
+ composition_unit=declared_unit,
744
+ )
618
745
 
619
746
  def _column(self, column: str) -> list[_Number | str]:
620
747
  """Return one column's values, naming the ones there are when it is absent."""
@@ -641,7 +768,7 @@ class DatasetSource:
641
768
  self,
642
769
  name: str,
643
770
  *,
644
- composition_unit: str = "mass_percent",
771
+ composition_unit: str = _COMPOSITION_UNIT_UNSET,
645
772
  columns: Sequence[str] | None = None,
646
773
  ) -> tuple["Composition", list["DatasetCompositionalVariable"]]:
647
774
  """Resolve this dataset's compositions into the pair the wire accepts.
@@ -653,16 +780,29 @@ class DatasetSource:
653
780
  app does too: a composition that only references a dataset is read by
654
781
  nothing, and the job runs with no composition columns at all.
655
782
 
783
+ The dataset's declared label decides the **cells**: a ``mass_fraction``
784
+ or ``mole_fraction`` label has every numeric cell scaled by 100, since
785
+ no fraction unit reaches the wire at all; a percent label leaves them as
786
+ they stand. ``composition_unit`` decides the **label** the composition
787
+ carries and nothing else -- passing one does not turn that scaling off.
788
+ Left unset it follows the dataset as well: a fraction label's matching
789
+ percent unit, a percent label as it stands, ``mass_percent`` for a
790
+ dataset declaring nothing, and an error for a label this SDK does not
791
+ know, which passing a unit overrides.
792
+
793
+ That unit is cross-checked only where this SDK converts: against a
794
+ fraction-labelled dataset the other basis is refused, since scaling
795
+ cannot change what the cells measure. A percent-labelled dataset takes
796
+ whatever is passed -- the label is free-form metadata the caller may be
797
+ correcting.
798
+
656
799
  ``columns`` defaults to every column named after an element. Each row
657
800
  must mark exactly one balance element with ``bal``, which is what the
658
801
  uploaded composition dataset carries.
659
802
  """
660
- if composition_unit not in _PERCENT_UNITS:
661
- raise ValueError(
662
- f"composition_unit must be one of {sorted(_PERCENT_UNITS)}; got "
663
- f"{composition_unit!r}. The balance element is filled in as what is left "
664
- "of 100, which is a wrong number in any other unit."
665
- )
803
+ composition_unit, scaled_unit = _resolve_composition_unit(
804
+ self.name, self._composition_unit, composition_unit
805
+ )
666
806
  selected = (
667
807
  list(columns)
668
808
  if columns is not None
@@ -677,6 +817,10 @@ class DatasetSource:
677
817
  composition_columns = {
678
818
  column: _composition_values(column, self._column(column)) for column in selected
679
819
  }
820
+ if scaled_unit is not None:
821
+ composition_columns = {
822
+ column: _as_percent(values) for column, values in composition_columns.items()
823
+ }
680
824
  if not all(composition_columns.values()):
681
825
  raise ValueError(
682
826
  f"Dataset {self.name!r} has no rows, so there is no composition to "
icmd/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "3.3.0"
1
+ __version__ = "3.3.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: icmd-python
3
- Version: 3.3.0
3
+ Version: 3.3.4
4
4
  Summary: Python client app for ICMD®
5
5
  Author: QuesTek Innovations LLC
6
6
  License-Expression: LicenseRef-Proprietary
@@ -1,10 +1,10 @@
1
1
  icmd/__init__.py,sha256=BoE6tl0PaBgXqXmL_QjebFos5eOvS91Dj6tjJwolezk,2739
2
2
  icmd/client.py,sha256=nqp6MZh2BdT2Qx1cv_Dg306BzvTcHJ1F2Dd0NU1Oh20,22150
3
- icmd/compose.py,sha256=WaZEF2JcrMbJK90nhGrL-0iyJRyuQgq6Nh1-OgK71K8,6960
3
+ icmd/compose.py,sha256=V_B4EYuX01oJexnGGvhIwDUKgfx_Fyd0lPVvSRUoH8c,6931
4
4
  icmd/links.py,sha256=I8v8_xhI6lbUwrMjIUcYYcqKUi2PIPm6zQ7mXYP9A5E,6827
5
5
  icmd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- icmd/types.py,sha256=pMAaQaMHolaiAYh_aBG0NZWf5yiqxY5XbRmekxCUREU,72026
7
- icmd/version.py,sha256=MPSbkJS_-QSkLK7sMlFXFf5iWlbcSL18Dj2ZiMCtS9c,22
6
+ icmd/types.py,sha256=LizuU96hF7VRjl4OlyF0oPcao1cKe795YXEsLW9fKI4,79496
7
+ icmd/version.py,sha256=Smy9HlTNV-SDgnJHDo2272CgsZJYueox7aaoRhK744k,22
8
8
  icmd/_vendor/__init__.py,sha256=VFA4EQd2l76sxgkHPUpgzZavFF6kFdpq9v71AZsPIdQ,579
9
9
  icmd/_vendor/assembler.py,sha256=U8VPlqN1605RJQ5qMVoNrL3uJ7NpUe8BVmD8q06IN5w,5282
10
10
  icmd/apps/__init__.py,sha256=ak2ch1UU-qaiyOT1eCdRwqSI0Avhzyw5DLkw-U2ivQQ,659
@@ -13,7 +13,7 @@ icmd/apps/agent.py,sha256=oSgIjchkcWi83-1Bsqywe2yqzfz0cM0XTyidQPoo7IM,8037
13
13
  icmd/apps/base.py,sha256=y4uvIiomCLia89UL2rV4oufLBUQaODZ8qwl-EwDtjhM,18979
14
14
  icmd/apps/constants.py,sha256=5xoIwFNGOaAp2cHAlVDlbzJ7ObQHYXJgVGMBfNLCHzI,757
15
15
  icmd/apps/documents.py,sha256=8_wFc7NLUFQMYZA5hqsciXniHECZEWS-egX_ni8F4LE,3720
16
- icmd/apps/jobs.py,sha256=TVSSR6HLmQuSc0mA0Zrax_LclncOmmAMfn7ITf-aT9E,35319
16
+ icmd/apps/jobs.py,sha256=mUwV1wD4NlxOuYbK6a2HFZvZOtd1cXt-NQMZnmsYo1c,35282
17
17
  icmd/apps/navigation.py,sha256=mbEJG33Z-Nha5txvMpL63sT-UqsgFgZ-NMhTU7L7gI0,3738
18
18
  icmd/apps/results.py,sha256=lPs9iBoW49h0HTIYaUIau7QZVok7RdpSeawnxyA0ETU,7246
19
19
  icmd/apps/studio.py,sha256=XN6gVCqkTzp7qH9I-UOhW7UJ2RfkGv60H-G7ulkR850,2419
@@ -25,7 +25,7 @@ icmd/cli/account.py,sha256=8TM-zwOUIJVc11IePT0iL7RC7ZoTBBI5oBMIMW6Ms2Q,13723
25
25
  icmd/cli/auth.py,sha256=UFpEf-lLlkbphivgBfd9NxAEqHcCsPmEl-DXet-NStg,3317
26
26
  icmd/cli/auth_status.py,sha256=6wkI3Q0c-t8DBOfuPaN_xXEUcL9HLZm1jX4lQ76X3Ag,13505
27
27
  icmd/cli/client.py,sha256=3iToPSABFYWWUPn-uDVFAv9GoBWQXP04l2Id_aYgd64,2186
28
- icmd/cli/commands.py,sha256=AvAvK9VNk7WydcpeB019ZetGDKuJqWN3bwhY3hcpSpc,54828
28
+ icmd/cli/commands.py,sha256=JdEsIlsMvThWaMHbNmOWyESKWRbnpdTt_qxY56RHpxo,54852
29
29
  icmd/cli/diagnostics.py,sha256=1fXgKmpUMA5U-_79AzZ0uSibFmDTK_CLCe91YhBnM8s,2747
30
30
  icmd/cli/domain.py,sha256=MiGzdKwKaOEvMshsPPJTmdBm7pCTjGM98MOg9RhAL_k,874
31
31
  icmd/cli/links.py,sha256=7CMhKUJ-qKQmk2I-KCdn6oF06cdCq0v5jQDGU7gEJ30,14039
@@ -41,10 +41,10 @@ icmd/core/display.py,sha256=RkgJhr2Bfw3-ZjGvE100ddD6oyAAUUAJfBAy6DvX5ik,13060
41
41
  icmd/core/exceptions.py,sha256=9iFCw93yZ0Eh1x_6-acPU8pVtaUy7EhSz-h1JkYS4M4,6985
42
42
  icmd/core/groups.py,sha256=OFbQce42DLDK0rDAdMbh8H7imNPAkBJMl7o7tbKQ4Iw,7211
43
43
  icmd/core/model_classes.py,sha256=lOwTwZzmQ247fddFMjPVo43Z_VZfdPhhM0NI8LfRqlc,40047
44
- icmd/core/session.py,sha256=JKlvzairnw9ZQBTzN1EyABM2OwgDX98-ZzwpHWa_2Ls,59188
45
- icmd_python-3.3.0.dist-info/licenses/LICENSE,sha256=644lGvCmDFa5hQl6xdhaYk3o-1oWqPXAIiiQlvqeZdE,1028
46
- icmd_python-3.3.0.dist-info/METADATA,sha256=NDMgci_LiXhHg7gN0vjCzu4wH6ygTIQ6_m9fN7M7dhM,4919
47
- icmd_python-3.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
48
- icmd_python-3.3.0.dist-info/entry_points.txt,sha256=CFX537PlyyPQL3Q_3TKUFeiiDSkOAboqaLEy9AIEwVU,39
49
- icmd_python-3.3.0.dist-info/top_level.txt,sha256=olzM6Z_QEwg7RrFVOnkgO0I2Ps2m7vhSqYQZYeiawcw,5
50
- icmd_python-3.3.0.dist-info/RECORD,,
44
+ icmd/core/session.py,sha256=WWsZDUampgxnPQj-aM5l4OkLS2ZgvSMstjqNCsYAuI0,60054
45
+ icmd_python-3.3.4.dist-info/licenses/LICENSE,sha256=644lGvCmDFa5hQl6xdhaYk3o-1oWqPXAIiiQlvqeZdE,1028
46
+ icmd_python-3.3.4.dist-info/METADATA,sha256=gtyRqOMLEKdmaBoKGd220JkW3pRXZX8a7Etz2kcmhh0,4919
47
+ icmd_python-3.3.4.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
48
+ icmd_python-3.3.4.dist-info/entry_points.txt,sha256=CFX537PlyyPQL3Q_3TKUFeiiDSkOAboqaLEy9AIEwVU,39
49
+ icmd_python-3.3.4.dist-info/top_level.txt,sha256=olzM6Z_QEwg7RrFVOnkgO0I2Ps2m7vhSqYQZYeiawcw,5
50
+ icmd_python-3.3.4.dist-info/RECORD,,