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 +16 -16
- icmd/cli/commands.py +2 -2
- icmd/compose.py +5 -5
- icmd/core/session.py +19 -0
- icmd/types.py +153 -9
- icmd/version.py +1 -1
- {icmd_python-3.3.0.dist-info → icmd_python-3.3.4.dist-info}/METADATA +1 -1
- {icmd_python-3.3.0.dist-info → icmd_python-3.3.4.dist-info}/RECORD +12 -12
- {icmd_python-3.3.0.dist-info → icmd_python-3.3.4.dist-info}/WHEEL +0 -0
- {icmd_python-3.3.0.dist-info → icmd_python-3.3.4.dist-info}/entry_points.txt +0 -0
- {icmd_python-3.3.0.dist-info → icmd_python-3.3.4.dist-info}/licenses/LICENSE +0 -0
- {icmd_python-3.3.0.dist-info → icmd_python-3.3.4.dist-info}/top_level.txt +0 -0
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"``, ``"
|
|
227
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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"
|
|
436
|
-
are terminal too, so a caller that
|
|
437
|
-
must check this. ``None`` only when
|
|
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
|
|
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,
|
|
1152
|
-
"
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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.
|
|
1
|
+
__version__ = "3.3.4"
|
|
@@ -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=
|
|
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=
|
|
7
|
-
icmd/version.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
45
|
-
icmd_python-3.3.
|
|
46
|
-
icmd_python-3.3.
|
|
47
|
-
icmd_python-3.3.
|
|
48
|
-
icmd_python-3.3.
|
|
49
|
-
icmd_python-3.3.
|
|
50
|
-
icmd_python-3.3.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|