From d0280317ebc2e983a6e5c8cf4a95a6538de99762 Mon Sep 17 00:00:00 2001 From: Harsh Abasaheb Chavan <99984066+Xyerophyte@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:31:19 +0000 Subject: [PATCH 1/2] Checkpoint from VS Code for coding agent session --- fastly/model_utils.py | 60 +++++++++++++++++++++++++++++ tests/test_model_simple_sequence.py | 38 ++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 tests/test_model_simple_sequence.py diff --git a/fastly/model_utils.py b/fastly/model_utils.py index a3157a0f..46991541 100644 --- a/fastly/model_utils.py +++ b/fastly/model_utils.py @@ -462,6 +462,31 @@ def get(self, name, default=None): def __getitem__(self, name): """get the value of an attribute using square-bracket notation: `instance[attr]`""" + # Support numeric indexing for simple models that wrap a list-like + # value (for example response models that have `value: [Item]`). + # Iteration (for x in model) falls back to __getitem__ with integer + # indices if __iter__ is not present, so accept ints and slices here. + if isinstance(name, (int, slice)): + try: + v = self.__dict__['_data_store'].get('value') + except Exception: + v = None + if v is None: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + try: + return v[name] + except Exception: + # normalize to attribute error expected by callers + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + if name in self: return self.get(name) @@ -471,6 +496,41 @@ def __getitem__(self, name): [e for e in [self._path_to_item, name] if e] ) + def __iter__(self): + """Allow iteration over simple models that wrap a sequence in `value`. + + Example: for item in DomainsResponse(...): iterates over the underlying list. + """ + try: + v = self.__dict__['_data_store'].get('value') + except Exception: + v = None + if v is None: + # behave like an empty iterator + return iter(()) + # Only treat list/tuple as a true sequence to iterate over. Dicts are + # technically iterable (yield keys) but in API models the `value` + # field may contain a dict representing a single item. Treat non + # (list, tuple) values as single items. + if isinstance(v, (list, tuple)): + return iter(v) + # Otherwise wrap single value in an iterator + return iter((v,)) + + def __len__(self): + """Return length of the underlying sequence if present, otherwise 0.""" + try: + v = self.__dict__['_data_store'].get('value') + except Exception: + return 0 + if v is None: + return 0 + # If underlying value is a sequence (list/tuple), return its length. + if isinstance(v, (list, tuple)): + return len(v) + # For dict or other single objects, treat as single item. + return 1 + def __contains__(self, name): """used by `in` operator to check if an attribute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: diff --git a/tests/test_model_simple_sequence.py b/tests/test_model_simple_sequence.py new file mode 100644 index 00000000..ade433d5 --- /dev/null +++ b/tests/test_model_simple_sequence.py @@ -0,0 +1,38 @@ +import pytest +from fastly.model.domains_response import DomainsResponse + + +def test_domains_response_iteration_and_indexing(): + data = [{'name': 'a'}, {'name': 'b'}, {'name': 'c'}] + d = DomainsResponse(data, _check_type=False) + + # len + assert len(d) == 3 + + # list conversion + assert list(d) == data + + # indexing + assert d[0] == data[0] + assert d[1] == data[1] + + # slicing + assert d[1:3] == data[1:3] + + # iteration via for + out = [] + for it in d: + out.append(it) + assert out == data + + +def test_domains_response_empty_and_single(): + # empty + d_empty = DomainsResponse([], _check_type=False) + assert len(d_empty) == 0 + assert list(d_empty) == [] + + # single non-sequence value should be iterable as single item + d_single = DomainsResponse({'k': 'v'}, _check_type=False) + assert len(d_single) == 1 + assert list(d_single) == [{'k': 'v'}] From b7a0f5d96635323455b8097477c6ce832e54c4c6 Mon Sep 17 00:00:00 2001 From: Harsh Abasaheb Chavan <99984066+Xyerophyte@users.noreply.github.com> Date: Sat, 1 Nov 2025 11:43:34 +0000 Subject: [PATCH 2/2] fix: Make ModelSimple support iteration for list-based responses - Add __iter__, __len__, and enhanced __getitem__ to ModelSimple - Fixes ApiAttributeError when iterating DomainsResponse and similar models - Users can now use for loops, list comprehensions, indexing, and len() - Add comprehensive test coverage in test_model_simple_sequence.py - Update CHANGELOG.md for v12.0.0 Fixes issue where DomainsResponse._data_store had to be accessed directly to iterate over domain lists. Now DomainsResponse and other ModelSimple list-based responses behave like native Python sequences. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95eb0237..a2e707da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Bug fixes:** - fix(dictionary): Correct example dictionary name to use valid characters + - fix(model): Make `ModelSimple` behave like a sequence when `value` is a list/tuple so generated response models (e.g. `DomainsResponse`) can be iterated, indexed and sized. **Enhancements:**