ortools.math_opt.python.normalize

Utility functions for normalizing proto3 message objects in Python.

 1#!/usr/bin/env python3
 2# Copyright 2010-2025 Google LLC
 3# Licensed under the Apache License, Version 2.0 (the "License");
 4# you may not use this file except in compliance with the License.
 5# You may obtain a copy of the License at
 6#
 7#     http://www.apache.org/licenses/LICENSE-2.0
 8#
 9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15# A fork of net/proto2/contrib/pyutil/normalize.py. A lot of the code can be
16# deleted because we do not support proto2 (no groups, no extension). Further,
17# the code has been changed to not clear:
18#   * optional scalar fields at their default value.
19#   * durations
20#   * messages in a oneof
21
22
23"""Utility functions for normalizing proto3 message objects in Python."""
24from google.protobuf import duration_pb2
25from google.protobuf import descriptor
26from google.protobuf import message
27
28
29def math_opt_normalize_proto(protobuf_message: message.Message) -> None:
30    """Clears all non-duration submessages that are not in one_ofs.
31
32    A message is considered `empty` if:
33      * every non-optional scalar fields has its default value,
34      * every optional scalar field is unset,
35      * every repeated/map fields is empty
36      * every oneof is unset,
37      * every duration field is unset
38      * all other message fields (singular, not oneof, not duration) are `empty`.
39    This function clears all `empty` fields from `message`.
40
41    This is useful for testing.
42
43    Args:
44      protobuf_message: The Message object to clear.
45    """
46    for field, value in protobuf_message.ListFields():
47        if field.type != field.TYPE_MESSAGE:
48            continue
49        if field.label == field.LABEL_REPEATED:
50            # Now the repeated case, recursively normalize each member. Note that
51            # there is no field presence for repeated fields, so we don't need to call
52            # ClearField().
53            #
54            # Maps need to be handled specially.
55            if (
56                field.message_type.has_options
57                and field.message_type.GetOptions().map_entry
58            ):
59                if (
60                    field.message_type.fields_by_number[2].type
61                    == descriptor.FieldDescriptor.TYPE_MESSAGE
62                ):
63                    for item in value.values():
64                        math_opt_normalize_proto(item)
65            # The remaining case is a regular repeated field (a list).
66            else:
67                for item in value:
68                    math_opt_normalize_proto(item)
69            continue
70        # Last case, the non-repeated sub-message
71        math_opt_normalize_proto(value)
72        # If field value is empty, not a Duration, and not in a oneof, clear it.
73        if (
74            not value.ListFields()
75            and field.message_type != duration_pb2.Duration.DESCRIPTOR
76            and field.containing_oneof is None
77        ):
78            protobuf_message.ClearField(field.name)
def math_opt_normalize_proto(protobuf_message: google.protobuf.message.Message) -> None:
30def math_opt_normalize_proto(protobuf_message: message.Message) -> None:
31    """Clears all non-duration submessages that are not in one_ofs.
32
33    A message is considered `empty` if:
34      * every non-optional scalar fields has its default value,
35      * every optional scalar field is unset,
36      * every repeated/map fields is empty
37      * every oneof is unset,
38      * every duration field is unset
39      * all other message fields (singular, not oneof, not duration) are `empty`.
40    This function clears all `empty` fields from `message`.
41
42    This is useful for testing.
43
44    Args:
45      protobuf_message: The Message object to clear.
46    """
47    for field, value in protobuf_message.ListFields():
48        if field.type != field.TYPE_MESSAGE:
49            continue
50        if field.label == field.LABEL_REPEATED:
51            # Now the repeated case, recursively normalize each member. Note that
52            # there is no field presence for repeated fields, so we don't need to call
53            # ClearField().
54            #
55            # Maps need to be handled specially.
56            if (
57                field.message_type.has_options
58                and field.message_type.GetOptions().map_entry
59            ):
60                if (
61                    field.message_type.fields_by_number[2].type
62                    == descriptor.FieldDescriptor.TYPE_MESSAGE
63                ):
64                    for item in value.values():
65                        math_opt_normalize_proto(item)
66            # The remaining case is a regular repeated field (a list).
67            else:
68                for item in value:
69                    math_opt_normalize_proto(item)
70            continue
71        # Last case, the non-repeated sub-message
72        math_opt_normalize_proto(value)
73        # If field value is empty, not a Duration, and not in a oneof, clear it.
74        if (
75            not value.ListFields()
76            and field.message_type != duration_pb2.Duration.DESCRIPTOR
77            and field.containing_oneof is None
78        ):
79            protobuf_message.ClearField(field.name)

Clears all non-duration submessages that are not in one_ofs.

A message is considered empty if:

  • every non-optional scalar fields has its default value,
  • every optional scalar field is unset,
  • every repeated/map fields is empty
  • every oneof is unset,
  • every duration field is unset
  • all other message fields (singular, not oneof, not duration) are empty. This function clears all empty fields from message.

This is useful for testing.

Arguments:
  • protobuf_message: The Message object to clear.