# -*- coding: utf-8 -*- # # Copyright 2025 Google LLC. All Rights Reserved. # # 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. """Wrapper for Cloud Run InstanceSplit messages.""" from __future__ import absolute_import from __future__ import annotations from __future__ import division from __future__ import print_function from __future__ import unicode_literals import collections from googlecloudsdk.core import exceptions from googlecloudsdk.generated_clients.apis.run.v1 import run_v1_messages try: # Python 3.3 and above. collections_abc = collections.abc except AttributeError: collections_abc = collections class InvalidInstanceSplitSpecificationError(exceptions.Error): """Error to indicate an invalid instance split specification.""" pass # Designated key value for latest. # Revisions' names may not be uppercase, so this is distinct. LATEST_REVISION_KEY = 'LATEST' def NewInstanceSplit(messages, key: str, percent=None): """Creates a new InstanceSplit. Args: messages: The message module that defines InstanceSplit. key: The key for the instance split assignment in the InstanceSplits mapping. percent: Optional percent of instance split to assign. Returns: The newly created InstanceSplit. """ if key == LATEST_REVISION_KEY: result = messages.InstanceSplit(latestRevision=True, percent=percent) else: result = messages.InstanceSplit(revisionName=key, percent=percent) return result def GetKey(split: run_v1_messages.InstanceSplit): """Returns the key for a InstanceSplit. Args: split: InstanceSplit, the InstanceSplit to check Returns: LATEST_REVISION_KEY if split is for the latest revison or split.revisionName if not. """ return LATEST_REVISION_KEY if split.latestRevision else split.revisionName def SortKeyFromKey(key: str): """Sorted key function to order InstanceSplit keys. InstanceSplits keys are one of: o revisionName o LATEST_REVISION_KEY Note LATEST_REVISION_KEY is not a str so its ordering with respect to revisionName keys is hard to predict. Args: key: Key for a InstanceSplits dictionary. Returns: A value that sorts by revisionName with LATEST_REVISION_KEY last. """ if key == LATEST_REVISION_KEY: result = (2, key) else: result = (1, key) return result def SortKeyFromSplit(split: run_v1_messages.InstanceSplit): """Sorted key function to order InstanceSplit objects by key. Args: split: A InstanceSplit. Returns: A value that sorts by revisionName with LATEST_REVISION_KEY last. """ key = GetKey(split) return SortKeyFromKey(key) def _GetItemSortKey(split: run_v1_messages.InstanceSplit): """Key function for sorting InstanceSplit objects during __getitem__.""" # The list of InstanceSplits returned by InstanceSplits.__getitem__ needs to # be sorted for comparisons on InstanceSplits instances to work correctly. The # order of the list of instance split assignments for a given key should not # affect equality. InstanceSplit is not hashable so a set is not an option. percent = split.percent if split.percent else 0 return percent class InstanceSplits(collections_abc.MutableMapping): """Wraps a repeated InstanceSplit message and provides dict-like access. The dictionary key is one of LATEST_REVISION_KEY for the latest revision InstanceSplit.revisionName for InstanceSplits with a revision name. The dictionary value is a list of all instance split assignments referencing the same revision, either by name or the latest revision. """ def __init__(self, messages_module, to_wrap): """Constructs a new InstanceSplits instance. The InstanceSplits instance wraps the to_wrap argument, which is a repeated proto message. Operations that mutate to_wrap will usually occur through this class, but that is not a requirement. Callers can directly mutate to_wrap by accessing the proto directly. Args: messages_module: The message module that defines InstanceSplit. to_wrap: The instance split assignments to wrap. """ self._messages = messages_module self._m = to_wrap self._instance_split_cls = self._messages.InstanceSplit def __getitem__(self, key): """Gets a sorted list of instance split assignments associated with the given key. Allows accessing instance split assignments based on the revision they reference (either directly by name or the latest ready revision by specifying "LATEST" as the key). Returns a sorted list of instance split assignments to support comparison operations on InstanceSplits objects which should be independent of the order of the instance split assignments for a given key. Args: key: A revision name or "LATEST" to get the instance split assignments for. Returns: A sorted list of instance split assignments associated with the given key. Raises: KeyError: If this object does not contain the given key. """ result = sorted( (i for i in self._m if GetKey(i) == key), key=_GetItemSortKey ) if not result: raise KeyError(key) return result def __delitem__(self, key): """Not implemented for now.""" raise NotImplementedError() def __setitem__(self, key, new_splits): """Not implemented for now.""" raise NotImplementedError() def __contains__(self, key): """Implements evaluation of `item in self`.""" for split in self._m: if key == GetKey(split): return True return False @property def _key_set(self): """A set containing the mapping's keys.""" return set(GetKey(i) for i in self._m) def __len__(self): """Implements evaluation of `len(self)`.""" return len(self._key_set) def __iter__(self): """Returns an iterator over the instance split assignment keys.""" return iter(self._key_set) def MakeSerializable(self): return self._m def __repr__(self): content = ', '.join('{}: {}'.format(k, v) for k, v in self.items()) return '[%s]' % content def AddSplit(self, key, percent): """Add a new instance split assignments for the given key. Mainly for testing. Args: key: Name of the revision (or "LATEST") to set the percent for. percent: Percent of instance split to set. """ self._m.append(NewInstanceSplit(self._messages, key, percent))