1. Keyword Value Types
      1. Boolean Value Type
      2. Enumerated Value Type
      3. Bitfield Value Type
    1. Repeated Value Type
    2. Compound Value Type
    3. String Conversion
    4. Automated Documentation

From Ops/Core

Keyword Value Types

This page documents the python module opscore.protocols.types.

The optional values associated with message keywords are, by default, treated as opaque strings. A value type declaration associates a value with the metadata necessary to to support services such as:

The following types are currently supported:

String A string value
Float An IEEE single-precision floating point number
Double An IEEE double-precision floating point number
Int A signed 32-bit decimal integer
UInt An unsigned 32-bit decimal integer
Long A signed 64-bit decimal integer
Hex An unsigned 32-bit hexadecimal integer
Bool A boolean value
Enum An enumerated value
Bits An unsigned 32-bit integer interpreted as packed bit fields

The following string metadata can be associated with any type (adapted from the data dictionary keyValInfo):

name An optional name identifying this value. Should be a valid python identifier. Used by the archiver when building expressions and creating tables.
help Descriptive help text for a value.
units The display units of a value. E.g., "arcsec"
reprFmt A printf-style format for the internal representation of a value. For numeric values, this should display the full precision available. E.g., "%.6f"
strFmt A printf-style format for the default display string of a value (omitting its units). E.g., "%.1f"
invalid A character literal that represents a case-insensitive invalid value. E.g., "???"

Note that a reprFmt or strFmt that contains either %r or %s is potentially recursive and will raise types.ValueTypeError.

Value types are implemented as bona fide python types which share a common ValueType metaclass and generalize the built-in python types. For example:

>>> DewarTemp = Float(strFmt='%.1f',units='C',help='Dewar temperature')
>>> t1 = DewarTemp(-12.345)
>>> t2 = DewarTemp(0.2)
>>> isinstance(DewarTemp,type)
True
>>> isinstance(t1,float)
True

Metadata is used to assign type methods and attributes:

>>> repr(t1),str(t1)
('Float(-12.345)', '-12.3')
>>> t2.help
'dewar temperature'
>> t2.units
'C'

Since the underlying python built-in types are immutable, the same is also true of value types. First, consider what happens when we assign float literals:

pi = 3.1    # assigns a reference to a newly-created float(3.1) to the symbol 'pi'
pi = 3.141  # assigns a reference to a newly-created float(3.141) to the symbol 'pi', so that id(pi) has changed

As a result, assigning a float literal to a value type instance does not do what you might expect:

temp = DewarTemp(-12.345)  # assigns a reference to a newly-created DewarTemp(-12.345) to the symbol 'temp'
temp = -9.99               # assigns a reference to a newly-created float(-9.99) to the symbol 'temp', so that id(temp) has changed

To avoid the unintentional use of an implicit type, use instead:

temp = DewarTemp(-12.345)
temp = DewarTemp(-9.99)

Simple value types are declared as <Type>(<metadata>) where <Type> is String, Float, Int, ... and <metadata> is a sequence of field=value pairs. No metadata is required. Each declaration creates a distinct python type, even when two declarations share the same metadata. Value types with special declaration syntax and string formatting are described below.

Boolean Value Type

A boolean type declaration must start with two values that identify the False and True states, in that order, followed by optional field=value metadata:

>>> Tripped = Bool('OK','FAULT',help='Interlock trip status')
>>> Tripped('FAULT')
Bool(1)
>>> print Tripped('OK')
OK

The Boolean type provides custom str() formatting and ignores any strFmt metadata with a warning.

Enumerated Value Type

An enumerated type declaration must start with one or more labels identifying the allowed states followed by optional field=value metadata:

>>> ReadoutState = Enum('Idle','Busy','Fault',help='Instrument readout state')
>>> ReadoutState('Fault')
Enum('Fault')
>>> print ReadoutState('Busy')
Busy

The Enum type provides custom str() formatting and silently ignores any strFmt metadata.

Enumeration objects are interchangeable with python built-in strings. Comparisons between enumeration objects and strings are case insensitive:

>>> ReadoutState('Fault') == 'fault'
True

The capitalization specified in the constructor defines the canonical form of each label that will be used in generating documentation, for example. Enum objects are internally canonicalized so the following constructors are all equivalent and you should never need to add explicit .lower() or .upper() calls in your code:

ReadoutState('Fault')
ReadoutState('fault')
ReadoutState('FAULT')

The Enum type supports optional labelHelp metadata that documents each label individually. When defining new enumerations, it is preferable to make the labels self describing, however this is not always possible when interfacing to legacy systems. The labelHelp should be a list of strings:

MsgCode = types.Enum('>','I','W',':','F','!',labelHelp=['Queued','Information','Warning','Finished','Error','Fatal'])

Bitfield Value Type

A bitfield type declaration must start with one or more bitfield specifications followed by optional field=value metadata. A bitfield specification has one of the following forms:

Bitfield names are restricted to the characters A-Z, a-z, 0-9 and underscore (_). Bitfields are mapped to an unsigned 32-bit integer starting from the least-significant bit, in the order they are declared.

>>> Register = Bits('addr:8','data:8',':2','rw','as',help='FPGA control register')
>>> Register(0x04beef)
Bits(311023L)
>>> print Register(0x04beef)
(addr=11101111,data=10111110,rw=1,as=0)

The Bits type provides custom str() formatting and ignores any strFmt metadata with a warning.

Repeated Value Type

Any value type can be repeated exactly n times using the shorthand

Float(...)*n

An allowed range, n to m, in the number of repetitions is indicated with

Float(...)*(n,m)

Finally, a range with no maximum is indicated with

Float(...)*(n,)

Compound Value Type

A sequence of predefined types that always appear together can be represented as a compound type, for example:

CompoundValueType(
  Enum('INFO','WARN','ERROR','FAIL',name='severity',help='Message severity'),
  String(name='text',help='Message text'),
  help="A logging message"
)

String Conversion

All value types can be initialized from a string value, for example:

Float()('3.141')
Enum('RED','GREEN','BLUE')('GREEN')
Bool('no','yes')('no')

This is the mechanism used to assign typed values to keywords. Assuming that the constructor argument is a string, then there are two ways in which an initialization can fail. First, the string might be a case-insensitive match to value of the invalid metadata field:

Float(invalid='???')('???')

This will raise a special InvalidValue exception defined in the types module. Otherwise, the string value might not be interpretable as the specified type:

Float()('abc')
Int()('1.2')
UInt()('-12')

This will raise a standard ValueError exception, similar to the behavior of the built-in types:

float('abc')
int('1.2')

Note that the predefined IEEE-754 special floating point values 'nan' and 'inf' will not, by default, raise any exceptions for a Float or Double type and will be correctly mapped to the database's special float values:

Float()('nan')
Double()('-inf')

However, a string such as 'nan' can be specified as the invalid value and this will take precedence over the IEEE-754 special value handling. Therefore, the following will raise opscore.protocols.types.InvalidValueError (note that the invalid value test is not case sensitive):

Float(invalid='NaN')('nan')

Automated Documentation

Any value type (or repeated value type) can describe itself in either plain text format:

print vtype.describe()

or else in html format:

print >> htmlfile, vtype.describeAsHTML()

In both cases, the return value is a printable string. Some example type declarations and their plain text descriptions follow:

Float(units='C',strFmt='%.2f',help='Air Temperature',invalid='?')
 Description: Air Temperature
        Type: Float (float)
       Units: C
     Invalid: ?
Enum('RED','GREEN','BLUE',help='Colors',invalid='PINK')*(2,)
    Repeated: at least 2 times
 Description: Colors
        Type: Enum (int)
      Values: RED,GREEN,BLUE
     Invalid: PINK
Enum('>','I','W',':','F','!',labelHelp=['Queued','Information','Warning','Finished','Error','Fatal'],help='Reply header status code')
 Description: Reply header status code
        Type: Enum (str,int2)
     Value-0: > (Queued)
     Value-1: I (Information)
     Value-2: W (Warning)
     Value-3: : (Finished)
     Value-4: F (Error)
     Value-5: ! (Fatal)
Bool('no','yes',help='The answer',invalid='unknown')
 Description: The answer
        Type: Bool (int)
       False: no
        True: yes
     Invalid: unknown
Bits('addr:8','data:8',':2','rw','as',help='FPGA control register')
 Description: FPGA control register
        Type: Bits (long)
     Field-0: 00000000000011111111 addr
     Field-1: 00001111111100000000 data
     Field-2: 01000000000000000000 rw
     Field-3: 10000000000000000000 as

Descriptions formatted for HTML contain the same (label,value) descriptor elements but wrapped in DIV and SPAN elements tagged with CSS class names for user-defined styling. For example:

<div class="vtype">
  <div class="descriptor"><span class="label">Description</span><span class="value">Air Temperature</span></div>
  <div class="descriptor"><span class="label">Type</span><span class="value">Float (float)</span></div>
  <div class="descriptor"><span class="label">Units</span><span class="value">C</span></div>
  <div class="descriptor"><span class="label">Invalid</span><span class="value">?</span></div>
</div>