(case_properties_by_case_type, parent_type_map,
                                             include_parent_properties)
return normalized_case_properties_by_case_type
Analyze and propagate all parent refs in `case_properties_by_case_type`

    and return something in the same format but with all parent refs propagated
    up (and if `include_parent_properties`, down) the chain

    A note about *implementation*. The approach used here is to first receive all possible
    case properties from all sources, but with inconsistent parent/<property> strewn about.
    (For example child may have parent/<foo> but parent does not have <foo>.)
    Then (in this method), the mapping of case types to property sets is flattened
    to a single set of `_CasePropertyRef`s, which represents each property
    (conceptually "parent/<property> relative to <case_type>")
    as a pair of
      - _CaseTypeRef ('parent' [or parent/parent, etc.] relative to <case_type>)
      - case property (the string)
    Finally, this set is copied over to a new set, but with each case_type_ref of each
    case_property_ref replaced with its most canonical form
    (or forms in the rare case that a case_type_ref resolves to more than one case_type,
    i.e. a child case has more than one case type that can be its parent).
    Additionally, if include_parent_properties is True, each canonical form is then splatted
    out into all of the other forms it can take: (child, 'parent/parent', property) becomes
    (parent, 'parent', property) and (grandparent, '', property) as well. (Note I'm using
    conceptual notation here, whereas in the code each of these is a `_CasePropertyRef`.)

    :param case_properties_by_case_type: {case_type: set(case_property)} mapping,
           where case_property is a string of the form "(parent/)*<case_property>"
    :param parent_type_map: {case_type: {relationship: [parent_type]}}
           note that for each relationship (almost always "parent"), there can be *multiple*
           possible parent_types
    :param include_parent_properties: If this is set to True, then propagate parents'
           properties down (as parent/<property>), in addition to propagating
           parent/<property> on the child up to <property> on the parent
           (which happens regardless of this flag). If set to True, all `parent/<property>`
           cases are also *removed* from the child, unless it cannot be determined what
           it refers to.

    :return: {case_type: set(case_property)} mapping (with the propagated properties)
Analyze and propagate all parent refs in `case_properties_by_case_type`
Analyze and propagate all parent refs in case_properties_by_case_type
def _propagate_and_normalize_case_properties(case_properties_by_case_type, parent_type_map,
                                             include_parent_properties):
    """
    Analyze and propagate all parent refs in `case_properties_by_case_type`

    and return something in the same format but with all parent refs propagated
    up (and if `include_parent_properties`, down) the chain

    A note about *implementation*. The approach used here is to first receive all possible
    case properties from all sources, but with inconsistent parent/<property> strewn about.
    (For example child may have parent/<foo> but parent does not have <foo>.)
    Then (in this method), the mapping of case types to property sets is flattened
    to a single set of `_CasePropertyRef`s, which represents each property
    (conceptually "parent/<property> relative to <case_type>")
    as a pair of
      - _CaseTypeRef ('parent' [or parent/parent, etc.] relative to <case_type>)
      - case property (the string)
    Finally, this set is copied over to a new set, but with each case_type_ref of each
    case_property_ref replaced with its most canonical form
    (or forms in the rare case that a case_type_ref resolves to more than one case_type,
    i.e. a child case has more than one case type that can be its parent).
    Additionally, if include_parent_properties is True, each canonical form is then splatted
    out into all of the other forms it can take: (child, 'parent/parent', property) becomes
    (parent, 'parent', property) and (grandparent, '', property) as well. (Note I'm using
    conceptual notation here, whereas in the code each of these is a `_CasePropertyRef`.)

    :param case_properties_by_case_type: {case_type: set(case_property)} mapping,
           where case_property is a string of the form "(parent/)*<case_property>"
    :param parent_type_map: {case_type: {relationship: [parent_type]}}
           note that for each relationship (almost always "parent"), there can be *multiple*
           possible parent_types
    :param include_parent_properties: If this is set to True, then propagate parents'
           properties down (as parent/<property>), in addition to propagating
           parent/<property> on the child up to <property> on the parent
           (which happens regardless of this flag). If set to True, all `parent/<property>`
           cases are also *removed* from the child, unless it cannot be determined what
           it refers to.

    :return: {case_type: set(case_property)} mapping (with the propagated properties)
    """
    case_relationship_manager = _CaseRelationshipManager(
        parent_type_map, case_types=list(case_properties_by_case_type.keys()))
    flattened_case_properties = _flatten_case_properties(case_properties_by_case_type)
    normalized_case_properties = set()
    for property_ref in flattened_case_properties:
        resolved_case_types = case_relationship_manager.resolve_expansion(property_ref.case_type_ref)
        if resolved_case_types:
            if include_parent_properties:
                normalized_case_properties.update(
                    _PropertyRef(expansion, property_ref.case_property)
                    for case_type in resolved_case_types
                    for expansion in case_relationship_manager.expand_case_type(case_type)
                )
            else:
                normalized_case_properties.update(
                    _PropertyRef(_CaseTypeRef(case_type, ()), property_ref.case_property)
                    for case_type in resolved_case_types
                )
        else:
            # if #case/parent doesn't match any known case type, leave the property_ref as is
            normalized_case_properties.add(property_ref)

    normalized_case_properties_by_case_type = defaultdict(set)
    for property_ref in normalized_case_properties:
        normalized_case_properties_by_case_type[property_ref.case_type_ref.case_type].add(
            '/'.join(property_ref.case_type_ref.relationship_path + (property_ref.case_property,)))
    return normalized_case_properties_by_case_type
def _propagate_and_normalize_case_properties ( case_properties_by_case_type , parent_type_map , include_parent_properties ) : case_relationship_manager = _CaseRelationshipManager ( parent_type_map , case_types = list ( case_properties_by_case_type . keys ( ) ) ) flattened_case_properties = _flatten_case_properties ( case_properties_by_case_type ) normalized_case_properties = set ( ) for property_ref in flattened_case_properties : resolved_case_types = case_relationship_manager . resolve_expansion ( property_ref . case_type_ref ) if resolved_case_types : if include_parent_properties : normalized_case_properties . update ( _PropertyRef ( expansion , property_ref . case_property ) for case_type in resolved_case_types for expansion in case_relationship_manager . expand_case_type ( case_type ) ) else : normalized_case_properties . update ( _PropertyRef ( _CaseTypeRef ( case_type , ( ) ) , property_ref . case_property ) for case_type in resolved_case_types ) else : # if #case/parent doesn't match any known case type, leave the property_ref as is normalized_case_properties . add ( property_ref ) normalized_case_properties_by_case_type = defaultdict ( set ) for property_ref in normalized_case_properties : normalized_case_properties_by_case_type [ property_ref . case_type_ref . case_type ] . add ( '/' . join ( property_ref . case_type_ref . relationship_path + ( property_ref . case_property , ) ) ) return normalized_case_properties_by_case_type