Repository: piccolo-orm/asyncio_tools

Issue:
gather() not working
I just tried this library and got following results with exmaple in the README file:

```
$ python3.8 test.py
Traceback (most recent call last):
  File "test.py", line 57, in <module>
    asyncio.run(main())
  File "/usr/local/Cellar/python@3.8/3.8.13_2/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/local/Cellar/python@3.8/3.8.13_2/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete
    return future.result()
  File "test.py", line 13, in main
    response = await gather(
  File "/usr/local/lib/python3.8/site-packages/asyncio_tools.py", line 137, in gather
    return GatheredResults(results)
  File "/usr/local/lib/python3.8/site-packages/asyncio_tools.py", line 51, in __init__
    self.results = results
  File "/usr/local/lib/python3.8/site-packages/asyncio_tools.py", line 63, in __setattr__
    raise ValueError("results is immutable")
ValueError: results is immutable
```

and similar results when running tests:
```
19:54 $ python --version
Python 3.10.6
(.venv) ✔ ~/src/asyncio_tools [master|…2025] 
19:54 $ python -m pytest -s
======================================================================== test session starts ========================================================================
platform darwin -- Python 3.10.6, pytest-7.1.3, pluggy-1.0.0
rootdir: /Users/janne.kujanpaa/src/asyncio_tools
collected 4 items                                                                                                                                                   

tests/test_gathered_results.py FFFF

============================================================================= FAILURES ==============================================================================
____________________________________________________________ TestGatheredResults.test_compound_exception ____________________________________________________________

self = <test_gathered_results.TestGatheredResults testMethod=test_compound_exception>

    def test_compound_exception(self):
>       response: GatheredResults = asyncio.run(
            gather(good(), bad(), good(), bad())
        )

tests/test_gathered_results.py:27: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/runners.py:44: in run
    return loop.run_until_complete(main)
/usr/local/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/base_events.py:646: in run_until_complete
    return future.result()
asyncio_tools.py:137: in gather
    return GatheredResults(results)
asyncio_tools.py:51: in __init__
    self.results = results
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <asyncio_tools.GatheredResults object at 0x104049d80>, key = 'results', value = ['OK', ValueError('Bad value'), 'OK', ValueError('Bad value')]

    def __setattr__(self, key, value):
        """
        Since we use cached_properties for most of the lookups, we don't want
        the underlying results to be changed. There should be no reason for a
        user to want to change the results, but just to be sure we raise a
        ValueError.
        """
        if key == "results":
>           raise ValueError("results is immutable")
E           ValueError: results is immutable

asyncio_tools.py:63: ValueError
________________________________________________________________ TestGatheredResults.test_exceptions ________________________________________________________________

self = <test_gathered_results.TestGatheredResults testMethod=test_exceptions>

    def test_exceptions(self):
>       response: GatheredResults = asyncio.run(gather(good(), bad(), good()))

tests/test_gathered_results.py:17: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/runners.py:44: in run
    return loop.run_until_complete(main)
/usr/local/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/base_events.py:646: in run_until_complete
    return future.result()
asyncio_tools.py:137: in gather
    return GatheredResults(results)
asyncio_tools.py:51: in __init__
    self.results = results
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <asyncio_tools.GatheredResults object at 0x10416f730>, key = 'results', value = ['OK', ValueError('Bad value'), 'OK']

    def __setattr__(self, key, value):
        """
        Since we use cached_properties for most of the lookups, we don't want
        the underlying results to be changed. There should be no reason for a
        user to want to change the results, but just to be sure we raise a
        ValueError.
        """
        if key == "results":
>           raise ValueError("results is immutable")
E           ValueError: results is immutable

asyncio_tools.py:63: ValueError
___________________________________________________________________ TestGatheredResults.test_set ____________________________________________________________________

self = <test_gathered_results.TestGatheredResults testMethod=test_set>

    def test_set(self):
>       results = GatheredResults([])

tests/test_gathered_results.py:38: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
asyncio_tools.py:51: in __init__
    self.results = results
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <asyncio_tools.GatheredResults object at 0x10417c190>, key = 'results', value = []

    def __setattr__(self, key, value):
        """
        Since we use cached_properties for most of the lookups, we don't want
        the underlying results to be changed. There should be no reason for a
        user to want to change the results, but just to be sure we raise a
        ValueError.
        """
        if key == "results":
>           raise ValueError("results is immutable")
E           ValueError: results is immutable

asyncio_tools.py:63: ValueError
________________________________________________________________ TestGatheredResults.test_successes _________________________________________________________________

self = <test_gathered_results.TestGatheredResults testMethod=test_successes>

    def test_successes(self):
>       response: GatheredResults = asyncio.run(gather(good(), bad(), good()))

tests/test_gathered_results.py:22: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/runners.py:44: in run
    return loop.run_until_complete(main)
/usr/local/Cellar/python@3.10/3.10.6_1/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/base_events.py:646: in run_until_complete
    return future.result()
asyncio_tools.py:137: in gather
    return GatheredResults(results)
asyncio_tools.py:51: in __init__
    self.results = results
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <asyncio_tools.GatheredResults object at 0x103ad4280>, key = 'results', value = ['OK', ValueError('Bad value'), 'OK']

    def __setattr__(self, key, value):
        """
        Since we use cached_properties for most of the lookups, we don't want
        the underlying results to be changed. There should be no reason for a
        user to want to change the results, but just to be sure we raise a
        ValueError.
        """
        if key == "results":
>           raise ValueError("results is immutable")
E           ValueError: results is immutable

asyncio_tools.py:63: ValueError
====================================================================== short test summary info ======================================================================
FAILED tests/test_gathered_results.py::TestGatheredResults::test_compound_exception - ValueError: results is immutable
FAILED tests/test_gathered_results.py::TestGatheredResults::test_exceptions - ValueError: results is immutable
FAILED tests/test_gathered_results.py::TestGatheredResults::test_set - ValueError: results is immutable
FAILED tests/test_gathered_results.py::TestGatheredResults::test_successes - ValueError: results is immutable
========================================================================= 4 failed in 0.34s =========================================================================
(.venv) ✔ ~/src/asyncio_tools [master|…2025] 
```

Discussion:
dantownsend: @jikuja Thanks for this! It works great.

Patch:
diff --git a/asyncio_tools.py b/asyncio_tools.py
index 3c78360..a9a6711 100644
--- a/asyncio_tools.py
+++ b/asyncio_tools.py
@@ -45,30 +45,23 @@ class CompoundException(Exception):
 class GatheredResults:
 
     # __dict__ is required for cached_property
-    __slots__ = ("results", "__dict__")
+    __slots__ = ("__results", "__dict__")
 
     def __init__(self, results: t.List[t.Any]):
-        self.results = results
+        self.__results = results
 
     ###########################################################################
 
-    def __setattr__(self, key, value):
-        """
-        Since we use cached_properties for most of the lookups, we don't want
-        the underlying results to be changed. There should be no reason for a
-        user to want to change the results, but just to be sure we raise a
-        ValueError.
-        """
-        if key == "results":
-            raise ValueError("results is immutable")
-        super().__setattr__(key, value)
+    @property
+    def results(self):
+        return self.__results
 
     @property
     def all(self) -> t.List[t.Any]:
         """
         Just a proxy.
         """
-        return self.results
+        return self.__results
 
     ###########################################################################
 


Test patch:
diff --git a/tests/test_gathered_results.py b/tests/test_gathered_results.py
index 1b69db0..61dcbd4 100644
--- a/tests/test_gathered_results.py
+++ b/tests/test_gathered_results.py
@@ -36,5 +36,12 @@ class TestGatheredResults(TestCase):
 
     def test_set(self):
         results = GatheredResults([])
-        with self.assertRaises(ValueError):
+        with self.assertRaises(AttributeError):
             results.results = None
+
+    def test_set_2(self):
+        results = asyncio.run(gather(good(), bad()))
+        with self.assertRaises(AttributeError):
+            results.results = None
+        self.assertEqual(len(results.all), 2)
+        self.assertEqual(len(results.results), 2)