XRPL-py: asyncio.run() cannot be called from a running event loop

93 Views Asked by At

I tried to execute the following example Python code in Jupyter Notebook v7.0.6:

https://xrpl-py.readthedocs.io/en/stable/source/snippets.html

from xrpl.clients import JsonRpcClient
from xrpl.models import Ledger, Tx

# References
# - https://xrpl.org/look-up-transaction-results.html
# - https://xrpl.org/parallel-networks.html#parallel-networks
# - https://xrpl.org/tx.html

# Create a client to connect to the main network
client = JsonRpcClient("https://xrplcluster.com/")

# Create a Ledger request and have the client call it
ledger_request = Ledger(ledger_index="validated", transactions=True)
ledger_response = client.request(ledger_request)

The code fails on the last line with the following error:

/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/genericpath.py:77: RuntimeWarning: coroutine 'JsonRpcBase.request_impl' was never awaited
  m = tuple(map(os.fspath, m))
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/genericpath.py:77: RuntimeWarning: coroutine 'WebsocketClient.request_impl' was never awaited
  m = tuple(map(os.fspath, m))
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[47], line 2
      1 ledger_request = Ledger(ledger_index="validated", transactions=True)
----> 2 ledger_response = client.request(ledger_request)
      3 #ledger_response2 = client2.request(ledger_request)
      4 
      5 #print(ledger_response)

File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/xrpl/clients/sync_client.py:28, in SyncClient.request(self, request)
     18 def request(self: SyncClient, request: Request) -> Response:
     19     """
     20     Makes a request with this client and returns the response.
     21 
   (...)
     26         The Response for the given Request.
     27     """
---> 28     return asyncio.run(self.request_impl(request))

File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/runners.py:33, in run(main, debug)
      9 """Execute the coroutine and return the result.
     10 
     11 This function runs the passed coroutine, taking care of
   (...)
     30     asyncio.run(main())
     31 """
     32 if events._get_running_loop() is not None:
---> 33     raise RuntimeError(
     34         "asyncio.run() cannot be called from a running event loop")
     36 if not coroutines.iscoroutine(main):
     37     raise ValueError("a coroutine was expected, got {!r}".format(main))

RuntimeError: asyncio.run() cannot be called from a running event loop

Note the same error occurs if the client was a Websocketclient:

from xrpl.clients import WebsocketClient

I see many posts regarding the same error on SO but I am not the author of xrpl-py and can't make the appropriate changes.

The solution offered is as follows:

asyncio.run error when using Jupyter Notebook

Error message when using a solution offered:

In [15]: import asyncio
    ...: from xrpl.clients import JsonRpcClient
    ...: from xrpl.models import Ledger
    ...: 

In [16]: async def fetch_ledger_data():
    ...:     client = JsonRpcClient("https://s1.ripple.com:51234")
    ...: 
    ...:     ledger_request = Ledger(ledger_index="validated", transactions=True)
    ...: 
    ...:     ledger_response = await client.request(ledger_request)
    ...: 
    ...:     return ledger_response
    ...: 

In [17]: run -m fetch_ledger_data
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[17], line 1
----> 1 get_ipython().run_line_magic('run', '-m fetch_ledger_data')

File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/IPython/core/interactiveshell.py:2456, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2454     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2455 with self.builtin_trap:
-> 2456     result = fn(*args, **kwargs)
   2458 # The code below prevents the output from being displayed
   2459 # when using magics with decorator @output_can_be_silenced
   2460 # when the last Python token in the expression is a ';'.
   2461 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/IPython/core/magics/execution.py:708, in ExecutionMagics.run(self, parameter_s, runner, file_finder)
    706 if "m" in opts:
    707     modulename = opts["m"][0]
--> 708     modpath = find_mod(modulename)
    709     if modpath is None:
    710         msg = '%r is not a valid modulename on sys.path'%modulename

File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/IPython/utils/module_paths.py:62, in find_mod(module_name)
     40 """
     41 Find module `module_name` on sys.path, and return the path to module `module_name`.
     42 
   (...)
     59     depending on above conditions.
     60 """
     61 spec = importlib.util.find_spec(module_name)
---> 62 module_path = spec.origin
     63 if module_path is None:
     64     if spec.loader in sys.meta_path:

AttributeError: 'NoneType' object has no attribute 'origin'

UPDATE-2: This is the error message I get using the updated solution. At this point I am wondering whether the xrpl-py library can even be used with IPython (Jupyter Notebooks).

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[76], line 1
----> 1 response = await fetch_ledger_data()

Cell In[75], line 8, in fetch_ledger_data()
      6 client = JsonRpcClient("https://s1.ripple.com:51234")
      7 ledger_request = Ledger(ledger_index="validated", transactions=True)
----> 8 ledger_response = await client.request(ledger_request)
      9 return ledger_response

File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/xrpl/clients/sync_client.py:28, in SyncClient.request(self, request)
     18 def request(self: SyncClient, request: Request) -> Response:
     19     """
     20     Makes a request with this client and returns the response.
     21 
   (...)
     26         The Response for the given Request.
     27     """
---> 28     return asyncio.run(self.request_impl(request))

File /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/asyncio/runners.py:33, in run(main, debug)
      9 """Execute the coroutine and return the result.
     10 
     11 This function runs the passed coroutine, taking care of
   (...)
     30     asyncio.run(main())
     31 """
     32 if events._get_running_loop() is not None:
---> 33     raise RuntimeError(
     34         "asyncio.run() cannot be called from a running event loop")
     36 if not coroutines.iscoroutine(main):
     37     raise ValueError("a coroutine was expected, got {!r}".format(main))

RuntimeError: asyncio.run() cannot be called from a running event loop
1

There are 1 best solutions below

7
str1ng On BEST ANSWER

Well, the problem is within an already running event loop, it's common thing when you're running code in the env like Jupyter Notebook, and the problem within that is that it already has an event loop running in the background - therefore asyncio.run() itself is made to run a coroutine in a fresh event loop, so that means it can't be used if there's already an event loop in progress...

Now, I know that xrpl-py library functions are async, you will need to run them in an async context, so on Jupyter notebook - by using %run to handle this. Your approach would be:

  1. Define an async function
  2. Use %run to run the async func

Something like this should be working fine:

import asyncio
from xrpl.clients import JsonRpcClient
from xrpl.models import Ledger

async def fetch_ledger_data():
    client = JsonRpcClient("https://xrplcluster.com/")

    ledger_request = Ledger(ledger_index="validated", transactions=True)

    ledger_response = await client.request(ledger_request)

    return ledger_response

# To run it you'd do it like this:
# %run -m fetch_ledger_data

UPDATE 1: Okay, since I now got a little bit of clue of what's happening here, let's re-arange the approach, as I think I confused OP with %run command. The error you get, while using %run -m fetch_ledger_data is originating by cause that Jupyter itself is trying to access the module named fetch_ledger_data - but it can't (it doesn't exist as a separate module). Therefore or should I say: "that being said", %run - command is used to execute python scripts, and not functions defined.

To address issue and provide more tailored up answer, in order for you to execute asynchronous code, you should be using await keyword directly in a notebook cell. What is Notebook Cell. Therefore, define async func, as we did before, then you place this in a cell and execute it to define the function.

import asyncio
from xrpl.clients import JsonRpcClient
from xrpl.models import Ledger

async def fetch_ledger_data():
    client = JsonRpcClient("https://xrplcluster.com/")
    ledger_request = Ledger(ledger_index="validated", transactions=True)
    ledger_response = await client.request(ledger_request)
    return ledger_response

After that, in a new cell, you will directly await the function that we just defined, why? The reason is simple, this way you will be running the async function and since you expect some result you will be waiting for it.

# Execute the function and wait for the response
response = await fetch_ledger_data()
print(response)

By this, your async function, you should be able to interact with xrpl library, without running into problems with the asyncio related errors.

When it comes to AttributeError which you mentioned, it's more likely to be an issue which is not related to asyncio and it might indicate a problem with the library or network request. (I can't really help here, I would need more context).

NEW UPDATE: I got this running by usage of nest_asyncio, you executed the following in new cell:

import nest_asyncio
nest_asyncio.apply()

You will have to update the code above to this:

async def fetch_ledger_data():
    client = JsonRpcClient("https://xrplcluster.com/")
    ledger_request = Ledger(ledger_index="validated", transactions=True)
    ledger_response = client.request(ledger_request)  # Removed 'await'
    return ledger_response

Becasue the client.request method is not designed to be awaited, you should call it without the await keyword.

Then you attempt this:

response = await fetch_ledger_data()
print(response)

Response Summary (which I got):

  • Status: SUCCESS
  • Ledger Index: 85408465
  • Ledger Hash: [REDACTED]
  • Total Coins: 99,987,960,274,971,725
  • Close Time (UTC): 2024-Jan-20 15:53:40.000000000
  • Transactions Included: [List of transaction hashes redacted for privacy]

Btw. I don't say that this is perfect solution, but it indeed does get stuff running.