#
# Copyright (c) 2021, NVIDIA CORPORATION.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# Generated by the protocol buffer compiler.  DO NOT EDIT!
# sources: tensorflow_metadata/proto/v0/path.proto, tensorflow_metadata/proto/v0/schema.proto
# plugin: python-betterproto
import json
import os
from dataclasses import dataclass
from types import SimpleNamespace
from typing import Dict, List

import betterproto

os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
from google.protobuf import json_format  # noqa: E402
from google.protobuf.any_pb2 import Any as AnyPb2  # noqa: E402
from google.protobuf.struct_pb2 import ListValue, Struct  # noqa: E402


# This is a manual hack to make it possible to parse the Any types in annotations
@dataclass(eq=False, repr=False)
class Any(betterproto.Message):
    type_url: str = betterproto.string_field(1)
    value: betterproto.Message = betterproto.message_field(2)

    def from_dict(self, value: dict):
        if "@type" in value:
            self.value = value["value"]
            self.type_url = value["@type"]
        else:
            msg_struct = Struct()
            any_pack = AnyPb2()
            json_formatted = json_format.ParseDict(value, msg_struct)
            any_pack.Pack(json_formatted)
            self.value = any_pack.value
            self.type_url = any_pack.type_url
        return self

    def to_dict(
        self,
        casing: betterproto.Casing = betterproto.Casing.CAMEL,
        include_default_values: bool = False,
    ):
        if isinstance(self.value, dict):
            ret = self.value
        else:
            msg_struct = Struct()
            msg_struct.ParseFromString(self.value)
            ret = dict(msg_struct.items())

        # the ParseFromString method will return Struct types for embedded
        # dictionaries, and ListValue for embedded lists.
        # Recursively convert embedded values
        def _ensure_value(v):
            if isinstance(v, dict):
                return _ensure_dict(v)
            elif isinstance(v, (list, ListValue)):
                return [_ensure_value(val) for val in v]
            elif isinstance(v, Struct):
                return _ensure_dict(dict(v.items()))
            return v

        def _ensure_dict(x):
            return {k: _ensure_value(v) for k, v in x.items()}

        return _ensure_dict(ret)


class LifecycleStage(betterproto.Enum):
    """
    LifecycleStage. Only UNKNOWN_STAGE, BETA, and PRODUCTION features are
    actually validated. PLANNED, ALPHA, DISABLED, and DEBUG are treated as
    DEPRECATED.
    """

    UNKNOWN_STAGE = 0
    PLANNED = 1
    ALPHA = 2
    BETA = 3
    PRODUCTION = 4
    DEPRECATED = 5
    DEBUG_ONLY = 6
    DISABLED = 7


class FeatureType(betterproto.Enum):
    """
    Describes the physical representation of a feature. It may be different
    than the logical representation, which is represented as a Domain.
    """

    TYPE_UNKNOWN = 0
    BYTES = 1
    INT = 2
    FLOAT = 3
    STRUCT = 4


class TimeDomainIntegerTimeFormat(betterproto.Enum):
    FORMAT_UNKNOWN = 0
    UNIX_DAYS = 5
    UNIX_SECONDS = 1
    UNIX_MILLISECONDS = 2
    UNIX_MICROSECONDS = 3
    UNIX_NANOSECONDS = 4


class TimeOfDayDomainIntegerTimeOfDayFormat(betterproto.Enum):
    FORMAT_UNKNOWN = 0
    PACKED_64_NANOS = 1


class TensorRepresentationRowPartitionDType(betterproto.Enum):
    UNSPECIFIED = 0
    INT64 = 1
    INT32 = 2


@dataclass
class Path(betterproto.Message):
    """
    A path is a more general substitute for the name of a field or feature that
    can be used for flat examples as well as structured data. For example, if
    we had data in a protocol buffer: message Person {   int age = 1;
    optional string gender = 2;   repeated Person parent = 3; } Thus, here the
    path {step:["parent", "age"]} in statistics would refer to the age of a
    parent, and {step:["parent", "parent", "age"]} would refer to the age of a
    grandparent. This allows us to distinguish between the statistics of
    parents' ages and grandparents' ages. In general, repeated messages are to
    be preferred to linked lists of arbitrary length. For SequenceExample, if
    we have a feature list "foo", this is represented by {step:["##SEQUENCE##",
    "foo"]}.
    """

    # Any string is a valid step. However, whenever possible have a step be
    # [A-Za-z0-9_]+.
    step: List[str] = betterproto.string_field(1)


@dataclass
class Schema(betterproto.Message):
    """Message to represent schema information. NextID: 14"""

    # Features described in this schema.
    feature: List["Feature"] = betterproto.message_field(1)
    # Sparse features described in this schema.
    sparse_feature: List["SparseFeature"] = betterproto.message_field(6)
    # Weighted features described in this schema.
    weighted_feature: List["WeightedFeature"] = betterproto.message_field(12)
    # declared as top-level features in <feature>. String domains referenced in
    # the features.
    string_domain: List["StringDomain"] = betterproto.message_field(4)
    # top level float domains that can be reused by features
    float_domain: List["FloatDomain"] = betterproto.message_field(9)
    # top level int domains that can be reused by features
    int_domain: List["IntDomain"] = betterproto.message_field(10)
    # Default environments for each feature. An environment represents both a
    # type of location (e.g. a server or phone) and a time (e.g. right before
    # model X is run). In the standard scenario, 99% of the features should be in
    # the default environments TRAINING, SERVING, and the LABEL (or labels) AND
    # WEIGHT is only available at TRAINING (not at serving). Other possible
    # variations: 1. There may be TRAINING_MOBILE, SERVING_MOBILE,
    # TRAINING_SERVICE,    and SERVING_SERVICE. 2. If one is ensembling three
    # models, where the models of the first    three models are available
    # for the ensemble model, there may be    TRAINING, SERVING_INITIAL,
    # SERVING_ENSEMBLE. See FeatureProto::not_in_environment and
    # FeatureProto::in_environment.
    default_environment: List[str] = betterproto.string_field(5)
    # Additional information about the schema as a whole. Features may also be
    # annotated individually.
    annotation: "Annotation" = betterproto.message_field(8)
    # Dataset-level constraints. This is currently used for specifying
    # information about changes in num_examples.
    dataset_constraints: "DatasetConstraints" = betterproto.message_field(11)
    # TensorRepresentation groups. The keys are the names of the groups. Key ""
    # (empty string) denotes the "default" group, which is what should be used
    # when a group name is not provided. See the documentation at
    # TensorRepresentationGroup for more info. Under development. DO NOT USE.
    tensor_representation_group: Dict[str, "TensorRepresentationGroup"] = betterproto.map_field(
        13, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE
    )


@dataclass
class ValueCountList(betterproto.Message):
    value_count: List["ValueCount"] = betterproto.message_field(1)


@dataclass
class Feature(betterproto.Message):
    """
    Describes schema-level information about a specific feature. NextID: 33
    """

    # The name of the feature.
    name: str = betterproto.string_field(1)
    # This field is no longer supported. Instead, use: lifecycle_stage:
    # DEPRECATED TODO(b/111450258): remove this.
    deprecated: bool = betterproto.bool_field(2)
    # Constraints on the presence of this feature in the examples.
    presence: "FeaturePresence" = betterproto.message_field(14, group="presence_constraints")
    # Only used in the context of a "group" context, e.g., inside a sequence.
    group_presence: "FeaturePresenceWithinGroup" = betterproto.message_field(
        17, group="presence_constraints"
    )
    # The feature has a fixed shape corresponding to a multi-dimensional tensor.
    shape: "FixedShape" = betterproto.message_field(23, group="shape_type")
    # The feature doesn't have a well defined shape. All we know are limits on
    # the minimum and maximum number of values.
    value_count: "ValueCount" = betterproto.message_field(5, group="shape_type")
    # Captures the same information as value_count but for features with nested
    # values. A ValueCount is provided for each nest level.
    value_counts: "ValueCountList" = betterproto.message_field(32, group="shape_type")
    # Physical type of the feature's values. Note that you can have: type: BYTES
    # int_domain: {   min: 0   max: 3 } This would be a field that is
    # syntactically BYTES (i.e. strings), but semantically an int, i.e. it would
    # be "0", "1", "2", or "3".
    type: "FeatureType" = betterproto.enum_field(6)
    # Reference to a domain defined at the schema level.
    domain: str = betterproto.string_field(7, group="domain_info")
    # Inline definitions of domains.
    int_domain: "IntDomain" = betterproto.message_field(9, group="domain_info")
    float_domain: "FloatDomain" = betterproto.message_field(10, group="domain_info")
    string_domain: "StringDomain" = betterproto.message_field(11, group="domain_info")
    bool_domain: "BoolDomain" = betterproto.message_field(13, group="domain_info")
    struct_domain: "StructDomain" = betterproto.message_field(29, group="domain_info")
    # Supported semantic domains.
    natural_language_domain: "NaturalLanguageDomain" = betterproto.message_field(
        24, group="domain_info"
    )
    image_domain: "ImageDomain" = betterproto.message_field(25, group="domain_info")
    mid_domain: "MIDDomain" = betterproto.message_field(26, group="domain_info")
    url_domain: "URLDomain" = betterproto.message_field(27, group="domain_info")
    time_domain: "TimeDomain" = betterproto.message_field(28, group="domain_info")
    time_of_day_domain: "TimeOfDayDomain" = betterproto.message_field(30, group="domain_info")
    # Constraints on the distribution of the feature values. Only supported for
    # StringDomains.
    distribution_constraints: "DistributionConstraints" = betterproto.message_field(15)
    # Additional information about the feature for documentation purpose.
    annotation: "Annotation" = betterproto.message_field(16)
    # Tests comparing the distribution to the associated serving data.
    skew_comparator: "FeatureComparator" = betterproto.message_field(18)
    # Tests comparing the distribution between two consecutive spans (e.g. days).
    drift_comparator: "FeatureComparator" = betterproto.message_field(21)
    # List of environments this feature is present in. Should be disjoint from
    # not_in_environment. This feature is in environment "foo" if: ("foo" is in
    # in_environment or default_environment) AND "foo" is not in
    # not_in_environment. See Schema::default_environment.
    in_environment: List[str] = betterproto.string_field(20)
    # List of environments this feature is not present in. Should be disjoint
    # from of in_environment. See Schema::default_environment and in_environment.
    not_in_environment: List[str] = betterproto.string_field(19)
    # The lifecycle stage of a feature. It can also apply to its descendants.
    # i.e., if a struct is DEPRECATED, its children are implicitly deprecated.
    lifecycle_stage: "LifecycleStage" = betterproto.enum_field(22)
    # Constraints on the number of unique values for a given feature. This is
    # supported for string and categorical features only.
    unique_constraints: "UniqueConstraints" = betterproto.message_field(31)


@dataclass
class Annotation(betterproto.Message):
    """Additional information about the schema or about a feature."""

    # Tags can be used to mark features. For example, tag on user_age feature can
    # be `user_feature`, tag on user_country feature can be `location_feature`,
    # `user_feature`.
    tag: List[str] = betterproto.string_field(1)
    # Free-text comments. This can be used as a description of the feature,
    # developer notes etc.
    comment: List[str] = betterproto.string_field(2)
    # Application-specific metadata may be attached here.
    extra_metadata: List[Any] = betterproto.message_field(3)

    @property
    def metadata(self):
        if self.comment:
            metadata = SimpleNamespace(**json.loads(self.comment[-1]))
            return metadata

        return None


@dataclass
class NumericValueComparator(betterproto.Message):
    """
    Checks that the ratio of the current value to the previous value is not
    below the min_fraction_threshold or above the max_fraction_threshold. That
    is, previous value * min_fraction_threshold <= current value <= previous
    value * max_fraction_threshold. To specify that the value cannot change,
    set both min_fraction_threshold and max_fraction_threshold to 1.0.
    """

    min_fraction_threshold: float = betterproto.double_field(1)
    max_fraction_threshold: float = betterproto.double_field(2)


@dataclass
class DatasetConstraints(betterproto.Message):
    """Constraints on the entire dataset."""

    # Tests differences in number of examples between the current data and the
    # previous span.
    num_examples_drift_comparator: "NumericValueComparator" = betterproto.message_field(1)
    # Tests comparisons in number of examples between the current data and the
    # previous version of that data.
    num_examples_version_comparator: "NumericValueComparator" = betterproto.message_field(2)
    # Minimum number of examples in the dataset.
    min_examples_count: int = betterproto.int64_field(3)
    # Maximum number of examples in the dataset.
    max_examples_count: int = betterproto.int64_field(4)


@dataclass
class FixedShape(betterproto.Message):
    """
    Specifies a fixed shape for the feature's values. The immediate implication
    is that each feature has a fixed number of values. Moreover, these values
    can be parsed in a multi-dimensional tensor using the specified axis sizes.
    The FixedShape defines a lexicographical ordering of the data. For
    instance, if there is a FixedShape {   dim {size:3} dim {size:2} } then
    tensor[0][0]=field[0] then tensor[0][1]=field[1] then tensor[1][0]=field[2]
    then tensor[1][1]=field[3] then tensor[2][0]=field[4] then
    tensor[2][1]=field[5] The FixedShape message is identical with the
    TensorFlow TensorShape proto message.
    """

    # The dimensions that define the shape. The total number of values in each
    # example is the product of sizes of each dimension.
    dim: List["FixedShapeDim"] = betterproto.message_field(2)


@dataclass
class FixedShapeDim(betterproto.Message):
    """An axis in a multi-dimensional feature representation."""

    size: int = betterproto.int64_field(1)
    # Optional name of the tensor dimension.
    name: str = betterproto.string_field(2)


@dataclass
class ValueCount(betterproto.Message):
    """
    Limits on maximum and minimum number of values in a single example (when
    the feature is present). Use this when the minimum value count can be
    different than the maximum value count. Otherwise prefer FixedShape.
    """

    min: int = betterproto.int64_field(1)
    max: int = betterproto.int64_field(2)


@dataclass
class WeightedFeature(betterproto.Message):
    """
    Represents a weighted feature that is encoded as a combination of raw base
    features. The `weight_feature` should be a float feature with identical
    shape as the `feature`. This is useful for representing weights associated
    with categorical tokens (e.g. a TFIDF weight associated with each token).
    TODO(b/142122960): Handle WeightedCategorical end to end in TFX
    (validation, TFX Unit Testing, etc)
    """

    # Name for the weighted feature. This should not clash with other features in
    # the same schema.
    name: str = betterproto.string_field(1)
    # Path of a base feature to be weighted. Required.
    feature: "Path" = betterproto.message_field(2)
    # Path of weight feature to associate with the base feature. Must be same
    # shape as feature. Required.
    weight_feature: "Path" = betterproto.message_field(3)
    # The lifecycle_stage determines where a feature is expected to be used, and
    # therefore how important issues with it are.
    lifecycle_stage: "LifecycleStage" = betterproto.enum_field(4)


@dataclass
class SparseFeature(betterproto.Message):
    """
    A sparse feature represents a sparse tensor that is encoded with a
    combination of raw features, namely index features and a value feature.
    Each index feature defines a list of indices in a different dimension.
    """

    # Name for the sparse feature. This should not clash with other features in
    # the same schema.
    name: str = betterproto.string_field(1)
    # This field is no longer supported. Instead, use: lifecycle_stage:
    # DEPRECATED TODO(b/111450258): remove this.
    deprecated: bool = betterproto.bool_field(2)
    # The lifecycle_stage determines where a feature is expected to be used, and
    # therefore how important issues with it are.
    lifecycle_stage: "LifecycleStage" = betterproto.enum_field(7)
    # Constraints on the presence of this feature in examples. Deprecated, this
    # is inferred by the referred features.
    presence: "FeaturePresence" = betterproto.message_field(4)
    # Shape of the sparse tensor that this SparseFeature represents. Currently
    # not supported. TODO(b/109669962): Consider deriving this from the referred
    # features.
    dense_shape: "FixedShape" = betterproto.message_field(5)
    # Features that represent indexes. Should be integers >= 0.
    index_feature: List["SparseFeatureIndexFeature"] = betterproto.message_field(6)
    # If true then the index values are already sorted lexicographically.
    is_sorted: bool = betterproto.bool_field(8)
    value_feature: "SparseFeatureValueFeature" = betterproto.message_field(9)
    # Type of value feature. Deprecated, this is inferred by the referred
    # features.
    type: "FeatureType" = betterproto.enum_field(10)


@dataclass
class SparseFeatureIndexFeature(betterproto.Message):
    # Name of the index-feature. This should be a reference to an existing
    # feature in the schema.
    name: str = betterproto.string_field(1)


@dataclass
class SparseFeatureValueFeature(betterproto.Message):
    # Name of the value-feature. This should be a reference to an existing
    # feature in the schema.
    name: str = betterproto.string_field(1)


@dataclass
class DistributionConstraints(betterproto.Message):
    """
    Models constraints on the distribution of a feature's values.
    TODO(martinz): replace min_domain_mass with max_off_domain (but slowly).
    """

    # The minimum fraction (in [0,1]) of values across all examples that should
    # come from the feature's domain, e.g.:   1.0  => All values must come from
    # the domain.    .9  => At least 90% of the values must come from the domain.
    min_domain_mass: float = betterproto.double_field(1)


@dataclass
class FeatureCoverageConstraints(betterproto.Message):
    """Encodes vocabulary coverage constraints."""

    # Fraction of feature values that map to a vocab entry (i.e. are not oov).
    min_coverage: float = betterproto.float_field(1)
    # Average length of tokens. Used for cases such as wordpiece that fallback to
    # character-level tokenization.
    min_avg_token_length: float = betterproto.float_field(2)
    # String tokens to exclude when calculating min_coverage and
    # min_avg_token_length. Useful for tokens such as [PAD].
    excluded_string_tokens: List[str] = betterproto.string_field(3)
    # Integer tokens to exclude when calculating min_coverage and
    # min_avg_token_length.
    excluded_int_tokens: List[int] = betterproto.int64_field(4)
    # String tokens to treat as oov tokens (e.g. [UNK]). These tokens are also
    # excluded when calculating avg token length.
    oov_string_tokens: List[str] = betterproto.string_field(5)


@dataclass
class SequenceValueConstraints(betterproto.Message):
    """Encodes constraints on specific values in sequences."""

    int_value: int = betterproto.int64_field(1, group="value")
    string_value: str = betterproto.string_field(2, group="value")
    # Min / max number of times the value can occur in a sequence.
    min_per_sequence: int = betterproto.int64_field(3)
    max_per_sequence: int = betterproto.int64_field(4)
    # Min / max fraction of sequences that must contain the value.
    min_fraction_of_sequences: float = betterproto.float_field(5)
    max_fraction_of_sequences: float = betterproto.float_field(6)


@dataclass
class SequenceLengthConstraints(betterproto.Message):
    """Encodes constraints on sequence lengths."""

    # Token values (int and string) that are excluded when calculating sequence
    # length.
    excluded_int_value: List[int] = betterproto.int64_field(1)
    excluded_string_value: List[str] = betterproto.string_field(2)
    # Min / max sequence length.
    min_sequence_length: int = betterproto.int64_field(3)
    max_sequence_length: int = betterproto.int64_field(4)


@dataclass
class IntDomain(betterproto.Message):
    """
    Encodes information for domains of integer values. Note that FeatureType
    could be either INT or BYTES.
    """

    # Id of the domain. Required if the domain is defined at the schema level. If
    # so, then the name must be unique within the schema.
    name: str = betterproto.string_field(1)
    # Min and max values for the domain.
    min: int = betterproto.int64_field(3)
    max: int = betterproto.int64_field(4)
    # If true then the domain encodes categorical values (i.e., ids) rather than
    # ordinal values.
    is_categorical: bool = betterproto.bool_field(5)


@dataclass
class FloatDomain(betterproto.Message):
    """
    Encodes information for domains of float values. Note that FeatureType
    could be either INT or BYTES.
    """

    # Id of the domain. Required if the domain is defined at the schema level. If
    # so, then the name must be unique within the schema.
    name: str = betterproto.string_field(1)
    # Min and max values of the domain.
    min: float = betterproto.float_field(3)
    max: float = betterproto.float_field(4)
    # If true, feature should not contain NaNs.
    disallow_nan: bool = betterproto.bool_field(5)
    # If true, feature should not contain Inf or -Inf.
    disallow_inf: bool = betterproto.bool_field(6)
    # If True, this indicates that the feature is semantically an embedding. This
    # can be useful for distinguishing fixed dimensional numeric features that
    # should be fed to a prediction unmodified.
    is_embedding: bool = betterproto.bool_field(7)


@dataclass
class StructDomain(betterproto.Message):
    """
    Domain for a recursive struct. NOTE: If a feature with a StructDomain is
    deprecated, then all the child features (features and sparse_features of
    the StructDomain) are also considered to be deprecated.  Similarly child
    features can only be in environments of the parent feature.
    """

    feature: List["Feature"] = betterproto.message_field(1)
    sparse_feature: List["SparseFeature"] = betterproto.message_field(2)


@dataclass
class StringDomain(betterproto.Message):
    """Encodes information for domains of string values."""

    # Id of the domain. Required if the domain is defined at the schema level. If
    # so, then the name must be unique within the schema.
    name: str = betterproto.string_field(1)
    # The values appearing in the domain.
    value: List[str] = betterproto.string_field(2)


@dataclass
class BoolDomain(betterproto.Message):
    """
    Encodes information about the domain of a boolean attribute that encodes
    its TRUE/FALSE values as strings, or 0=false, 1=true. Note that FeatureType
    could be either INT or BYTES.
    """

    # Id of the domain. Required if the domain is defined at the schema level. If
    # so, then the name must be unique within the schema.
    name: str = betterproto.string_field(1)
    # Strings values for TRUE/FALSE.
    true_value: str = betterproto.string_field(2)
    false_value: str = betterproto.string_field(3)


@dataclass
class NaturalLanguageDomain(betterproto.Message):
    """Natural language text."""

    # Name of the vocabulary associated with the NaturalLanguageDomain. When
    # computing and validating stats using TFDV, tfdv.StatsOptions.vocab_paths
    # should map this name to a vocabulary file.
    vocabulary: str = betterproto.string_field(1)
    coverage: "FeatureCoverageConstraints" = betterproto.message_field(2)
    token_constraints: List["SequenceValueConstraints"] = betterproto.message_field(3)
    sequence_length_constraints: "SequenceLengthConstraints" = betterproto.message_field(5)
    # Specifies the location constraints as a function of the tokens specified in
    # token_constraints. String tokens will be specified by S_TOKEN_, (e.g.
    # S_(PAD)_) and integer tokens will be specified as I_#_ (e.g. I_123_). A_T_
    # will match any token that has not been specified in token_constraints.
    # Parenthesis, +, and * are supported. _ will be escapable with a \ for
    # tokens containing it (e.g. FOO\_BAR). For example, a two-sequence BERT
    # model may look as follows: S_(CLS)_ A_T_+ S_(SEP)_ A_T_+ S_(SEP)_ S_(PAD)_*
    # Note: Support for this field is not yet implemented. Please do not use.
    # TODO(b/188095987): Remove warning once field is implemented.
    location_constraint_regex: str = betterproto.string_field(4)


@dataclass
class ImageDomain(betterproto.Message):
    """Image data."""

    # If set, at least this fraction of values should be TensorFlow supported
    # images.
    minimum_supported_image_fraction: float = betterproto.float_field(1)
    # If set, image should have less than this value of undecoded byte size.
    max_image_byte_size: int = betterproto.int64_field(2)


@dataclass
class MIDDomain(betterproto.Message):
    """Knowledge graph ID, see: https://www.wikidata.org/wiki/Property:P646"""


@dataclass
class URLDomain(betterproto.Message):
    """A URL, see: https://en.wikipedia.org/wiki/URL"""


@dataclass
class TimeDomain(betterproto.Message):
    """Time or date representation."""

    # Expected format that contains a combination of regular characters and
    # special format specifiers. Format specifiers are a subset of the strptime
    # standard.
    string_format: str = betterproto.string_field(1, group="format")
    # Expected format of integer times.
    integer_format: "TimeDomainIntegerTimeFormat" = betterproto.enum_field(2, group="format")


@dataclass
class TimeOfDayDomain(betterproto.Message):
    """Time of day, without a particular date."""

    # Expected format that contains a combination of regular characters and
    # special format specifiers. Format specifiers are a subset of the strptime
    # standard.
    string_format: str = betterproto.string_field(1, group="format")
    # Expected format of integer times.
    integer_format: "TimeOfDayDomainIntegerTimeOfDayFormat" = betterproto.enum_field(
        2, group="format"
    )


@dataclass
class FeaturePresence(betterproto.Message):
    """Describes constraints on the presence of the feature in the data."""

    # Minimum fraction of examples that have this feature.
    min_fraction: float = betterproto.double_field(1)
    # Minimum number of examples that have this feature.
    min_count: int = betterproto.int64_field(2)


@dataclass
class FeaturePresenceWithinGroup(betterproto.Message):
    """
    Records constraints on the presence of a feature inside a "group" context
    (e.g., .presence inside a group of features that define a sequence).
    """

    required: bool = betterproto.bool_field(1)


@dataclass
class InfinityNorm(betterproto.Message):
    """
    Checks that the L-infinity norm is below a certain threshold between the
    two discrete distributions. Since this is applied to a
    FeatureNameStatistics, it only considers the top k. L_infty(p,q) = max_i
    |p_i-q_i|
    """

    # The InfinityNorm is in the interval [0.0, 1.0] so sensible bounds should be
    # in the interval [0.0, 1.0).
    threshold: float = betterproto.double_field(1)


@dataclass
class JensenShannonDivergence(betterproto.Message):
    """
    Checks that the approximate Jensen-Shannon Divergence is below a certain
    threshold between the two distributions.
    """

    # The JensenShannonDivergence will be in the interval [0.0, 1.0] so sensible
    # bounds should be in the interval [0.0, 1.0).
    threshold: float = betterproto.double_field(1)


@dataclass
class FeatureComparator(betterproto.Message):
    infinity_norm: "InfinityNorm" = betterproto.message_field(1)
    jensen_shannon_divergence: "JensenShannonDivergence" = betterproto.message_field(2)


@dataclass
class UniqueConstraints(betterproto.Message):
    """
    Checks that the number of unique values is greater than or equal to the
    min, and less than or equal to the max.
    """

    min: int = betterproto.int64_field(1)
    max: int = betterproto.int64_field(2)


@dataclass
class TensorRepresentation(betterproto.Message):
    """
    A TensorRepresentation captures the intent for converting columns in a
    dataset to TensorFlow Tensors (or more generally, tf.CompositeTensors).
    Note that one tf.CompositeTensor may consist of data from multiple columns,
    for example, a N-dimensional tf.SparseTensor may need N + 1 columns to
    provide the sparse indices and values. Note that the "column name" that a
    TensorRepresentation needs is a string, not a Path -- it means that the
    column name identifies a top-level Feature in the schema (i.e. you cannot
    specify a Feature nested in a STRUCT Feature).
    """

    dense_tensor: "TensorRepresentationDenseTensor" = betterproto.message_field(1, group="kind")
    varlen_sparse_tensor: "TensorRepresentationVarLenSparseTensor" = betterproto.message_field(
        2, group="kind"
    )
    sparse_tensor: "TensorRepresentationSparseTensor" = betterproto.message_field(3, group="kind")
    ragged_tensor: "TensorRepresentationRaggedTensor" = betterproto.message_field(4, group="kind")


@dataclass
class TensorRepresentationDefaultValue(betterproto.Message):
    float_value: float = betterproto.double_field(1, group="kind")
    # Note that the data column might be of a shorter integral type. It's the
    # user's responsitiblity to make sure the default value fits that type.
    int_value: int = betterproto.int64_field(2, group="kind")
    bytes_value: bytes = betterproto.bytes_field(3, group="kind")
    # uint_value should only be used if the default value can't fit in a int64
    # (`int_value`).
    uint_value: int = betterproto.uint64_field(4, group="kind")


@dataclass
class TensorRepresentationDenseTensor(betterproto.Message):
    """A tf.Tensor"""

    # Identifies the column in the dataset that provides the values of this
    # Tensor.
    column_name: str = betterproto.string_field(1)
    # The shape of each row of the data (i.e. does not include the batch
    # dimension)
    shape: "FixedShape" = betterproto.message_field(2)
    # If this column is missing values in a row, the default_value will be used
    # to fill that row.
    default_value: "TensorRepresentationDefaultValue" = betterproto.message_field(3)


@dataclass
class TensorRepresentationVarLenSparseTensor(betterproto.Message):
    """A ragged tf.SparseTensor that models nested lists."""

    # Identifies the column in the dataset that should be converted to the
    # VarLenSparseTensor.
    column_name: str = betterproto.string_field(1)


@dataclass
class TensorRepresentationSparseTensor(betterproto.Message):
    """
    A tf.SparseTensor whose indices and values come from separate data columns.
    This will replace Schema.sparse_feature eventually. The index columns must
    be of INT type, and all the columns must co-occur and have the same valency
    at the same row.
    """

    # The dense shape of the resulting SparseTensor (does not include the batch
    # dimension).
    dense_shape: "FixedShape" = betterproto.message_field(1)
    # The columns constitute the coordinates of the values. indices_column[i][j]
    # contains the coordinate of the i-th dimension of the j-th value.
    index_column_names: List[str] = betterproto.string_field(2)
    # The column that contains the values.
    value_column_name: str = betterproto.string_field(3)


@dataclass
class TensorRepresentationRaggedTensor(betterproto.Message):
    """
    A tf.RaggedTensor that models nested lists. Currently there is no way for
    the user to specify the shape of the leaf value (the innermost value tensor
    of the RaggedTensor). The leaf value will always be a 1-D tensor.
    """

    # Identifies the leaf feature that provides values of the RaggedTensor.
    # struct type sub fields. The first step of the path refers to a top-level
    # feature in the data. The remaining steps refer to STRUCT features under the
    # top-level feature, recursively. If the feature has N outer ragged lists,
    # they will become the first N dimensions of the resulting RaggedTensor and
    # the contents will become the flat_values.
    feature_path: "Path" = betterproto.message_field(1)
    # The result RaggedTensor would be of shape: [B, D_0, D_1, ..., D_N, P_0,
    # P_1, ..., P_M, U_0, U_1, ..., U_P] Where the dimensions belong to different
    # categories: * B: Batch size dimension * D_n: Dimensions specified by the
    # nested structure specified by the value path until the leaf node. n>=1. *
    # P_m: Dimensions specified by the partitions that do not define any fixed
    # diomension size. m>=0. * U_0: Dimensions specified by the latest partitions
    # of type uniform_row_length that can define the fixed inner shape of the
    # tensor. If iterationg the partitions from the end to the beginning, these
    # dimensions are defined by all the continuous uniform_row_length partitions
    # present. p>=0.
    partition: List["TensorRepresentationRaggedTensorPartition"] = betterproto.message_field(3)
    # The data type of the ragged tensor's row partitions. This will default to
    # INT64 if it is not specified.
    row_partition_dtype: "TensorRepresentationRowPartitionDType" = betterproto.enum_field(2)


@dataclass
class TensorRepresentationRaggedTensorPartition(betterproto.Message):
    """Further partition of the feature values at the leaf level."""

    # If the final element(s) of partition are uniform_row_lengths [U0, U1, ...]
    # , then the result RaggedTensor will have  their flat values (a dense
    # tensor) being of shape [U0, U1, ...]. Otherwise, a uniform_row_length
    # simply means a ragged dimension with row_lengths
    # [uniform_row_length]*nrows.
    uniform_row_length: int = betterproto.int64_field(1, group="kind")
    # Identifies a leaf feature who share the same parent of value_feature_path
    # that contains the partition row lengths.
    row_length: str = betterproto.string_field(2, group="kind")


@dataclass
class TensorRepresentationGroup(betterproto.Message):
    """
    A TensorRepresentationGroup is a collection of TensorRepresentations with
    names. These names may serve as identifiers when converting the dataset to
    a collection of Tensors or tf.CompositeTensors. For example, given the
    following group: {   key: "dense_tensor"   tensor_representation {
    dense_tensor {       column_name: "univalent_feature"       shape {
    dim {           size: 1         }       }       default_value {
    float_value: 0       }     }   } } {   key: "varlen_sparse_tensor"
    tensor_representation {     varlen_sparse_tensor {       column_name:
    "multivalent_feature"     }   } } Then the schema is expected to have
    feature "univalent_feature" and "multivalent_feature", and when a batch of
    data is converted to Tensors using this TensorRepresentationGroup, the
    result may be the following dict: {   "dense_tensor": tf.Tensor(...),
    "varlen_sparse_tensor": tf.SparseTensor(...), }
    """

    tensor_representation: Dict[str, "TensorRepresentation"] = betterproto.map_field(
        1, betterproto.TYPE_STRING, betterproto.TYPE_MESSAGE
    )
