def _map_attr_to_type_for_class( cls: Type, ) -> Dict[attr.Attribute, Tuple[BuildableAttrFieldType, Optional[Type[Enum]]]]: """Helper function for _attribute_field_type_reference_for_class to map attributes to their BuildableAttrFieldType for a class if the attributes of the class aren't yet in the cached _class_structure_reference. """ attr_field_types: Dict[ attr.Attribute, Tuple[BuildableAttrFieldType, Optional[Type[Enum]]] ] = {} for attribute in attr.fields_dict(cls).values(): if is_forward_ref(attribute): attr_field_types[attribute] = (BuildableAttrFieldType.FORWARD_REF, None) elif is_enum(attribute): enum_cls = get_enum_cls(attribute) if not enum_cls: raise ValueError( f"Did not find enum class for enum attribute [{attribute}]" ) attr_field_types[attribute] = (BuildableAttrFieldType.ENUM, enum_cls) elif is_date(attribute): attr_field_types[attribute] = (BuildableAttrFieldType.DATE, None) else: attr_field_types[attribute] = (BuildableAttrFieldType.OTHER, None) return attr_field_types
def schema_type_for_attribute(attribute: Any) -> str: # Race and ethnicity fields are the only ones that support list form. These # are converted to comma-separated lists stored as strings in BigQuery. if is_enum(attribute) or is_list(attribute) or is_str(attribute): return bigquery.enums.SqlTypeNames.STRING.value if is_int(attribute): return bigquery.enums.SqlTypeNames.INTEGER.value if is_float(attribute): return bigquery.enums.SqlTypeNames.FLOAT.value if is_date(attribute): return bigquery.enums.SqlTypeNames.DATE.value if is_bool(attribute): return bigquery.enums.SqlTypeNames.BOOLEAN.value raise ValueError(f"Unhandled attribute type for attribute: {attribute}")
def build_from_dictionary(cls, build_dict: Dict[str, Any]) -> \ Optional['BuildableAttr']: """Builds a BuildableAttr with values from the given build_dict. Given build_dict must contain all required fields, and cannot contain any fields with attribute types of List or ForwardRef. Any date values must be in the format 'YYYY-MM-DD' if they are present. """ if not attr.has(cls): raise Exception("Parent class must be an attr class") if not build_dict: raise ValueError("build_dict cannot be empty") cls_builder = cls.builder() for field, attribute in attr.fields_dict(cls).items(): if field in build_dict: if is_list(attribute): raise ValueError("build_dict should be a dictionary of " "flat values. Should not contain any " f"lists: {build_dict}.") if is_forward_ref(attribute): # TODO(1886): Implement detection of non-ForwardRefs # ForwardRef fields are expected to be references to other # BuildableAttrs raise ValueError("build_dict should be a dictionary of " "flat values. Should not contain any " f"ForwardRef fields: {build_dict}") if is_enum(attribute): value = cls.extract_enum_value(build_dict, field, attribute) elif is_date(attribute): value = cls.extract_date_value(build_dict, field) else: value = build_dict.get(field) setattr(cls_builder, field, value) return cls_builder.build()
def convert_field_value(field: attr.Attribute, field_value: Union[str, EnumParser]) -> Any: if field_value is None: return None if is_forward_ref(field) or is_list(field): return field_value if isinstance(field_value, str): if not field_value or not field_value.strip(): return None if field.name in converter_overrides: converter = converter_overrides[field.name] if not isinstance(field_value, converter.field_type): raise ValueError( f"Found converter for field [{field.name}] in the converter_overrides, but expected " f"field type [{converter.field_type}] does not match actual field type " f"[{type(field_value)}]") return converter.convert(field_value) if isinstance(field_value, EnumParser): if is_enum(field): return field_value.parse() raise ValueError( f"Found field value [{field_value}] for field that is not an enum [{field}]." ) if isinstance(field_value, str): if is_str(field): return normalize(field_value) if is_date(field): return parse_date(field_value) if is_int(field): return parse_int(field_value) if field.type in {bool, Union[bool, None]}: return parse_bool(field_value) raise ValueError(f"Unsupported field {field.name}")
def _convert_forward(self, src: SrcBaseType, populate_back_edges: bool) -> DstBaseType: """Converts the given src object to its entity/schema counterpart.""" src_id = self._id_from_src_object(src) if src_id in self._converted_map: return self._converted_map[src_id] schema_cls: Type[DatabaseEntity] = self._get_schema_class(src) entity_cls: Type[Entity] = self._get_entity_class(src) if entity_cls is None or schema_cls is None: raise DatabaseConversionError("Both |entity_cls| and |schema_cls| " "should be not None") if isinstance(src, Entity): dst_builder: \ Union[BuildableAttr.Builder, DatabaseEntity] = schema_cls() elif isinstance(src, DatabaseEntity): if not issubclass(entity_cls, BuildableAttr): raise DatabaseConversionError( f"Expected [{entity_cls}] to be a subclass of " f"BuildableAttr, but it is not") dst_builder = entity_cls.builder() else: raise DatabaseConversionError( "Unable to convert class [{0}]".format(src.__class__)) for field, attribute in attr.fields_dict(entity_cls).items(): if self._should_skip_field(entity_cls, field): continue if self._direction_checker.is_back_edge(src, field) and \ not populate_back_edges: continue v = getattr(src, field) if not isinstance(attribute, attr.Attribute): raise DatabaseConversionError( f"Expected attribute with class [{attribute.__class__}] to " f"be an instance of Attribute, but it is not") if isinstance(v, list): values = [] for next_src in v: if self._direction_checker.is_back_edge(src, field): self._register_back_edge(src, next_src, field) continue values.append( self._convert_forward(next_src, populate_back_edges)) if not values: continue value: Optional[Any] = values elif issubclass(type(v), Entity) or issubclass( type(v), DatabaseEntity): next_src = v if self._direction_checker.is_back_edge(src, field): self._register_back_edge(src, next_src, field) continue value = self._convert_forward(v, populate_back_edges) elif v is None: value = None elif is_enum(attribute): value = self._convert_enum(src, field, attribute) else: value = v setattr(dst_builder, field, value) if isinstance(dst_builder, BuildableAttr.Builder): dst = dst_builder.build() elif isinstance(dst_builder, DatabaseEntity): dst = dst_builder else: raise DatabaseConversionError( f"Unexpected type [{type(dst_builder)}] for dst_builder") self._converted_map[src_id] = dst return dst