Environment
OS: Windows 10
Python Version: 3.13.7
aioftp Version: 0.26.2
Problem
I think a problem in list() is it cannot handle cancellation and have the client recover properly because the buffers still contain data from the previous command. If we cancel in the middle of the list() call, aioftp.errors.StatusCodeError is raised when we try to run another command or call quit() in this case.
This can be reproduced with the following code:
import asyncio
import aioftp
import pathlib
async def run_server():
root = pathlib.Path.home()
server = aioftp.Server(users=[aioftp.User("anonymous", "", base_path=root)])
await server.start("127.0.0.1", 2121)
print("FTP server running on 127.0.0.1:2121")
await asyncio.Event().wait()
asyncio.run(run_server())
if you uncomment # clear_buffers(), the client.quit() call is able to run successfully.
import asyncio
import aioftp
import time
from contextlib import suppress
async def clear_buffers(client: aioftp.Client):
print("clearing buffers...")
while True:
await asyncio.sleep(0.250)
available = len(client.stream.reader._buffer)
if available == 0:
break
await client.stream.read(available)
async def work():
try:
client = aioftp.Client()
await client.connect("127.0.0.1", 2121)
await client.login(user="anonymous", password="")
lister = client.list("./", recursive=True, raw_command="MLSD")
print("Listing...")
results = await lister
finally:
print("DONE")
# await clear_buffers(client)
if client:
await client.quit()
async def main():
while True:
task = asyncio.create_task(work())
await asyncio.sleep(2)
task.cancel()
with suppress(asyncio.CancelledError):
await task
asyncio.run(main())
output without calling clear_buffers()...
Listing...
DONE
Traceback (most recent call last):
File "c:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\test_client.py", line 24, in work
results = await lister
^^^^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\common.py", line 182, in _to_list
async for item in self:
items.append(item)
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 980, in __anext__
cls.stream = await cls._new_stream(current_path)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 955, in _new_stream
return await self.get_stream(command, "1xx")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 1403, in get_stream
await self.command(command, expected_codes, wait_codes, censor_after)
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 397, in command
code, info = await self.parse_response()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 301, in parse_response
code, rest = await self.parse_line()
^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 281, in parse_line
line = await self.stream.readline()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\common.py", line 673, in readline
data = await super().readline()
^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\common.py", line 79, in wrapper
return await asyncio.wait_for(f(*args, **kwargs), timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\AppData\Local\Programs\Python\Python313\Lib\asyncio\tasks.py", line 507, in wait_for
return await fut
^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\common.py", line 402, in readline
return await self.reader.readline()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\AppData\Local\Programs\Python\Python313\Lib\asyncio\streams.py", line 562, in readline
line = await self.readuntil(sep)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\AppData\Local\Programs\Python\Python313\Lib\asyncio\streams.py", line 677, in readuntil
await self._wait_for_data('readuntil')
File "C:\Users\cmaynes\AppData\Local\Programs\Python\Python313\Lib\asyncio\streams.py", line 539, in _wait_for_data
await self._waiter
asyncio.exceptions.CancelledError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\test_client.py", line 44, in <module>
asyncio.run(main())
~~~~~~~~~~~^^^^^^^^
File "C:\Users\cmaynes\AppData\Local\Programs\Python\Python313\Lib\asyncio\runners.py", line 195, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "C:\Users\cmaynes\AppData\Local\Programs\Python\Python313\Lib\asyncio\runners.py", line 118, in run
return self._loop.run_until_complete(task)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "C:\Users\cmaynes\AppData\Local\Programs\Python\Python313\Lib\asyncio\base_events.py", line 725, in run_until_complete
return future.result()
~~~~~~~~~~~~~^^
File "c:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\test_client.py", line 38, in main
await task
File "c:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\test_client.py", line 29, in work
await client.quit()
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 1314, in quit
await self.command("QUIT", "2xx")
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 401, in command
self.check_codes(expected_codes, code, info)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\cmaynes\Desktop\repos\jma-wireless-oem-test-data-sync-service\.venv\Lib\site-packages\aioftp\client.py", line 331, in check_codes
raise errors.StatusCodeError(expected_codes, received_code, info)
aioftp.errors.StatusCodeError: Waiting for ('2xx',) but got 150 [' mlsd transfer started']
Environment
OS: Windows 10
Python Version: 3.13.7
aioftp Version: 0.26.2
Problem
I think a problem in
list()is it cannot handle cancellation and have the client recover properly because the buffers still contain data from the previous command. If we cancel in the middle of thelist()call,aioftp.errors.StatusCodeErroris raised when we try to run another command or callquit()in this case.This can be reproduced with the following code:
if you uncomment
# clear_buffers(), theclient.quit()call is able to run successfully.output without calling
clear_buffers()...