def link_command(self, command): """Links the schema Command.""" if not isinstance(command, schema.Command): raise exception.InvalidType('Expecting a command') options = command.desc.options.Extensions[wdl_options_pb2.command] if options.completion_event: response_desc = command.desc.parent.messages[options.completion_event] if not response_desc: raise exception.InvalidType('Cannot find completion event %s in trait' % options.completion_event) response = self.get_obj(response_desc.full_name) if not isinstance(response, schema.CommandResponse): raise exception.InvalidType( 'Completion event %s must be a ResponseEvent' % response_desc.full_name) command.response = response command.response.parent = command if options.compatibility.HasField('min_version'): command.min_version = options.compatibility.min_version if options.compatibility.HasField('max_version'): command.max_version = options.compatibility.max_version for field_desc in command.desc.fields.values(): command.parameter_list.append(self.get_obj(field_desc.full_name)) command.extends = self.get_extends( command.desc.options.Extensions[wdl_options_pb2.command].extends)
def link_typespace(self, typespace): """Links and initializes a given schema typespace. Args: typespace: Uninitialized typespace object Raises: InvalidType: MissingArgument: """ if not isinstance(typespace, schema.Typespace): raise exception.InvalidType('Expecting an typespace') if typespace.desc.fields: raise exception.InvalidType( 'Typespace {} contains properties, typespaces should not contain ' 'properties.'.format(typespace.full_name)) options = typespace.desc.options.Extensions[wdl_options_pb2.typespace] vendor = self.get_vendor(typespace.desc.full_name) vendor.typespace_list.append(typespace) # Used to set the file descriptor in dev_* typespace.nwv_pb_desc = text_encoding.CEscape( typespace.desc.SerializeToString(), False) typespace.stability = schema.Stability(options.stability) typespace.version = options.version typespace.version_map = schema.VersionMap(options.version_map) for nested_msg_desc in _order_messages_by_dependency( typespace.desc.messages.values(), typespace.full_name): if nested_msg_desc.is_map_entry: continue # ignore map entries nested_msg = self.get_obj(nested_msg_desc.full_name) if nested_msg: if isinstance(nested_msg, schema.Command): typespace.command_list.append(nested_msg) elif isinstance(nested_msg, schema.Event): typespace.event_list.append(nested_msg) elif isinstance(nested_msg, schema.Struct): typespace.struct_list.append(nested_msg) else: raise exception.InvalidType('Unexpected type in typespace') for nested_enum_desc in typespace.desc.enums.values(): nested_enum = self.get_obj(nested_enum_desc.full_name) constant_type = nested_enum_desc.options.Extensions[ wdl_options_pb2.enumopts].constant_type if constant_type: typespace.constant_group_list.append(nested_enum) else: typespace.enum_list.append(nested_enum)
def link_struct(self, struct): """Parse a protobuf message and turn it into a gwv schema struct. Args: struct: Protobuf message descriptor representing a struct Raises: InvalidType: Encountered an invalid type while linking """ options = struct.desc.options.Extensions[wdl_options_pb2.structopts] if struct.desc.is_map_entry: # ignore map entries return if self.is_common(struct.full_name): self.link_common_struct(struct) elif (not struct.desc.parent or not isinstance( self.get_obj(struct.desc.parent.full_name), schema.StructEnumCollectionBase)): raise exception.InvalidType(( 'Unexpected struct %s defined outside a typespace or trait' % struct)) for field_desc in struct.desc.fields.values(): struct.field_list.append(self.get_obj(field_desc.full_name)) struct.extends = self.get_extends( struct.desc.options.Extensions[wdl_options_pb2.structopts].extends) if options is not None: if options.compatibility.HasField('min_version'): struct.min_version = options.compatibility.min_version if options.compatibility.HasField('max_version'): struct.max_version = options.compatibility.max_version
def link_file(self, file_obj): """Links and initializes a given schema file. Args: file_obj: Uninitialized file_obj object Raises: InvalidType: """ if not isinstance(file_obj, schema.File): raise exception.InvalidType('Expecting a file') if not self.is_protobuf(file_obj.full_name): vendor = self.get_vendor(file_obj.full_name) if vendor != None: vendor.file_list.append(file_obj) for message_desc in file_obj.desc.message_type: full_name = '.'.join((file_obj.desc.package, message_desc.name)) schema_obj = self.get_obj(full_name) if isinstance(schema_obj, schema.Trait): file_obj.trait_list.append(schema_obj) elif isinstance(schema_obj, schema.Typespace): file_obj.typespace_list.append(schema_obj) elif isinstance(schema_obj, schema.Interface): file_obj.interface_list.append(schema_obj) elif isinstance(schema_obj, schema.Resource): file_obj.resource_list.append(schema_obj) elif isinstance(schema_obj, schema.Struct): file_obj.struct_list.append(schema_obj) else: raise exception.InvalidType('Unexpected type in file: {}'.format( type(schema_obj))) for enum_desc in file_obj.desc.enum_type: full_name = '.'.join((file_obj.desc.package, enum_desc.name)) schema_obj = self.get_obj(full_name) if isinstance(schema_obj, schema.Enum): file_obj.enum_list.append(schema_obj) else: raise exception.InvalidType('Unexpected type in file: {}'.format( type(schema_obj)))
def link_enum(self, enum): """Parses the protobuf enum descriptor turns into a Google Weave Enum. Args: enum: A protobuf enum description to add to schema Raises: InvalidType: Encountered an invalid type while linking DuplicateObject: Duplicate enum value pair encountered InvalidUsage: Option used in an invalid way """ options = enum.desc.options.Extensions[wdl_options_pb2.enumopts] enum_pairs = enum.pair_list if self.is_common(enum.full_name): self.link_common_enum(enum) elif (not enum.desc.parent or not isinstance( self.get_obj(enum.desc.parent.full_name), schema.StructEnumCollectionBase)): raise exception.InvalidType( ('Unexpected enum %s defined outside a typespace or trait' % enum)) if options is not None: if options.compatibility.HasField('min_version'): enum.min_version = options.compatibility.min_version if options.compatibility.HasField('max_version'): enum.max_version = options.compatibility.max_version for value in enum.desc.values.values(): description = self.parse_comments(value) pair_opts = value.options.Extensions[wdl_options_pb2.enumvalue] value.full_name.replace( inflection.underscore(enum.base_name).decode('utf-8').upper() + '_', '') enum_pair = schema.EnumPair(value.full_name, value.number, description) enum_pair.source_file = enum.source_file if pair_opts is not None: if pair_opts.compatibility.HasField('min_version'): enum_pair.min_version = pair_opts.compatibility.min_version if pair_opts.compatibility.HasField('max_version'): enum_pair.max_version = pair_opts.compatibility.max_version try: enum_pairs.append(enum_pair) except exception.DuplicateObject: pass enum.is_bitmask = options.bitmask if options.extends: raise exception.InvalidUsage('Extending enums is not yet supported.')
def link_command_response(self, response): """Links the schema CommandResponse.""" if not isinstance(response, schema.CommandResponse): raise exception.InvalidType('Expecting a CommandResponse') options = response.desc.options.Extensions[wdl_options_pb2.event] if options.compatibility.HasField('min_version'): response.min_version = options.compatibility.min_version if options.compatibility.HasField('max_version'): response.max_version = options.compatibility.max_version for field_desc in response.desc.fields.values(): response.field_list.append(self.get_obj(field_desc.full_name))
def link_event(self, event): """Links the schema Event.""" if not isinstance(event, schema.Event): raise exception.InvalidType('Expecting an event') options = event.desc.options.Extensions[wdl_options_pb2.event] for field_desc in event.desc.fields.values(): event.field_list.append(self.get_obj(field_desc.full_name)) if options.compatibility.HasField('min_version'): event.min_version = options.compatibility.min_version if options.compatibility.HasField('max_version'): event.max_version = options.compatibility.max_version event.extends = self.get_extends(options.extends) event.importance = event.Importance(options.event_importance)
def link_constant_group(self, enum): """Links the schema ConstantGroup.""" # NOTE: Throws ValueError if constant_type is invalid if not isinstance(enum, schema.ConstantGroup): raise exception.InvalidType('Expecting a constant group') constant_type = schema.Constant.Type( enum.desc.options.Extensions[wdl_options_pb2.enumopts].constant_type) constants = enum.constant_list for value in enum.desc.values.values(): description = self.parse_comments(value) # For now, require exactly 1 value constant_value, = ( value.options.Extensions[wdl_options_pb2.enumvalue] .constant_resource_id) constants.append( schema.Constant(value.full_name, value.number, description, constant_type, constant_value))
def link_resource_component(self, component): """Link and initialize an nwv resource component. Args: component: Uninitialized component objcet """ options = component.desc.options if options.HasExtension(wdl_options_pb2.traitinst): instance_id = options.Extensions[wdl_options_pb2.traitinst].instance component.instance_id = instance_id if not options.HasExtension(wdl_options_pb2.traitconfig): raise exception.InvalidUsage( 'Trait config missing on {}. Every trait instance ' 'component must define trait config options.'.format( component.full_name)) config_options = options.Extensions[wdl_options_pb2.traitconfig] component.published_by = schema.ResourceComponent.PublishedBy( config_options.published_by) if component.published_by == schema.ResourceComponent.PublishedBy.SELF: if not config_options.HasField('proxied'): raise exception.InvalidUsage( 'Trait component {} is published by SELF but does not ' 'explicitly define proxied.'.format(component.full_name)) elif (component.published_by == schema.ResourceComponent.PublishedBy.EXTERNAL): if not config_options.HasField('subscribed'): raise exception.InvalidUsage( 'Trait component {} is published by EXTERNAL but does ' 'not explicitly define subscribed.'.format(component.full_name)) component.subscribed = config_options.subscribed component.proxied = config_options.proxied component.trait = self.get_obj(component.desc.type_name) if config_options.HasField('min_version'): component.min_version = config_options.min_version refinement_options = config_options.prop_refinement for refinement_option in refinement_options: prop_name = refinement_option.property prop = component.trait.state_list.by_name(prop_name) if not prop: raise exception.InvalidType('Property {} does not exist on trait {}' .format(prop_name, component.full_name)) refinement = component.property_refinements[ prop_name] = schema.FieldRefinement(prop) refinement.implemented = not refinement_option.unimplemented field_desc = refinement.field.desc field_type = WRAPPER_TYPES.get(field_desc.type_name, field_desc.type) if refinement_option.initial_bool_value: if field_type != field_desc.TYPE_BOOL: raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type, 'BOOL')) refinement.initial_value = refinement_option.initial_bool_value if refinement_option.initial_int_value: if field_type not in (field_desc.TYPE_INT32, field_desc.TYPE_INT64): raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type, 'INT32 or INT64')) refinement.initial_value = refinement_option.initial_int_value if refinement_option.initial_uint_value: if field_type not in (field_desc.TYPE_UINT32, field_desc.TYPE_UINT64): raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type, 'UINT32 or UINT64')) refinement.initial_value = refinement_option.initial_uint_value if refinement_option.initial_number_value: if field_type not in (field_desc.TYPE_FLOAT, field_desc.TYPE_DOUBLE): raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type, 'FLOAT or DOUBLE')) refinement.initial_value = refinement_option.initial_number_value if refinement_option.initial_string_value: if field_type != field_desc.TYPE_STRING: raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type, 'STRING')) refinement.initial_value = refinement_option.initial_string_value if refinement_option.initial_bytes_base16_value: if field_type != field_desc.TYPE_BYTES: raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type, 'BYTES')) value = refinement_option.initial_bytes_base16_value refinement.initial_bytes_base16_value = value if refinement_option.initial_resource_id_value: if field_desc.type_name != '.weave.common.ResourceId': raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.component, 'weave.common.ResourceId')) refinement.initial_value = refinement_option.initial_resource_id_value if refinement_option.initial_duration_value: if field_desc.type_name != '.google.protobuf.Duration': raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type_name, 'google.protobuf.Duration')) refinement.initial_value = [{ 'seconds': o.seconds, 'nanos': o.nanos } for o in refinement_option.initial_duration_value] if refinement_option.initial_timestamp_value: if field_desc.type_name != '.google.protobuf.Timestamp': raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type_name, 'google.protobuf.Timestamp')) refinement.initial_value = [{ 'seconds': o.seconds, 'nanos': o.nanos } for o in refinement_option.initial_timestamp_value] if refinement_option.initial_enum_value_name: if field_type != field_desc.TYPE_ENUM: raise exception.InvalidType( 'Inital value for for {} on {} is type {}, expected {}'.format( prop_name, component.full_name, field_desc.type_name, 'ENUM')) refinement.initial_value = refinement_option.initial_enum_value_name if refinement_option.initial_struct_value: raise exception.InvalidUsage( 'Component {} defines an initial value; initial values for' ' structs is not implemented.'.format(component.full_name)) # Every initial value option is an array # Flatten the array if the target property is not an array if (refinement.initial_value and field_desc.label != field_desc.LABEL_REPEATED): if len(refinement.initial_value) > 1: raise exception.InvalidType( 'Array of initial values given for {}, which is not' ' an array'.format(component.full_name)) refinement.initial_value = refinement.initial_value[0]
def link_trait(self, trait): """Links a given trait and inserts it into the schema. Args: trait: An uninitialized schema trait object Raises: InvalidType: MissingArgument: """ if not isinstance(trait, schema.Trait): raise exception.InvalidType('Expecting an trait') options = trait.desc.options.Extensions[wdl_options_pb2.trait] vendor = self.get_vendor(trait.desc.full_name) vendor.trait_list.append(trait) # Used to set the file descriptor in dev_* trait.nwv_pb_desc = text_encoding.CEscape(trait.desc.SerializeToString(), False) trait.stability = schema.Stability(options.stability) trait.version = options.version trait.version_map = schema.VersionMap(options.version_map) for field_desc in trait.desc.fields.values(): trait.state_list.append(self.get_obj(field_desc.full_name)) for nested_msg_desc in _order_messages_by_dependency( trait.desc.messages.values(), trait.full_name): if nested_msg_desc.is_map_entry: continue # ignore map entries nested_msg = self.get_obj(nested_msg_desc.full_name) if isinstance(nested_msg, schema.Command): trait.command_list.append(nested_msg) elif isinstance(nested_msg, schema.Event): trait.event_list.append(nested_msg) elif isinstance(nested_msg, schema.CommandResponse): pass # ignore, it will be handled inside link_command elif isinstance(nested_msg, schema.Struct): trait.struct_list.append(nested_msg) else: raise exception.InvalidType('Unexpected nested type in trait') for nested_enum_desc in trait.desc.enums.values(): nested_enum = self.get_obj(nested_enum_desc.full_name) constant_type = nested_enum_desc.options.Extensions[ wdl_options_pb2.enumopts].constant_type if constant_type: trait.constant_group_list.append(nested_enum) else: trait.enum_list.append(nested_enum) trait.extends = self.get_extends( trait.desc.options.Extensions[wdl_options_pb2.properties].extends.trait) self.validate_extendable( trait.desc.options.Extensions[wdl_options_pb2.properties], trait.state_list, trait.extends.desc.fields if trait.extends else {})
def parse_options(self, field, options): """Parses field constraint options.""" encoding_val = field.desc.options.Extensions[wdl_options_pb2.tlv].encoding tlv_encoding_fixed = wdl_options_pb2.Encoding.Name(encoding_val) == 'FIXED' if not options: return if options.HasField('number_constraints'): if field.data_type not in (schema.Field.DataType.FLOAT, schema.Field.DataType.DOUBLE): raise exception.InvalidType( 'Field %s in %s specifies number constraints, but number ' 'constraints are only valid for floats and doubles.' % (field.base_name, field.parent.full_name)) constraint = options.number_constraints if constraint.HasField('min'): field.min_value = constraint.min if constraint.HasField('max'): field.max_value = constraint.max if constraint.HasField('precision'): field.precision = constraint.precision if constraint.HasField('fixed_encoding_width'): field.fixed_width = constraint.fixed_encoding_width field.is_signed = (field.min_value < 0) if not tlv_encoding_fixed: raise exception.InvalidType( 'Field %s in %s has number constraints, but tlv fixed ' 'encoding is not set.' % (field.base_name, field.parent.full_name)) elif tlv_encoding_fixed: raise exception.InvalidType( 'Field %s in %s has tlv encoding set to fixed but no ' 'number constraints.' % (field.base_name, field.parent.full_name)) if options.HasField('int_constraints'): if field.data_type not in (schema.Field.DataType.INT32, schema.Field.DataType.INT64): raise exception.InvalidType( 'Field %s in %s specifies int constraints, but int ' 'constraints are only valid for int32 and int64.' % (field.base_name, field.parent.full_name)) constraint = options.int_constraints if constraint.HasField('min'): field.min_value = constraint.min if constraint.HasField('max'): field.max_value = constraint.max if constraint.HasField('width'): field.fixed_width = constraint.width field.is_signed = True if options.HasField('uint_constraints'): if field.data_type not in (schema.Field.DataType.UINT32, schema.Field.DataType.UINT64): raise exception.InvalidType( 'Field %s in %s specifies uint constraints, but uint ' 'constraints are only valid for uint32 and uint64.' % (field.base_name, field.parent.full_name)) constraint = options.uint_constraints if constraint.HasField('min'): field.min_value = constraint.min if constraint.HasField('max'): field.max_value = constraint.max if constraint.HasField('width'): field.fixed_width = constraint.width field.is_signed = False if options.HasField('string_constraints'): if (field.data_type is not schema.Field.DataType.STRING and not field.desc.type_name.endswith('weave.common.StringRef')): raise exception.InvalidType( 'Field %s in %s specifies string constraints, but string ' 'constraints are only valid for strings.' % (field.base_name, field.parent.full_name)) constraint = options.string_constraints if constraint.HasField('min_length'): field.min_length = constraint.min_length if constraint.HasField('max_length'): field.max_length = constraint.max_length if options.HasField('bytes_constraints'): if field.data_type is not schema.Field.DataType.BYTES: raise exception.InvalidType( 'Field %s in %s specifies bytes constraints, but bytes ' 'constraints are only valid for bytes.' % (field.base_name, field.parent.full_name)) constraint = options.bytes_constraints if constraint.HasField('min_length'): field.min_length = constraint.min_length if constraint.HasField('max_length'): field.max_length = constraint.max_length if options.HasField('timestamp_constraints'): if not field.desc.type_name.endswith('google.protobuf.Timestamp'): raise exception.InvalidType( 'Field %s in %s specifies timestamp constraints, but ' 'timestamp constraints are only valid for timestamps.' % (field.base_name, field.parent.full_name)) constraint = options.timestamp_constraints if constraint.HasField('signed'): field.is_signed = constraint.signed if constraint.HasField('precision'): field.precision = constraint.precision if constraint.HasField('width'): field.fixed_width = constraint.width if options.HasField('duration_constraints'): if not field.desc.type_name.endswith('google.protobuf.Duration'): raise exception.InvalidType( 'Field %s in %s specifies duration constraints, but ' 'duration constraints are only valid for durations.' % (field.base_name, field.parent.full_name)) constraint = options.duration_constraints if constraint.HasField('signed'): field.is_signed = constraint.signed if constraint.HasField('precision'): field.precision = constraint.precision if constraint.HasField('width'): field.fixed_width = constraint.width if options.HasField('resource_type'): if not field.desc.type_name.endswith('weave.common.ResourceId'): raise exception.InvalidType( 'Field %s in %s specifies resource_type constraints, but ' 'resoruce type is only valid for resourceIds.' % (field.base_name, field.parent.full_name)) field.resource_type = getattr( field.ResourceType, resource_type_pb2.ResourceType.Name( options.resource_type)[len('RESOURCE_TYPE_'):])
def link_field(self, field): """Links a schema field object. Args: field: Unintialized schema field object """ options = None if field.desc.options.HasExtension(wdl_options_pb2.prop): options = field.desc.options.Extensions[wdl_options_pb2.prop] elif field.desc.options.HasExtension(wdl_options_pb2.param): options = field.desc.options.Extensions[wdl_options_pb2.param] parent_writable = True if field.desc.parent.options.HasExtension(wdl_options_pb2.properties): props = field.desc.parent.options.Extensions[wdl_options_pb2.properties] if props.HasField('writable'): parent_writable = ( wdl_options_pb2.WriteAccess.Name(props.writable) == 'READ_WRITE') field.objc_class_prefix = field.desc.file.options.objc_class_prefix field.java_outer_classname = field.desc.file.options.java_outer_classname field.source_file = field.desc.file.name field.is_oneof = field.desc.is_oneof() if options: field.is_nullable = options.nullable field.writable = parent_writable if field.desc.options.HasExtension(wdl_options_pb2.prop): props = field.desc.options.Extensions[wdl_options_pb2.prop] if options.HasField('writable'): field.writable = ( wdl_options_pb2.WriteAccess.Name(props.writable) == 'READ_WRITE') field.is_optional = options.optional field.is_ephemeral = options.ephemeral if options is not None: if options.compatibility.HasField('min_version'): field.min_version = options.compatibility.min_version if options.compatibility.HasField('max_version'): field.max_version = options.compatibility.max_version field_desc = field.desc if field_desc.is_map(): # Unwrap maps to value field field_desc = field.desc.message_type.fields['value'] field.map_key = self.get_obj( field.desc.message_type.fields['key'].full_name) self.parse_options(field.map_key, field.desc.options.Extensions[wdl_options_pb2.keyprop]) field.map_value = self.get_obj( field.desc.message_type.fields['value'].full_name) self.parse_options(field.map_value, options) field.is_map = True else: field.is_array = field.desc.label == field_desc.LABEL_REPEATED field_type = field_desc.type if field_desc.type_name in WRAPPER_TYPES: field_type = WRAPPER_TYPES[field_desc.type_name] if not field.is_nullable and not self.is_common(field.full_name): # The fact that this was wrapped is lost, so can't be checked in # separate validator raise exception.InvalidUsage( 'Field %s is a wrapper type ' 'but is not nullable.' % field_desc.full_name) elif field.is_nullable and field_type != field_desc.TYPE_MESSAGE: raise exception.InvalidUsage('Field %s is nullable, but ' 'is not a wrapper or strudct ' 'type.' % field_desc.full_name) field.data_type = { field_desc.TYPE_FLOAT: schema.Field.DataType.FLOAT, field_desc.TYPE_DOUBLE: schema.Field.DataType.DOUBLE, field_desc.TYPE_UINT64: schema.Field.DataType.UINT64, field_desc.TYPE_UINT32: schema.Field.DataType.UINT32, field_desc.TYPE_INT64: schema.Field.DataType.INT64, field_desc.TYPE_INT32: schema.Field.DataType.INT32, field_desc.TYPE_FIXED64: schema.Field.DataType.INT64, field_desc.TYPE_FIXED32: schema.Field.DataType.INT32, field_desc.TYPE_SINT64: schema.Field.DataType.INT64, field_desc.TYPE_SINT32: schema.Field.DataType.INT32, field_desc.TYPE_STRING: schema.Field.DataType.STRING, field_desc.TYPE_ENUM: schema.Field.DataType.ENUM, field_desc.TYPE_BOOL: schema.Field.DataType.BOOL, field_desc.TYPE_BYTES: schema.Field.DataType.BYTES, field_desc.TYPE_MESSAGE: schema.Field.DataType.STRUCT, }[field_type] if field.data_type is schema.Field.DataType.STRUCT: field.struct_type = self.get_obj(field_desc.type_name) if not isinstance(field.struct_type, schema.Struct): raise exception.InvalidType( 'Message {}, referenced by field {}, is not a struct. ' 'Fields may only reference structs.'.format( field.struct_type.full_name, field.full_name)) # set metadata for legacy support field.metadata = field.struct_type elif field.data_type is schema.Field.DataType.ENUM: field.enum_type = self.get_obj(field_desc.type_name) # set metadata for legacy support field.metadata = field.enum_type self.parse_options(field, options)