diff --git a/elasticsearch_dsl/response/__init__.py b/elasticsearch_dsl/response/__init__.py index f8b20acf..fcc5bdc3 100644 --- a/elasticsearch_dsl/response/__init__.py +++ b/elasticsearch_dsl/response/__init__.py @@ -26,7 +26,6 @@ Optional, Sequence, Tuple, - TypedDict, Union, cast, ) @@ -196,7 +195,7 @@ def search_after(self) -> "SearchBase[_R]": return self._search.extra(search_after=self.hits[-1].meta.sort) # type: ignore -_Aggregate = Union[ +AggregateResponseType = Union[ "types.CardinalityAggregate", "types.HdrPercentilesAggregate", "types.HdrPercentileRanksAggregate", @@ -268,28 +267,28 @@ def search_after(self) -> "SearchBase[_R]": "types.MatrixStatsAggregate", "types.GeoLineAggregate", ] -_AggResponseMeta = TypedDict( - "_AggResponseMeta", {"search": "Request[_R]", "aggs": Mapping[str, _Aggregate]} -) class AggResponse(AttrDict[Any], Generic[_R]): + """An Elasticsearch aggregation response.""" + _meta: Dict[str, Any] def __init__(self, aggs: "Agg[_R]", search: "Request[_R]", data: Dict[str, Any]): super(AttrDict, self).__setattr__("_meta", {"search": search, "aggs": aggs}) super().__init__(data) - def __getitem__(self, attr_name: str) -> _Aggregate: + def __getitem__(self, attr_name: str) -> AggregateResponseType: if attr_name in self._meta["aggs"]: # don't do self._meta['aggs'][attr_name] to avoid copying agg = self._meta["aggs"].aggs[attr_name] return cast( - _Aggregate, agg.result(self._meta["search"], self._d_[attr_name]) + AggregateResponseType, + agg.result(self._meta["search"], self._d_[attr_name]), ) return super().__getitem__(attr_name) # type: ignore - def __iter__(self) -> Iterator[_Aggregate]: # type: ignore[override] + def __iter__(self) -> Iterator[AggregateResponseType]: # type: ignore[override] for name in self._meta["aggs"]: yield self[name] diff --git a/elasticsearch_dsl/types.py b/elasticsearch_dsl/types.py index 08eaade0..24cf2455 100644 --- a/elasticsearch_dsl/types.py +++ b/elasticsearch_dsl/types.py @@ -4029,13 +4029,17 @@ def __init__(self, *, wkt: Union[str, DefaultType] = DEFAULT, **kwargs: Any): class AdjacencyMatrixAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["AdjacencyMatrixBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "AdjacencyMatrixBucket"]: + return self.buckets # type: ignore + class AdjacencyMatrixBucket(AttrDict[Any]): """ @@ -4194,7 +4198,7 @@ class ArrayPercentilesItem(AttrDict[Any]): class AutoDateHistogramAggregate(AttrDict[Any]): """ :arg interval: (required) - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -4202,6 +4206,10 @@ class AutoDateHistogramAggregate(AttrDict[Any]): buckets: Sequence["DateHistogramBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "DateHistogramBucket"]: + return self.buckets # type: ignore + class AvgAggregate(AttrDict[Any]): """ @@ -4407,7 +4415,7 @@ class CompletionSuggestOption(AttrDict[Any]): class CompositeAggregate(AttrDict[Any]): """ :arg after_key: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -4415,6 +4423,10 @@ class CompositeAggregate(AttrDict[Any]): buckets: Sequence["CompositeBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "CompositeBucket"]: + return self.buckets # type: ignore + class CompositeBucket(AttrDict[Any]): """ @@ -4442,13 +4454,17 @@ class CumulativeCardinalityAggregate(AttrDict[Any]): class DateHistogramAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["DateHistogramBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "DateHistogramBucket"]: + return self.buckets # type: ignore + class DateHistogramBucket(AttrDict[Any]): """ @@ -4468,13 +4484,17 @@ class DateRangeAggregate(AttrDict[Any]): aggregation: `from` and `to` in `buckets` are milliseconds since the Epoch, represented as a floating point number. - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["RangeBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "RangeBucket"]: + return self.buckets # type: ignore + class DerivativeAggregate(AttrDict[Any]): """ @@ -4567,7 +4587,7 @@ class DoubleTermsAggregate(AttrDict[Any]): :arg doc_count_error_upper_bound: :arg sum_other_doc_count: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -4576,6 +4596,10 @@ class DoubleTermsAggregate(AttrDict[Any]): buckets: Sequence["DoubleTermsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "DoubleTermsBucket"]: + return self.buckets # type: ignore + class DoubleTermsBucket(AttrDict[Any]): """ @@ -4808,13 +4832,17 @@ class FilterAggregate(AttrDict[Any]): class FiltersAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["FiltersBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "FiltersBucket"]: + return self.buckets # type: ignore + class FiltersBucket(AttrDict[Any]): """ @@ -4826,13 +4854,17 @@ class FiltersBucket(AttrDict[Any]): class FrequentItemSetsAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["FrequentItemSetsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "FrequentItemSetsBucket"]: + return self.buckets # type: ignore + class FrequentItemSetsBucket(AttrDict[Any]): """ @@ -4878,23 +4910,31 @@ class GeoDistanceAggregate(AttrDict[Any]): Result of a `geo_distance` aggregation. The unit for `from` and `to` is meters by default. - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["RangeBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "RangeBucket"]: + return self.buckets # type: ignore + class GeoHashGridAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["GeoHashGridBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "GeoHashGridBucket"]: + return self.buckets # type: ignore + class GeoHashGridBucket(AttrDict[Any]): """ @@ -4908,13 +4948,17 @@ class GeoHashGridBucket(AttrDict[Any]): class GeoHexGridAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["GeoHexGridBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "GeoHexGridBucket"]: + return self.buckets # type: ignore + class GeoHexGridBucket(AttrDict[Any]): """ @@ -4954,13 +4998,17 @@ class GeoLineAggregate(AttrDict[Any]): class GeoTileGridAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["GeoTileGridBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "GeoTileGridBucket"]: + return self.buckets # type: ignore + class GeoTileGridBucket(AttrDict[Any]): """ @@ -5004,13 +5052,17 @@ class HdrPercentilesAggregate(AttrDict[Any]): class HistogramAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["HistogramBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "HistogramBucket"]: + return self.buckets # type: ignore + class HistogramBucket(AttrDict[Any]): """ @@ -5145,13 +5197,17 @@ class InnerHitsResult(AttrDict[Any]): class IpPrefixAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["IpPrefixBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "IpPrefixBucket"]: + return self.buckets # type: ignore + class IpPrefixBucket(AttrDict[Any]): """ @@ -5171,13 +5227,17 @@ class IpPrefixBucket(AttrDict[Any]): class IpRangeAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["IpRangeBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "IpRangeBucket"]: + return self.buckets # type: ignore + class IpRangeBucket(AttrDict[Any]): """ @@ -5280,13 +5340,17 @@ class LongRareTermsAggregate(AttrDict[Any]): Result of the `rare_terms` aggregation when the field is some kind of whole number like a integer, long, or a date. - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["LongRareTermsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "LongRareTermsBucket"]: + return self.buckets # type: ignore + class LongRareTermsBucket(AttrDict[Any]): """ @@ -5307,7 +5371,7 @@ class LongTermsAggregate(AttrDict[Any]): :arg doc_count_error_upper_bound: :arg sum_other_doc_count: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -5316,6 +5380,10 @@ class LongTermsAggregate(AttrDict[Any]): buckets: Sequence["LongTermsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "LongTermsBucket"]: + return self.buckets # type: ignore + class LongTermsBucket(AttrDict[Any]): """ @@ -5421,7 +5489,7 @@ class MultiTermsAggregate(AttrDict[Any]): """ :arg doc_count_error_upper_bound: :arg sum_other_doc_count: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -5430,6 +5498,10 @@ class MultiTermsAggregate(AttrDict[Any]): buckets: Sequence["MultiTermsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "MultiTermsBucket"]: + return self.buckets # type: ignore + class MultiTermsBucket(AttrDict[Any]): """ @@ -5587,13 +5659,17 @@ class QueryProfile(AttrDict[Any]): class RangeAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["RangeBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "RangeBucket"]: + return self.buckets # type: ignore + class RangeBucket(AttrDict[Any]): """ @@ -5739,7 +5815,7 @@ class SignificantLongTermsAggregate(AttrDict[Any]): """ :arg bg_count: :arg doc_count: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -5748,6 +5824,10 @@ class SignificantLongTermsAggregate(AttrDict[Any]): buckets: Sequence["SignificantLongTermsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "SignificantLongTermsBucket"]: + return self.buckets # type: ignore + class SignificantLongTermsBucket(AttrDict[Any]): """ @@ -5769,7 +5849,7 @@ class SignificantStringTermsAggregate(AttrDict[Any]): """ :arg bg_count: :arg doc_count: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -5778,6 +5858,10 @@ class SignificantStringTermsAggregate(AttrDict[Any]): buckets: Sequence["SignificantStringTermsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "SignificantStringTermsBucket"]: + return self.buckets # type: ignore + class SignificantStringTermsBucket(AttrDict[Any]): """ @@ -5902,13 +5986,17 @@ class StringRareTermsAggregate(AttrDict[Any]): """ Result of the `rare_terms` aggregation when the field is a string. - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["StringRareTermsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "StringRareTermsBucket"]: + return self.buckets # type: ignore + class StringRareTermsBucket(AttrDict[Any]): """ @@ -5952,7 +6040,7 @@ class StringTermsAggregate(AttrDict[Any]): :arg doc_count_error_upper_bound: :arg sum_other_doc_count: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -5961,6 +6049,10 @@ class StringTermsAggregate(AttrDict[Any]): buckets: Sequence["StringTermsBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "StringTermsBucket"]: + return self.buckets # type: ignore + class StringTermsBucket(AttrDict[Any]): """ @@ -6055,13 +6147,17 @@ class TermSuggestOption(AttrDict[Any]): class TimeSeriesAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["TimeSeriesBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "TimeSeriesBucket"]: + return self.buckets # type: ignore + class TimeSeriesBucket(AttrDict[Any]): """ @@ -6118,13 +6214,17 @@ class UnmappedRareTermsAggregate(AttrDict[Any]): Result of a `rare_terms` aggregation when the field is unmapped. `buckets` is always empty. - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence[Any] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, Any]: + return self.buckets # type: ignore + class UnmappedSamplerAggregate(AttrDict[Any]): """ @@ -6143,7 +6243,7 @@ class UnmappedSignificantTermsAggregate(AttrDict[Any]): :arg bg_count: :arg doc_count: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -6152,6 +6252,10 @@ class UnmappedSignificantTermsAggregate(AttrDict[Any]): buckets: Sequence[Any] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, Any]: + return self.buckets # type: ignore + class UnmappedTermsAggregate(AttrDict[Any]): """ @@ -6160,7 +6264,7 @@ class UnmappedTermsAggregate(AttrDict[Any]): :arg doc_count_error_upper_bound: :arg sum_other_doc_count: - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ @@ -6169,6 +6273,10 @@ class UnmappedTermsAggregate(AttrDict[Any]): buckets: Sequence[Any] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, Any]: + return self.buckets # type: ignore + class ValueCountAggregate(AttrDict[Any]): """ @@ -6188,13 +6296,17 @@ class ValueCountAggregate(AttrDict[Any]): class VariableWidthHistogramAggregate(AttrDict[Any]): """ - :arg buckets: (required) the aggregation buckets + :arg buckets: (required) the aggregation buckets as a list :arg meta: """ buckets: Sequence["VariableWidthHistogramBucket"] meta: Mapping[str, Any] + @property + def buckets_as_dict(self) -> Mapping[str, "VariableWidthHistogramBucket"]: + return self.buckets # type: ignore + class VariableWidthHistogramBucket(AttrDict[Any]): """ diff --git a/utils/generator.py b/utils/generator.py index ae53f228..40272932 100644 --- a/utils/generator.py +++ b/utils/generator.py @@ -99,6 +99,18 @@ def add_not_set(type_): return type_ +def type_for_types_py(type_): + """Converts a type rendered in a generic way to the format needed in the + types.py module. + """ + type_ = type_.replace('"DefaultType"', "DefaultType") + type_ = type_.replace('"InstrumentedField"', "InstrumentedField") + type_ = re.sub(r'"(function\.[a-zA-Z0-9_]+)"', r"\1", type_) + type_ = re.sub(r'"types\.([a-zA-Z0-9_]+)"', r'"\1"', type_) + type_ = re.sub(r'"(wrappers\.[a-zA-Z0-9_]+)"', r"\1", type_) + return type_ + + class ElasticsearchSchema: """Operations related to the Elasticsearch schema.""" @@ -361,11 +373,7 @@ def add_attribute(self, k, arg, for_types_py=False, for_response=False): type_ = add_dict_type(type_) # interfaces can be given as dicts type_ = add_not_set(type_) if for_types_py: - type_ = type_.replace('"DefaultType"', "DefaultType") - type_ = type_.replace('"InstrumentedField"', "InstrumentedField") - type_ = re.sub(r'"(function\.[a-zA-Z0-9_]+)"', r"\1", type_) - type_ = re.sub(r'"types\.([a-zA-Z0-9_]+)"', r'"\1"', type_) - type_ = re.sub(r'"(wrappers\.[a-zA-Z0-9_]+)"', r"\1", type_) + type_ = type_for_types_py(type_) required = "(required) " if arg["required"] else "" server_default = ( f" Defaults to `{arg['serverDefault']}` if omitted." @@ -630,6 +638,9 @@ def interface_to_python_class( "required": bool, "positional": bool, ], + "buckets_as_dict": "type" # optional, only present in aggregation response + # classes that have buckets that can have a list + # or dict representation } ``` """ @@ -658,13 +669,14 @@ def interface_to_python_class( } ) elif interface == "ResponseBody" and arg["name"] == "aggregations": - # Aggregations are tricky because the DSL client uses a more - # flexible class than what can be generated from the schema. + # Aggregations are tricky because the DSL client uses a + # flexible representation that is difficult to generate + # from the schema. # To handle this we let the generator do its work by calling - # `add_attribute()`, but then we save this generated attribute - # apart and replace it with the existing `AggResponse` class. + # `add_attribute()`, but then we save the generated attribute + # apart and replace it with the DSL's `AggResponse` class. # The generated type is then used in type hints in variables - # and ethods of this class. + # and methods of this class. self.add_attribute( k, arg, for_types_py=for_types_py, for_response=for_response ) @@ -684,7 +696,18 @@ def interface_to_python_class( and type_["name"]["name"] == "MultiBucketAggregateBase" and arg["name"] == "buckets" ): - print("**", interface, generics) + # Also during aggregation response generation, the "buckets" + # attribute that many aggregation responses have is very + # complex, supporting over a dozen different aggregation + # types via generics, each in array or object configurations. + # Typing this attribute proved very difficult. A solution + # that worked with mypy and pyright is to type "buckets" + # with the array (list) form, and create a `buckets_as_dict` + # property that is typed appropriate for accessing the + # buckets when in object (dictionary) form. + # The generic type is assumed to be the first in the list, + # which is a simplification that should be removed when a + # more complete implementation of generic is added. if generics[0]["type"]["name"] == "Void": generic_type = "Any" else: @@ -695,19 +718,20 @@ def interface_to_python_class( generic_type, _ = self.get_python_type( _g, for_response=for_response ) - generic_type = re.sub( - r'"types\.([a-zA-Z0-9_]+)"', r'"\1"', generic_type - ) + generic_type = type_for_types_py(generic_type) k["args"].append( { "name": arg["name"], # for the type we only include the array form, since # this client does not request the dict form "type": f"Sequence[{generic_type}]", - "doc": [":arg buckets: (required) the aggregation buckets"], + "doc": [ + ":arg buckets: (required) the aggregation buckets as a list" + ], "required": True, } ) + k["buckets_as_dict"] = generic_type else: self.add_attribute( k, arg, for_types_py=for_types_py, for_response=for_response @@ -717,6 +741,10 @@ def interface_to_python_class( break if "generics" in type_["inherits"]: + # Generics are only supported for certain specific cases at this + # time. Here we just save them so that they can be recalled later + # while traversing over to parent classes to find inherited + # attributes. for generic_type in type_["inherits"]["generics"]: generics.append(generic_type) diff --git a/utils/templates/response.__init__.py.tpl b/utils/templates/response.__init__.py.tpl index 37f9d28e..a9aafc01 100644 --- a/utils/templates/response.__init__.py.tpl +++ b/utils/templates/response.__init__.py.tpl @@ -26,7 +26,6 @@ from typing import ( Optional, Sequence, Tuple, - TypedDict, Union, cast, ) @@ -174,25 +173,25 @@ class Response(AttrDict[Any], Generic[_R]): return self._search.extra(search_after=self.hits[-1].meta.sort) # type: ignore -_Aggregate = {{ response["aggregate_type"] }} -_AggResponseMeta = TypedDict("_AggResponseMeta", {"search": "Request[_R]", "aggs": Mapping[str, _Aggregate]}) +AggregateResponseType = {{ response["aggregate_type"] }} class AggResponse(AttrDict[Any], Generic[_R]): + """An Elasticsearch aggregation response.""" _meta: Dict[str, Any] def __init__(self, aggs: "Agg[_R]", search: "Request[_R]", data: Dict[str, Any]): super(AttrDict, self).__setattr__("_meta", {"search": search, "aggs": aggs}) super().__init__(data) - def __getitem__(self, attr_name: str) -> _Aggregate: + def __getitem__(self, attr_name: str) -> AggregateResponseType: if attr_name in self._meta["aggs"]: # don't do self._meta['aggs'][attr_name] to avoid copying agg = self._meta["aggs"].aggs[attr_name] - return cast(_Aggregate, agg.result(self._meta["search"], self._d_[attr_name])) + return cast(AggregateResponseType, agg.result(self._meta["search"], self._d_[attr_name])) return super().__getitem__(attr_name) # type: ignore - def __iter__(self) -> Iterator[_Aggregate]: # type: ignore[override] + def __iter__(self) -> Iterator[AggregateResponseType]: # type: ignore[override] for name in self._meta["aggs"]: yield self[name] diff --git a/utils/templates/types.py.tpl b/utils/templates/types.py.tpl index 44fbf454..0571e068 100644 --- a/utils/templates/types.py.tpl +++ b/utils/templates/types.py.tpl @@ -94,6 +94,12 @@ class {{ k.name }}({{ k.parent if k.parent else "AttrDict[Any]" }}): super().__init__(kwargs) {% endif %} {% endif %} + {% if k.buckets_as_dict %} + + @property + def buckets_as_dict(self) -> Mapping[str, {{ k.buckets_as_dict }}]: + return self.buckets # type: ignore + {% endif %} {% else %} pass {% endif %}