Skip to content

Commit 848a7f3

Browse files
OpenHTF Ownerscopybara-github
OpenHTF Owners
authored andcommitted
Add public accessor of measurements through ImmutableMeasurements in Collection.
PiperOrigin-RevId: 595488744
1 parent e4a844c commit 848a7f3

File tree

3 files changed

+63
-52
lines changed

3 files changed

+63
-52
lines changed

openhtf/core/measurements.py

+45-2
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,15 @@ def WidgetTestPhase(test):
5959
"""
6060

6161
import collections
62+
import copy
6263
import enum
6364
import functools
6465
import logging
6566
import typing
66-
from typing import Any, Callable, Dict, Iterator, List, Optional, Text, Tuple, Union
67+
from typing import Any, Callable, Dict, Iterator, List, Mapping, Optional, Text, Tuple, Union
6768

6869
import attr
69-
70+
import immutabledict
7071
from openhtf import util
7172
from openhtf.util import data
7273
from openhtf.util import units as util_units
@@ -735,6 +736,42 @@ def to_dataframe(self, columns: Any = None) -> Any:
735736
return pandas.DataFrame.from_records(self.value, columns=columns)
736737

737738

739+
@attr.s(slots=True, frozen=True)
740+
class ImmutableMeasurement(object):
741+
"""Immutable copy of a measurement."""
742+
743+
name = attr.ib(type=Text)
744+
value = attr.ib(type=Any)
745+
units = attr.ib(type=Optional[util_units.UnitDescriptor])
746+
dimensions = attr.ib(type=Optional[List[Dimension]])
747+
outcome = attr.ib(type=Optional[Outcome])
748+
docstring = attr.ib(type=Optional[Text], default=None)
749+
750+
@classmethod
751+
def from_measurement(cls, measurement: Measurement) -> 'ImmutableMeasurement':
752+
"""Convert a Measurement into an ImmutableMeasurement."""
753+
measured_value = measurement.measured_value
754+
if isinstance(measured_value, DimensionedMeasuredValue):
755+
value = data.attr_copy(
756+
measured_value, value_dict=copy.deepcopy(measured_value.value_dict)
757+
)
758+
else:
759+
value = (
760+
copy.deepcopy(measured_value.value)
761+
if measured_value.is_value_set
762+
else None
763+
)
764+
765+
return cls(
766+
name=measurement.name,
767+
value=value,
768+
units=measurement.units,
769+
dimensions=measurement.dimensions,
770+
outcome=measurement.outcome,
771+
docstring=measurement.docstring,
772+
)
773+
774+
738775
@attr.s(slots=True)
739776
class Collection(object):
740777
"""Encapsulates a collection of measurements.
@@ -820,6 +857,12 @@ def __getitem__(self, name: Text) -> Any:
820857
# Return the MeasuredValue's value, MeasuredValue will raise if not set.
821858
return m.measured_value.value
822859

860+
def immutable_measurements(self) -> Mapping[Text, ImmutableMeasurement]:
861+
return immutabledict.immutabledict({
862+
name: ImmutableMeasurement.from_measurement(meas)
863+
for name, meas in self._measurements.items()
864+
})
865+
823866

824867
# Work around for attrs bug in 20.1.0; after the next release, this can be
825868
# removed and `Collection._custom_setattr` can be renamed to `__setattr__`.

openhtf/core/test_descriptor.py

+10-11
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,16 @@
3434

3535
import attr
3636
import colorama
37-
3837
from openhtf import util
3938
from openhtf.core import base_plugs
4039
from openhtf.core import diagnoses_lib
41-
from openhtf.core import measurements
40+
from openhtf.core import measurements as htf_measurements
4241
from openhtf.core import phase_collections
4342
from openhtf.core import phase_descriptor
4443
from openhtf.core import phase_executor
4544
from openhtf.core import test_executor
4645
from openhtf.core import test_record as htf_test_record
4746
from openhtf.core import test_state
48-
4947
from openhtf.util import configuration
5048
from openhtf.util import console_output
5149
from openhtf.util import logs
@@ -462,10 +460,10 @@ class TestApi(object):
462460
stdout (configurable) and the frontend via the Station API, if it's
463461
enabled, in addition to the 'log_records' attribute of the final
464462
TestRecord output by the running test.
465-
measurements: A measurements.Collection object used to get/set measurement
466-
values. See util/measurements.py for more implementation details, but in
467-
the simple case, set measurements directly as attributes on this object
468-
(see examples/measurements.py for examples).
463+
measurements: A htf_measurements.Collection object used to get/set
464+
measurement values. See util/measurements.py for more implementation
465+
details, but in the simple case, set measurements directly as attributes
466+
on this object (see examples/measurements.py for examples).
469467
attachments: Dict mapping attachment name to test_record.Attachment instance
470468
containing the data that was attached (and the MIME type that was assumed
471469
based on extension, if any). Only attachments that have been attached in
@@ -486,7 +484,7 @@ class TestApi(object):
486484
https://github.com/google/openhtf/issues/new
487485
"""
488486

489-
measurements = attr.ib(type=measurements.Collection)
487+
measurements = attr.ib(type=htf_measurements.Collection)
490488

491489
# Internal state objects. If you find yourself needing to use these, please
492490
# use required_state=True for the phase to use the test_state object instead.
@@ -568,8 +566,8 @@ def attach_from_file(
568566
filename, name=name, mimetype=mimetype)
569567

570568
def get_measurement(
571-
self,
572-
measurement_name: Text) -> Optional[test_state.ImmutableMeasurement]:
569+
self, measurement_name: Text
570+
) -> Optional[htf_measurements.ImmutableMeasurement]:
573571
"""Get a copy of a measurement value from current or previous phase.
574572
575573
Measurement and phase name uniqueness is not enforced, so this method will
@@ -584,7 +582,8 @@ def get_measurement(
584582
return self._running_test_state.get_measurement(measurement_name)
585583

586584
def get_measurement_strict(
587-
self, measurement_name: Text) -> test_state.ImmutableMeasurement:
585+
self, measurement_name: Text
586+
) -> htf_measurements.ImmutableMeasurement:
588587
"""Get a copy of the test measurement from current or previous phase.
589588
590589
Measurement and phase name uniqueness is not enforced, so this method will

openhtf/core/test_state.py

+8-39
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@
3333
import os
3434
import socket
3535
import sys
36-
from typing import Any, Dict, Iterator, List, Optional, Set, Text, Tuple, TYPE_CHECKING, Union
36+
from typing import Any, Dict, Iterator, List, Optional, Set, TYPE_CHECKING, Text, Tuple, Union
3737

3838
import attr
39-
4039
import openhtf
4140
from openhtf import plugs
4241
from openhtf import util
@@ -48,7 +47,6 @@
4847
from openhtf.util import configuration
4948
from openhtf.util import data
5049
from openhtf.util import logs
51-
from openhtf.util import units
5250
from typing_extensions import Literal
5351

5452
CONF = configuration.CONF
@@ -96,37 +94,6 @@ class InternalError(Exception):
9694
"""An internal error."""
9795

9896

99-
@attr.s(slots=True, frozen=True)
100-
class ImmutableMeasurement(object):
101-
"""Immutable copy of a measurement."""
102-
103-
name = attr.ib(type=Text)
104-
value = attr.ib(type=Any)
105-
units = attr.ib(type=Optional[units.UnitDescriptor])
106-
dimensions = attr.ib(type=Optional[List[measurements.Dimension]])
107-
outcome = attr.ib(type=Optional[measurements.Outcome])
108-
109-
@classmethod
110-
def from_measurement(
111-
cls, measurement: measurements.Measurement) -> 'ImmutableMeasurement':
112-
"""Convert a Measurement into an ImmutableMeasurement."""
113-
measured_value = measurement.measured_value
114-
if isinstance(measured_value, measurements.DimensionedMeasuredValue):
115-
value = data.attr_copy(
116-
measured_value, value_dict=copy.deepcopy(measured_value.value_dict))
117-
else:
118-
value = (
119-
copy.deepcopy(measured_value.value)
120-
if measured_value.is_value_set else None)
121-
122-
return cls(
123-
name=measurement.name,
124-
value=value,
125-
units=measurement.units,
126-
dimensions=measurement.dimensions,
127-
outcome=measurement.outcome)
128-
129-
13097
class TestState(util.SubscribableStateMixin):
13198
"""This class handles tracking the state of a running Test.
13299
@@ -263,8 +230,9 @@ def get_attachment(self,
263230
self.state_logger.warning('Could not find attachment: %s', attachment_name)
264231
return None
265232

266-
def get_measurement(self,
267-
measurement_name: Text) -> Optional[ImmutableMeasurement]:
233+
def get_measurement(
234+
self, measurement_name: Text
235+
) -> Optional[measurements.ImmutableMeasurement]:
268236
"""Get a copy of a measurement value from current or previous phase.
269237
270238
Measurement and phase name uniqueness is not enforced, so this method will
@@ -282,16 +250,17 @@ def get_measurement(self,
282250
# Check current running phase state
283251
if self.running_phase_state:
284252
if measurement_name in self.running_phase_state.measurements:
285-
return ImmutableMeasurement.from_measurement(
286-
self.running_phase_state.measurements[measurement_name])
253+
return measurements.ImmutableMeasurement.from_measurement(
254+
self.running_phase_state.measurements[measurement_name]
255+
)
287256

288257
# Iterate through phases in reversed order to return most recent (necessary
289258
# because measurement and phase names are not necessarily unique)
290259
for phase_record in reversed(self.test_record.phases):
291260
if (phase_record.result not in ignore_outcomes and
292261
measurement_name in phase_record.measurements):
293262
measurement = phase_record.measurements[measurement_name]
294-
return ImmutableMeasurement.from_measurement(measurement)
263+
return measurements.ImmutableMeasurement.from_measurement(measurement)
295264

296265
self.state_logger.warning('Could not find measurement: %s',
297266
measurement_name)

0 commit comments

Comments
 (0)